def get_exam_date_next_semester(course): """ Return a start date of an exam next semester. Looking for the latest course run that has just finished or is currently running, and uses its upgrade deadline as an even in time relative to which we can find an exam run this semester or next semester. Args: course (courses.models.Course): A course Returns: str: a string representation exam start date, example: Apr 5, 2021 """ current_course_run = (CourseRun.objects.filter( start_date__lte=now_in_utc(), course=course).order_by('-start_date').first()) three_months = datetime.timedelta(weeks=12) if current_course_run is None or current_course_run.upgrade_deadline is None: next_date = now_in_utc() + three_months else: next_date = current_course_run.upgrade_deadline + three_months exam_run = ExamRun.get_schedulable_in_future(course).filter( date_first_schedulable__gte=next_date).order_by( 'date_first_schedulable').first() return exam_run.date_last_eligible.strftime( '%b %-d, %Y') if exam_run else ""
def test_count_courses_mixed_fa(self): """ Test count_courses_passed with mixed course-exam configuration """ mmtrack = MMTrack( user=self.user, program=self.program_financial_aid, edx_user_data=self.cached_edx_user_data ) # this is course with exam run and the user has CombinedFinalGrade for it course_with_exam_1 = CourseFactory.create(program=self.program_financial_aid) ExamRunFactory.create(course=course_with_exam_1, date_grades_available=now_in_utc()-timedelta(weeks=1)) CombinedFinalGrade.objects.create(user=self.user, course=course_with_exam_1, grade=0.7) # create course with exam run the user did not pass ExamRunFactory.create( course__program=self.program_financial_aid, date_grades_available=now_in_utc() - timedelta(weeks=1) ) # another course with no exam FinalGradeFactory.create( user=self.user, course_run=self.crun_fa, passed=True ) assert mmtrack.count_courses_passed() == 2
def setUpTestData(cls): cls.user = SocialUserFactory.create() cls.run_fa = CourseRunFactory.create( freeze_grade_date=now_in_utc() - timedelta(days=1), course__program__financial_aid_availability=True, ) cls.run_fa_with_cert = CourseRunFactory.create( freeze_grade_date=None, course__program=cls.run_fa.course.program, ) cls.run_no_fa = CourseRunFactory.create( freeze_grade_date=now_in_utc() + timedelta(days=1), course__program__financial_aid_availability=False, ) cls.run_no_fa_with_cert = CourseRunFactory.create( course__program=cls.run_no_fa.course.program, ) all_course_runs = ( cls.run_fa, cls.run_fa_with_cert, cls.run_no_fa, cls.run_no_fa_with_cert, ) for run in all_course_runs: if run.course.program.financial_aid_availability: FinancialAidFactory.create( user=cls.user, tier_program=TierProgramFactory.create( program=run.course.program, income_threshold=0, current=True), status=FinancialAidStatus.RESET, ) cls.enrollments = { course_run.edx_course_key: CachedEnrollmentFactory.create(user=cls.user, course_run=course_run) for course_run in all_course_runs } cls.current_grades = { course_run.edx_course_key: CachedCurrentGradeFactory.create(user=cls.user, course_run=course_run) for course_run in all_course_runs } cls.certificates = { course_run.edx_course_key: CachedCertificateFactory.create(user=cls.user, course_run=course_run) for course_run in (cls.run_fa_with_cert, cls.run_no_fa_with_cert) } cls.user_edx_data = CachedEdxUserData(cls.user)
def setUpTestData(cls): cls.user = SocialUserFactory.create() cls.run_fa = CourseRunFactory.create( freeze_grade_date=now_in_utc()-timedelta(days=1), course__program__financial_aid_availability=True, ) cls.run_fa_with_cert = CourseRunFactory.create( freeze_grade_date=None, course__program=cls.run_fa.course.program, ) cls.run_no_fa = CourseRunFactory.create( freeze_grade_date=now_in_utc()+timedelta(days=1), course__program__financial_aid_availability=False, ) cls.run_no_fa_with_cert = CourseRunFactory.create( course__program=cls.run_no_fa.course.program, ) all_course_runs = (cls.run_fa, cls.run_fa_with_cert, cls.run_no_fa, cls.run_no_fa_with_cert, ) for run in all_course_runs: if run.course.program.financial_aid_availability: FinancialAidFactory.create( user=cls.user, tier_program=TierProgramFactory.create( program=run.course.program, income_threshold=0, current=True ), status=FinancialAidStatus.RESET, ) cls.enrollments = { course_run.edx_course_key: CachedEnrollmentFactory.create( user=cls.user, course_run=course_run) for course_run in all_course_runs } cls.current_grades = { course_run.edx_course_key: CachedCurrentGradeFactory.create( user=cls.user, course_run=course_run) for course_run in all_course_runs } cls.certificates = { course_run.edx_course_key: CachedCertificateFactory.create( user=cls.user, course_run=course_run) for course_run in (cls.run_fa_with_cert, cls.run_no_fa_with_cert) } cls.user_edx_data = CachedEdxUserData(cls.user)
def create_user_for_login(is_staff=True, username=None): """Create a test user that can log into the app""" later = now_in_utc() + timedelta(weeks=5000) with mute_signals(post_save): user = SocialProfileFactory.create( validated=True, user__is_staff=is_staff, image=None, # make these None so the default image is used image_small=None, image_medium=None, **({'user__username': username} if username is not None else {}), user__social_auth__extra_data={ 'access_token': 'fake', 'refresh_token': 'fake', 'updated_at': later.timestamp(), 'expires_in': 3600, } ).user UserCacheRefreshTime.objects.create( user=user, enrollment=later, certificate=later, current_grade=later, ) user.set_password(DEFAULT_PASSWORD) user.save() return user
def from_weeks(weeks, now=None): """Helper function to get a date adjusted by a number of weeks""" if weeks is None: return None if now is None: now = now_in_utc() return now + timedelta(weeks=weeks)
def test_no_frozen_runs(self): """ The course has run with no grading status """ now = now_in_utc() self.create_run(freeze_grade_date=now - timedelta(weeks=1)) assert self.course.has_frozen_runs() is False
def test_props(self, program_data): """ Fixture that provides test properties for FinancialAidDetailView test cases """ user = create_enrolled_profile(program_data.program).user pending_fa = FinancialAidFactory.create( user=user, tier_program=program_data.tier_programs["25k"], status=FinancialAidStatus.PENDING_DOCS ) docs_sent_url = reverse( "financial_aid", kwargs={"financial_aid_id": pending_fa.id} ) docs_sent_date = now_in_utc().date() docs_sent_request_params = dict( content_type="application/json", data=json.dumps({ "date_documents_sent": docs_sent_date.strftime("%Y-%m-%d") }) ) return SimpleNamespace( user=user, pending_fa=pending_fa, docs_sent_url=docs_sent_url, docs_sent_request_params=docs_sent_request_params, docs_sent_date=docs_sent_date, )
def calculate_users_to_refresh_in_bulk(): """ Calculate the set of user ids which would be updated when running a bulk update. This uses a 6 hour delta because this is a bulk operation. For individual updates see CachedEdxDataApi.is_cache_fresh. Returns: list of int: A list of user ids which need to be updated """ refresh_time_limit = now_in_utc() - datetime.timedelta(hours=6) all_users = User.objects.filter(is_active=True, profile__fake_user=False).exclude(social_auth=None) con = get_redis_connection("redis") user_ids_invalid_credentials = con.smembers(CACHE_KEY_FAILED_USERS_NOT_TO_UPDATE) # If one of these fields is null in the database the gte expression will be false, so we will refresh those users users_not_expired = all_users.filter( usercacherefreshtime__enrollment__gte=refresh_time_limit, usercacherefreshtime__certificate__gte=refresh_time_limit, usercacherefreshtime__current_grade__gte=refresh_time_limit ) return list( all_users .exclude(id__in=users_not_expired.values_list("id", flat=True)) .exclude(id__in=user_ids_invalid_credentials) .values_list("id", flat=True) )
def _upload_to(_, filename): """Function passed to upload_to on an ImageField""" name, ext = path.splitext(filename) timestamp = now_in_utc().replace(microsecond=0) path_format = "profile/{name}-{timestamp}{suffix}{ext}" path_without_name = path_format.format( timestamp=timestamp.strftime("%Y-%m-%dT%H%M%S-%z"), suffix=suffix, ext=ext, name='', ) if len(path_without_name) >= IMAGE_PATH_MAX_LENGTH: raise ValueError( "path is longer than max length even without name: {}".format( path_without_name)) max_name_length = IMAGE_PATH_MAX_LENGTH - len(path_without_name) full_path = path_format.format( name=name[:max_name_length], timestamp=timestamp.strftime("%Y-%m-%dT%H%M%S-%z"), suffix=suffix, ext=ext, ) return full_path
def update(self, request, *args, **kwargs): """ Overrides get_object in case financialaid object does not exist, as the learner may skip financial aid either after starting the process or in lieu of applying """ user = request.user program = get_object_or_404(Program, id=self.kwargs["program_id"]) if not program.financial_aid_availability: raise ValidationError( "Financial aid not available for this program.") if not ProgramEnrollment.objects.filter(program=program.id, user=user).exists(): raise ValidationError("User not in program.") financialaid = FinancialAid.objects.filter( user=user, tier_program__program=program, ).exclude(status=FinancialAidStatus.RESET).first() if financialaid is None: financialaid = FinancialAid( user=user, country_of_income=user.profile.country, date_exchange_rate=now_in_utc(), country_of_residence=user.profile.country, ) if financialaid.status in FinancialAidStatus.TERMINAL_STATUSES: raise ValidationError( "Financial aid application cannot be skipped once it's been approved or skipped." ) financialaid.tier_program = get_no_discount_tier_program(program.id) financialaid.status = FinancialAidStatus.SKIPPED financialaid.save_and_log(user) return Response(status=HTTP_200_OK)
def get(self, request, *args, **kargs): # pylint: disable=unused-argument, no-self-use """ Request for exam SSO parameters """ profile = request.user.profile student_id = profile.student_id if not ExamProfile.objects.filter( profile=profile, status=ExamProfile.PROFILE_SUCCESS, ).exists(): # UI should in theory not send a user here in this state, # but it's not impossible so let's handle it politely return Response(data={ 'error': 'You are not ready to schedule an exam at this time', }, status=status.HTTP_403_FORBIDDEN) timestamp = int(now_in_utc().timestamp()) session_timeout = request.session.get_expiry_age() try: digest = sso_digest(student_id, timestamp, session_timeout) except ImproperlyConfigured: return Response(status=500) return Response(data={ 'sso_digest': digest, 'timestamp': timestamp, 'session_timeout': session_timeout, 'sso_redirect_url': request.build_absolute_uri('/'), })
def batch_update_user_data(): """ Create sub tasks to update user data like enrollments, certificates and grades from edX platform. """ expiration = now_in_utc() + timedelta(hours=5) lock = Lock(LOCK_ID, expiration) if not lock.acquire(): # Lock should have expired by now log.error("Unable to acquire lock for batch_update_user_data") return users_to_refresh = calculate_users_to_refresh_in_bulk() jobs = release_batch_update_user_data_lock.s(token=lock.token.decode()) try: if len(users_to_refresh) > 0: user_id_chunks = chunks(users_to_refresh) job = group( batch_update_user_data_subtasks.s(user_id_chunk, expiration.timestamp()) for user_id_chunk in user_id_chunks ) jobs = job | jobs finally: jobs.delay()
def test_get_pearson_exam_status(self, profile_status, expected_status, make_exam_run, make_profile, make_auth, log_error_called, log_mock): """ test get_pearson_exam_status """ now = now_in_utc() exam_run = None if make_exam_run: exam_run = ExamRunFactory.create( course=self.course, date_first_eligible=now - timedelta(weeks=1), date_last_eligible=now + timedelta(weeks=1), ) if make_profile: ExamProfileFactory.create( profile=self.user.profile, status=profile_status, ) if make_auth: ExamAuthorizationFactory.create( user=self.user, status=ExamAuthorization.STATUS_SUCCESS, exam_run=exam_run, ) mmtrack = MMTrack( user=self.user, program=self.program, edx_user_data=self.cached_edx_user_data ) assert mmtrack.get_pearson_exam_status() == expected_status assert log_mock.error.called is log_error_called
def take_screenshot(self, filename=None, filename_prefix='', output_base64=False): """ Takes a screenshot of the selenium browser window and saves it """ self.set_dimension() if filename is None: if filename_prefix: filename_prefix += '_' filename = '{}{}'.format( filename_prefix, now_in_utc().strftime('%Y_%m_%d_%H_%M_%S_%f')) repo_root = os.path.dirname(os.path.dirname( os.path.realpath(__file__))) full_filename = os.path.join(repo_root, "{}.png".format(filename)) self.driver.save_screenshot(full_filename) print("PNG screenshot for {filename} output to {full_filename}".format( filename=filename, full_filename=full_filename, )) if output_base64: # Can be useful for travis where we don't have access to build artifacts with open(full_filename, 'rb') as f: print("Screenshot as base64: {}".format(b64encode(f.read())))
def test_update_cached_current_grades(self, mocked_index): """Test for update_cached_current_grades.""" self.assert_cache_in_db() assert UserCacheRefreshTime.objects.filter( user=self.user).exists() is False CachedEdxDataApi.update_cached_current_grades(self.user, self.edx_client) self.assert_cache_in_db(grades_keys=self.grades_ids) cache_time = UserCacheRefreshTime.objects.get(user=self.user) now = now_in_utc() assert cache_time.current_grade <= now assert mocked_index.delay.called is True mocked_index.reset_mock() # add another cached element for another course that will be removed by the refresh cached_grade = CachedCurrentGradeFactory.create(user=self.user) self.assert_cache_in_db(grades_keys=list(self.grades_ids) + [cached_grade.course_run.edx_course_key]) CachedEdxDataApi.update_cached_current_grades(self.user, self.edx_client) self.assert_cache_in_db(grades_keys=self.grades_ids) cache_time.refresh_from_db() assert cache_time.current_grade >= now mocked_index.delay.assert_called_once_with([self.user.id], check_if_changed=True)
def test_is_cache_fresh(self): """Test for is_cache_fresh""" with self.assertRaises(ValueError): CachedEdxDataApi.is_cache_fresh(self.user, 'footype') # if there is no entry in the table, the cache is not fresh assert UserCacheRefreshTime.objects.filter( user=self.user).exists() is False for cache_type in CachedEdxDataApi.SUPPORTED_CACHES: assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is False now = now_in_utc() user_cache = UserCacheRefreshTimeFactory.create( user=self.user, enrollment=now, certificate=now, current_grade=now, ) for cache_type in CachedEdxDataApi.SUPPORTED_CACHES: assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is True # moving back the timestamp of one day, makes the cache not fresh again yesterday = now - timedelta(days=1) user_cache.enrollment = yesterday user_cache.certificate = yesterday user_cache.current_grade = yesterday user_cache.save() for cache_type in CachedEdxDataApi.SUPPORTED_CACHES: assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is False
def batch_update_user_data(): """ Create sub tasks to update user data like enrollments, certificates and grades from edX platform. """ expiration = now_in_utc() + timedelta(hours=5) lock = Lock(LOCK_ID, expiration) if not lock.acquire(): # Lock should have expired by now log.error("Unable to acquire lock for batch_update_user_data") return users_to_refresh = calculate_users_to_refresh_in_bulk() jobs = release_batch_update_user_data_lock.s(token=lock.token.decode()) try: if len(users_to_refresh) > 0: user_id_chunks = chunks(users_to_refresh) job = group( batch_update_user_data_subtasks.s(user_id_chunk, expiration.timestamp()) for user_id_chunk in user_id_chunks) jobs = job | jobs finally: jobs.delay()
def handle_cdd(self, sftp_conn, remote_path, ratio): """Handle a CDD file""" now = now_in_utc() result_file = io.StringIO() writer = csv.DictWriter(result_file, [ 'ClientCandidateID', 'Status', 'Date', 'Message', ], delimiter='\t') writer.writeheader() with sftp_conn.open(remote_path, mode='r') as cdd_file: for row in csv.DictReader(cdd_file, delimiter='\t'): cid = row['ClientCandidateID'] error = random.random() > ratio status = 'Error' if error else 'Accepted' self.stdout.write('Marking profile {cid} as {status}'.format( cid=cid, status=status, )) writer.writerow({ 'ClientCandidateID': cid, 'Status': 'Error' if error else 'Accepted', 'Date': now.strftime(PEARSON_DEFAULT_DATETIME_FORMAT), 'Message': 'Invalid Address' if error else '', }) self.write_zip( sftp_conn, result_file.getvalue(), now.strftime('{}-%Y-%m-%d.dat'.format(PEARSON_FILE_TYPES.VCDC)), now )
def save(self, **kwargs): """ Override save method """ try: income_usd = determine_income_usd( self.validated_data["original_income"], self.validated_data["original_currency"]) except NotSupportedException: raise ValidationError("Currency not supported") user = self.context["request"].user tier_program = determine_tier_program(self.validated_data["program"], income_usd) financial_aid = FinancialAid.objects.create( original_income=self.validated_data["original_income"], original_currency=self.validated_data["original_currency"], tier_program=tier_program, user=user, income_usd=income_usd, country_of_income=user.profile.country, date_exchange_rate=now_in_utc(), country_of_residence=user.profile.country, ) if determine_auto_approval(financial_aid, tier_program) is True: financial_aid.status = FinancialAidStatus.AUTO_APPROVED else: financial_aid.status = FinancialAidStatus.PENDING_DOCS financial_aid.save_and_log(user) return financial_aid
def test_successful_program_certificate_generation_with_electives(self): """ Test has final grade and a certificate with elective courses """ run_2 = CourseRunFactory.create( freeze_grade_date=now_in_utc() - timedelta(days=1), course__program=self.program, ) electives_set = ElectivesSet.objects.create(program=self.program, required_number=1) for run in [self.run_1, run_2]: final_grade = FinalGradeFactory.create( user=self.user, course_run=run, passed=True, status='complete', grade=0.7 ) CourseRunGradingStatus.objects.create(course_run=run, status='complete') ElectiveCourse.objects.create(course=run.course, electives_set=electives_set) with mute_signals(post_save): MicromastersCourseCertificate.objects.create(course=final_grade.course_run.course, user=self.user) cert_qset = MicromastersProgramCertificate.objects.filter(user=self.user, program=self.program) assert cert_qset.exists() is False api.generate_program_certificate(self.user, self.program) assert cert_qset.exists() is True
def save(self, **kwargs): """ Override save method """ try: income_usd = determine_income_usd( self.validated_data["original_income"], self.validated_data["original_currency"] ) except NotSupportedException: raise ValidationError("Currency not supported") user = self.context["request"].user tier_program = determine_tier_program(self.validated_data["program"], income_usd) financial_aid = FinancialAid.objects.create( original_income=self.validated_data["original_income"], original_currency=self.validated_data["original_currency"], tier_program=tier_program, user=user, income_usd=income_usd, country_of_income=user.profile.country, date_exchange_rate=now_in_utc(), country_of_residence=user.profile.country, ) if determine_auto_approval(financial_aid, tier_program) is True: financial_aid.status = FinancialAidStatus.AUTO_APPROVED else: financial_aid.status = FinancialAidStatus.PENDING_DOCS financial_aid.save_and_log(user) return financial_aid
def test_get_best_proctored_exam_grade(self): """ Test get_best_proctorate_exam_grade to return a passed exam with the highest score """ mmtrack = MMTrack( user=self.user, program=self.program_financial_aid, edx_user_data=self.cached_edx_user_data ) finaid_course = self.crun_fa.course last_week = now_in_utc() - timedelta(weeks=1) ProctoredExamGradeFactory.create(user=self.user, course=finaid_course, passed=False, percentage_grade=0.6) assert mmtrack.get_best_proctored_exam_grade(finaid_course) is None best_exam = ProctoredExamGradeFactory.create( user=self.user, course=finaid_course, passed=True, percentage_grade=0.9, exam_run__date_grades_available=last_week ) assert mmtrack.get_best_proctored_exam_grade(finaid_course) == best_exam ProctoredExamGradeFactory.create( user=self.user, course=finaid_course, passed=True, percentage_grade=0.8, exam_run__date_grades_available=last_week ) assert mmtrack.get_best_proctored_exam_grade(finaid_course) == best_exam
def test_get_exam_card_status_for_edx_exams(self, profile_status, expected_status, make_exam_run, make_profile, make_auth): """ test get_exam_card_status """ now = now_in_utc() exam_run = None if make_exam_run: exam_run = ExamRunFactory.create( course=self.course, date_first_eligible=now - timedelta(weeks=1), date_last_eligible=now + timedelta(weeks=1), ) if make_profile: ExamProfileFactory.create( profile=self.user.profile, status=profile_status, ) if make_auth: ExamAuthorizationFactory.create( user=self.user, status=ExamAuthorization.STATUS_SUCCESS, exam_run=exam_run, ) mmtrack = MMTrack( user=self.user, program=self.program, edx_user_data=self.cached_edx_user_data ) assert mmtrack.get_exam_card_status() == expected_status
def can_freeze_grades(self): """ Checks if the final grades can be frozen. """ if self.freeze_grade_date is None: raise ImproperlyConfigured('Missing freeze_grade_date') return now_in_utc() > self.freeze_grade_date
def create_user_for_login(is_staff=True, username=None): """Create a test user that can sign into the app""" later = now_in_utc() + timedelta(weeks=5000) with mute_signals(post_save): user = SocialProfileFactory.create( validated=True, user__is_staff=is_staff, image=None, # make these None so the default image is used image_small=None, image_medium=None, **({ 'user__username': username } if username is not None else {}), user__social_auth__extra_data={ 'access_token': 'fake', 'refresh_token': 'fake', 'updated_at': later.timestamp(), 'expires_in': 3600, }).user UserCacheRefreshTime.objects.create( user=user, enrollment=later, certificate=later, current_grade=later, ) user.set_password(DEFAULT_PASSWORD) user.save() return user
def __init__(self, user, program, edx_user_data): """ Args: user (User): a Django user program (programs.models.Program): program where the user is enrolled edx_user_data (dashboard.api_edx_cache.CachedEdxUserData): A CachedEdxUserData object """ self.now = now_in_utc() self.user = user self.program = program self.enrollments = edx_user_data.enrollments self.current_grades = edx_user_data.current_grades self.certificates = edx_user_data.certificates self.financial_aid_available = program.financial_aid_availability self.paid_course_fa = {} # courses_id -> payment number association for financial aid courses with transaction.atomic(): # Maps a CourseRun's edx_course_key to its parent Course id self.edx_key_course_map = dict( CourseRun.objects.filter(course__program=program).exclude( Q(edx_course_key__isnull=True) | Q(edx_course_key__exact='') ).values_list("edx_course_key", "course__id") ) self.edx_course_keys = set(self.edx_key_course_map.keys()) if self.financial_aid_available: # edx course keys for courses with no exam self.edx_course_keys_no_exam = set(CourseRun.objects.filter( course__program=program, course__exam_runs__isnull=True ).values_list("edx_course_key", flat=True)) for course in self.program.course_set.all(): self.paid_course_fa[course.id] = self.get_payments_count_for_course(course) > 0
def test_update_cache_last_access(self): """Test for update_cache_last_access""" with self.assertRaises(ValueError): CachedEdxDataApi.update_cache_last_access(self.user, 'footype') assert UserCacheRefreshTime.objects.filter(user=self.user).exists() is False CachedEdxDataApi.update_cache_last_access(self.user, CachedEdxDataApi.ENROLLMENT) cache_time = UserCacheRefreshTime.objects.get(user=self.user) assert cache_time.enrollment <= now_in_utc() assert cache_time.certificate is None assert cache_time.current_grade is None old_timestamp = now_in_utc() - timedelta(days=1) CachedEdxDataApi.update_cache_last_access(self.user, CachedEdxDataApi.ENROLLMENT, old_timestamp) cache_time.refresh_from_db() assert cache_time.enrollment == old_timestamp
def calculate_users_to_refresh_in_bulk(): """ Calculate the set of user ids which would be updated when running a bulk update. This uses a 6 hour delta because this is a bulk operation. For individual updates see CachedEdxDataApi.is_cache_fresh. Returns: list of int: A list of user ids which need to be updated """ refresh_time_limit = now_in_utc() - datetime.timedelta(hours=6) all_users = User.objects.filter( is_active=True, profile__fake_user=False).exclude(social_auth=None) con = get_redis_connection("redis") user_ids_invalid_credentials = con.smembers( CACHE_KEY_FAILED_USERS_NOT_TO_UPDATE) # If one of these fields is null in the database the gte expression will be false, so we will refresh those users users_not_expired = all_users.filter( usercacherefreshtime__enrollment__gte=refresh_time_limit, usercacherefreshtime__certificate__gte=refresh_time_limit, usercacherefreshtime__current_grade__gte=refresh_time_limit) return list( all_users.exclude( id__in=users_not_expired.values_list("id", flat=True)).exclude( id__in=user_ids_invalid_credentials).values_list("id", flat=True))
def test_sso_get_with_exam_profile_success(self): """ Test issuing a GET request when user has an ExamProfile in PROFILE_SUCCESS status """ ExamProfile.objects.create( profile=self.user.profile, status=ExamProfile.PROFILE_SUCCESS, ) with patch('exams.views.sso_digest', return_value='test value'): response = self.client.get(reverse("pearson_sso_api")) result = response.json() assert response.status_code == status.HTTP_200_OK timestamp = result['timestamp'] assert isinstance(timestamp, int) now = int(now_in_utc().timestamp()) assert now - timestamp < 5 assert result['sso_digest'] == 'test value' # best we can assert is that this is an integer assert isinstance(result['session_timeout'], int) assert result['sso_redirect_url'] == 'http://testserver/'
def has_to_pay_for_exam(mmtrack, course): """ Determine if payment is required for another exam attempt Args: mmtrack (dashboard.utils.MMTrack): a instance of all user information about a program course (courses.models.Course): A course Returns: bool: if the user has to pay for another exam attempt """ now = now_in_utc() attempt_limit = None num_attempts = ATTEMPTS_PER_PAID_RUN if not mmtrack.program.exam_attempts_first_date: num_attempts = ATTEMPTS_PER_PAID_RUN_OLD elif mmtrack.program.exam_attempts_first_date > now: # still before the date num_attempts = ATTEMPTS_PER_PAID_RUN_OLD elif mmtrack.program.exam_attempts_second_date > now: # in between the dates: check when the user paid for each course run attempt_limit = mmtrack.get_custom_number_of_attempts_for_course( course) if attempt_limit is None: attempt_limit = mmtrack.get_payments_count_for_course( course) * num_attempts return ExamAuthorization.objects.filter( user=mmtrack.user, course=course, exam_taken=True).count() >= attempt_limit
def get_edx_exam_coupon_url(user, course): """ Find a successful exam authorization and return the coupon url for the exam Args: user (User): a user course (courses.models.Course): A course Returns: str: a url to the exam or empty string """ now = now_in_utc() schedulable_exam_runs = ExamRun.get_currently_schedulable(course) exam_auth = ExamAuthorization.objects.filter( user=user, course=course, status=ExamAuthorization.STATUS_SUCCESS, exam_run__in=schedulable_exam_runs).first() if exam_auth is None: return "" if exam_auth.exam_coupon is not None: return exam_auth.exam_coupon.coupon_url exam_coupon = ExamRunCoupon.objects.filter(expiration_date__gte=now.date(), is_taken=False, course=course).first() if exam_coupon is None: return "" exam_auth.exam_coupon = exam_coupon exam_auth.save() return exam_coupon.use_coupon()
def is_upgradable(self): """ Checks if the course can be upgraded A null value means that the upgrade window is always open """ return (self.upgrade_deadline is None or (self.upgrade_deadline > now_in_utc()))
def test_is_cache_fresh(self): """Test for is_cache_fresh""" with self.assertRaises(ValueError): CachedEdxDataApi.is_cache_fresh(self.user, 'footype') # if there is no entry in the table, the cache is not fresh assert UserCacheRefreshTime.objects.filter(user=self.user).exists() is False for cache_type in CachedEdxDataApi.SUPPORTED_CACHES: assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is False now = now_in_utc() user_cache = UserCacheRefreshTimeFactory.create( user=self.user, enrollment=now, certificate=now, current_grade=now, ) for cache_type in CachedEdxDataApi.SUPPORTED_CACHES: assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is True # moving back the timestamp of one day, makes the cache not fresh again yesterday = now - timedelta(days=1) user_cache.enrollment = yesterday user_cache.certificate = yesterday user_cache.current_grade = yesterday user_cache.save() for cache_type in CachedEdxDataApi.SUPPORTED_CACHES: assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is False
def is_not_beyond_enrollment(self): """ Checks if the course is not beyond its enrollment period """ now = now_in_utc() return ((self.enrollment_end is None and (self.end_date is None or self.end_date > now)) or self.enrollment_end > now)
def test_update_exam_authorization_cached_enrollment_when_no_exam_run(self): """ Test no exam profile created when course has no ExamRun """ self.exam_run.delete() course = CourseFactory.create(program=self.program) course_run = CourseRunFactory.create( end_date=now_in_utc() - timedelta(days=100), enrollment_end=now_in_utc() + timedelta(hours=1), course=course ) create_order(self.profile.user, course_run) # exam profile before enrollment. assert ExamProfile.objects.filter(profile=self.profile).exists() is False CachedEnrollmentFactory.create(user=self.profile.user, course_run=course_run) assert ExamProfile.objects.filter(profile=self.profile).exists() is False
def test_has_frozen_run_and_pending(self): """ The course has a run with pending status """ now = now_in_utc() not_frozen_run = self.create_run(freeze_grade_date=now - timedelta(weeks=1)) CourseRunGradingStatus.objects.create(course_run=not_frozen_run, status='pending') assert self.course.has_frozen_runs() is False
def setUpTestData(cls): cls.user = SocialUserFactory.create() cls.run_1 = CourseRunFactory.create( freeze_grade_date=now_in_utc()-timedelta(days=1), course__program__financial_aid_availability=True, ) cls.program = cls.run_1.course.program
def setUpTestData(cls): cls.users = [UserFactory.create() for _ in range(35)] freeze_date = now_in_utc()-timedelta(days=1) future_freeze_date = now_in_utc()+timedelta(days=1) cls.course_run1 = CourseRunFactory.create(freeze_grade_date=freeze_date) cls.course_run2 = CourseRunFactory.create(freeze_grade_date=freeze_date) cls.all_freezable_runs = [cls.course_run1, cls.course_run2] cls.course_run_future = CourseRunFactory.create(freeze_grade_date=future_freeze_date) cls.course_run_frozen = CourseRunFactory.create(freeze_grade_date=freeze_date) CourseRunGradingStatus.objects.create(course_run=cls.course_run_frozen, status=FinalGradeStatus.COMPLETE) for user in cls.users: CachedEnrollmentFactory.create(user=user, course_run=cls.course_run1) CachedCurrentGradeFactory.create(user=user, course_run=cls.course_run1)
def is_current(self): """Checks if the course is running now""" if not self.start_date: return False now = now_in_utc() if not self.end_date: return self.start_date <= now return self.start_date <= now <= self.end_date
def setUpTestData(cls): cls.user = SocialUserFactory.create() cls.run_1 = CourseRunFactory.create( freeze_grade_date=now_in_utc()-timedelta(days=1), course__program__financial_aid_availability=True, ) CourseRunGradingStatus.objects.create(course_run=cls.run_1, status='complete') cls.program = cls.run_1.course.program
def test_has_frozen_run_and_another_run(self): """ The course has one frozen run, and a run with no grading status """ now = now_in_utc() course_run = self.create_run(freeze_grade_date=now - timedelta(weeks=1)) CourseRunGradingStatus.objects.create(course_run=course_run, status='complete') self.create_run() assert self.course.has_frozen_runs() is True
def is_not_beyond_enrollment(self): """ Checks if the course is not beyond its enrollment period """ now = now_in_utc() return ( (self.enrollment_end is None and (self.end_date is None or self.end_date > now)) or self.enrollment_end > now )
def is_still_locked(self, *args, **kwargs): # pylint: disable=unused-argument """ Is the lock held? Arguments are ignored to allow for easier use with itertools.takewhile Returns: bool: True if the lock is held """ return self.acquired and self.expiration > now_in_utc()
def is_exam_schedulable(user, course): """ Check if a course is ready to schedule an exam or not """ now = now_in_utc() schedulable_exam_runs = ExamRun.objects.filter( course=course, date_last_eligible__gte=now.date() ) return ExamAuthorization.objects.filter(user=user, exam_run__in=schedulable_exam_runs).exclude( operation=ExamAuthorization.OPERATION_DELETE).exists()
def is_valid(self): """Returns true if the coupon is enabled and also within the valid date range""" now = now_in_utc() if not self.enabled: return False if self.activation_date is not None and now <= self.activation_date: return False if self.expiration_date is not None and now >= self.expiration_date: return False return True
def generate_cybersource_sa_payload(order, dashboard_url): """ Generates a payload dict to send to CyberSource for Secure Acceptance Args: order (Order): An order dashboard_url: (str): The absolute url for the dashboard Returns: dict: the payload to send to CyberSource via Secure Acceptance """ # http://apps.cybersource.com/library/documentation/dev_guides/Secure_Acceptance_WM/Secure_Acceptance_WM.pdf # Section: API Fields # Course key is used only to show the confirmation message to the user course_key = "" line = order.line_set.first() if line is not None: course_key = line.course_key course_run = CourseRun.objects.get(edx_course_key=course_key) # NOTE: be careful about max length here, many (all?) string fields have a max # length of 255. At the moment none of these fields should go over that, due to database # constraints or other reasons payload = { 'access_key': settings.CYBERSOURCE_ACCESS_KEY, 'amount': str(order.total_price_paid), 'consumer_id': get_social_username(order.user), 'currency': 'USD', 'locale': 'en-us', 'item_0_code': 'course', 'item_0_name': '{}'.format(course_run.title), 'item_0_quantity': 1, 'item_0_sku': '{}'.format(course_key), 'item_0_tax_amount': '0', 'item_0_unit_price': str(order.total_price_paid), 'line_item_count': 1, 'override_custom_cancel_page': make_dashboard_receipt_url(dashboard_url, course_key, 'cancel'), 'override_custom_receipt_page': make_dashboard_receipt_url(dashboard_url, course_key, 'receipt'), 'reference_number': make_reference_id(order), 'profile_id': settings.CYBERSOURCE_PROFILE_ID, 'signed_date_time': now_in_utc().strftime(ISO_8601_FORMAT), 'transaction_type': 'sale', 'transaction_uuid': uuid.uuid4().hex, 'unsigned_field_names': '', 'merchant_defined_data1': 'course', 'merchant_defined_data2': '{}'.format(course_run.title), 'merchant_defined_data3': '{}'.format(course_key), } field_names = sorted(list(payload.keys()) + ['signed_field_names']) payload['signed_field_names'] = ','.join(field_names) payload['signature'] = generate_cybersource_sa_signature(payload) return payload