Example #1
0
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 ""
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
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
Example #7
0
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)
Example #8
0
 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
Example #9
0
 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,
     )
Example #10
0
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)
    )
Example #11
0
    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
Example #12
0
    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)
Example #13
0
    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('/'),
        })
Example #14
0
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()
Example #15
0
    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
Example #16
0
 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
Example #19
0
 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,
     )
Example #20
0
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
        )
Example #22
0
    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
Example #23
0
    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
Example #24
0
    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
Example #25
0
    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
Example #26
0
    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
Example #27
0
    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
Example #28
0
 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
Example #29
0
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
Example #30
0
    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
Example #31
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
Example #32
0
 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
Example #33
0
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/'
Example #35
0
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)
Example #36
0
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
Example #37
0
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()
Example #38
0
 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
Example #39
0
 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()))
Example #40
0
 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
Example #41
0
 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()))
Example #42
0
 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)
Example #43
0
    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
Example #44
0
 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
Example #45
0
    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
Example #46
0
    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)
Example #47
0
 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
Example #48
0
 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
Example #49
0
 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
Example #50
0
 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
Example #51
0
 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
Example #52
0
    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
Example #53
0
 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
Example #54
0
 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
     )
Example #55
0
    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()
Example #56
0
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()
Example #57
0
 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
Example #58
0
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