Ejemplo n.º 1
0
    def test_refundable_when_certificate_exists(self, cutoff_date):
        """ Assert that enrollment is not refundable once a certificat has been generated."""

        cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(days=1)

        self.assertTrue(self.enrollment.refundable())

        GeneratedCertificateFactory.create(
            user=self.user,
            course_id=self.course.id,
            status=CertificateStatuses.downloadable,
            mode='verified'
        )

        self.assertFalse(self.enrollment.refundable())
        self.assertFalse(
            self.enrollment.refundable(
                user_already_has_certs_for=GeneratedCertificate.course_ids_with_certs_for_user(self.user)
            )
        )

        # Assert that can_refund overrides this and allows refund
        self.enrollment.can_refund = True
        self.assertTrue(self.enrollment.refundable())
        self.assertTrue(
            self.enrollment.refundable(
                user_already_has_certs_for=GeneratedCertificate.course_ids_with_certs_for_user(self.user)
            )
        )
Ejemplo n.º 2
0
    def test_course_ids_with_certs_for_user(self):
        # Create one user with certs and one without
        student_no_certs = UserFactory()
        student_with_certs = UserFactory()
        student_with_certs.profile.allow_certificate = True
        student_with_certs.profile.save()

        # Set up a couple of courses
        course_1 = CourseFactory.create()
        course_2 = CourseFactory.create()

        # Generate certificates
        GeneratedCertificateFactory.create(
            user=student_with_certs,
            course_id=course_1.id,
            status=CertificateStatuses.downloadable,
            mode='honor'
        )
        GeneratedCertificateFactory.create(
            user=student_with_certs,
            course_id=course_2.id,
            status=CertificateStatuses.downloadable,
            mode='honor'
        )

        # User with no certs should return an empty set.
        self.assertSetEqual(
            GeneratedCertificate.course_ids_with_certs_for_user(student_no_certs),
            set()
        )
        # User with certs should return a set with the two course_ids
        self.assertSetEqual(
            GeneratedCertificate.course_ids_with_certs_for_user(student_with_certs),
            {course_1.id, course_2.id}
        )
Ejemplo n.º 3
0
def can_generate_allowlist_certificate(user, course_key):
    """
    Check if an allowlist certificate can be generated (created if it doesn't already exist, or updated if it does
    exist) for this user, in this course run.
    """
    if not is_using_certificate_allowlist(course_key):
        # This course run is not using the allowlist feature
        log.info(
            '{course} is not using the certificate allowlist. Certificate cannot be generated.'.format(
                course=course_key
            ))
        return False

    if not auto_certificate_generation_enabled():
        # Automatic certificate generation is globally disabled
        log.info('Automatic certificate generation is globally disabled. Certificate cannot be generated.')
        return False

    if CertificateInvalidation.has_certificate_invalidation(user, course_key):
        # The invalidation list overrides the allowlist
        log.info(
            '{user} : {course} is on the certificate invalidation list. Certificate cannot be generated.'.format(
                user=user.id,
                course=course_key
            ))
        return False

    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
    if enrollment_mode is None:
        log.info('{user} : {course} does not have an enrollment. Certificate cannot be generated.'.format(
            user=user.id,
            course=course_key
        ))
        return False

    if not IDVerificationService.user_has_ever_been_verified(user):
        log.info(f'{user.id} has not ever had a verified id. Certificate cannot be generated.')
        return False

    if not _is_on_certificate_allowlist(user, course_key):
        log.info('{user} : {course} is not on the certificate allowlist. Certificate cannot be generated.'.format(
            user=user.id,
            course=course_key
        ))
        return False

    log.info('{user} : {course} is on the certificate allowlist'.format(
        user=user.id,
        course=course_key
    ))
    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    return _can_generate_allowlist_certificate_for_status(cert)
Ejemplo n.º 4
0
def _listen_for_failing_grade(sender, user, course_id, grade, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a learner failing a course, mark the cert as notpassing
    if it is currently passing,
    downstream signal from COURSE_GRADE_CHANGED
    """
    cert = GeneratedCertificate.certificate_for_student(user, course_id)
    if cert is not None:
        if CertificateStatuses.is_passing_status(cert.status):
            cert.mark_notpassing(grade.percent)
            log.info(
                u'Certificate marked not passing for {user} : {course} via failing grade: {grade}'
                .format(user=user.id, course=course_id, grade=grade))
Ejemplo n.º 5
0
 def test_no_match(self):
     """
     Make sure a badge isn't created before a user's reached any checkpoint.
     """
     user = UserFactory()
     course = CourseFactory()
     GeneratedCertificate(
         # pylint: disable=no-member
         user=user,
         course_id=course.location.course_key,
         status=CertificateStatuses.downloadable).save()
     # pylint: disable=no-member
     self.assertFalse(user.badgeassertion_set.all())
Ejemplo n.º 6
0
 def test_cert_failure(self, status):
     if CertificateStatuses.is_passing_status(status):
         expected_status = CertificateStatuses.notpassing
     else:
         expected_status = status
     GeneratedCertificate.eligible_certificates.create(
         user=self.user,
         course_id=self.course.id,
         status=status
     )
     CourseGradeFactory().update(self.user, self.course)
     cert = GeneratedCertificate.certificate_for_student(self.user, self.course.id)
     self.assertEqual(cert.status, expected_status)
Ejemplo n.º 7
0
def _is_cert_downloadable(user, course_key):
    """
    Check if cert already exists, has a downloadable status, and has not been invalidated
    """
    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    if cert is None:
        return False
    if cert.status != CertificateStatuses.downloadable:
        return False
    if CertificateInvalidation.has_certificate_invalidation(user, course_key):
        return False

    return True
Ejemplo n.º 8
0
def fire_ungenerated_certificate_task(user, course_key, expected_verification_status=None):
    """
    Helper function to fire certificate generation task.
    Auto-generation of certificates is available for following course modes:
        1- VERIFIED
        2- CREDIT_MODE
        3- PROFESSIONAL
        4- NO_ID_PROFESSIONAL_MODE

    Certificate generation task is fired to either generate a certificate
    when there is no generated certificate for user in a particular course or
    update a certificate if it has 'unverified' status.

    Task is fired to attempt an update to a certificate
    with 'unverified' status as this method is called when a user is
    successfully verified, any certificate associated
    with such user can now be verified.

    NOTE: Purpose of restricting other course modes (HONOR and AUDIT) from auto-generation is to reduce
    traffic to workers.
    """

    message = u'Entered into Ungenerated Certificate task for {user} : {course}'
    log.info(message.format(user=user.id, course=course_key))

    allowed_enrollment_modes_list = [
        CourseMode.VERIFIED,
        CourseMode.CREDIT_MODE,
        CourseMode.PROFESSIONAL,
        CourseMode.NO_ID_PROFESSIONAL_MODE,
        CourseMode.MASTERS,
    ]
    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
    cert = GeneratedCertificate.certificate_for_student(user, course_key)

    generate_learner_certificate = (
        enrollment_mode in allowed_enrollment_modes_list and (cert is None or cert.status == 'unverified')
    )

    if generate_learner_certificate:
        kwargs = {
            'student': six.text_type(user.id),
            'course_key': six.text_type(course_key)
        }
        if expected_verification_status:
            kwargs['expected_verification_status'] = six.text_type(expected_verification_status)
        generate_certificate.apply_async(countdown=CERTIFICATE_DELAY_SECONDS, kwargs=kwargs)
        return True

    message = u'Certificate Generation task failed for {user} : {course}'
    log.info(message.format(user=user.id, course=course_key))
Ejemplo n.º 9
0
def _set_allowlist_cert_status(user, course_key, enrollment_mode,
                               course_grade):
    """
    Determine the allowlist certificate status for this user, in this course run and update the cert.

    This is used when a downloadable cert cannot be generated, but we want to provide more info about why it cannot
    be generated.
    """
    if not _can_set_allowlist_cert_status(user, course_key, enrollment_mode):
        return None

    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    return _get_cert_status_common(user, course_key, enrollment_mode,
                                   course_grade, cert)
Ejemplo n.º 10
0
def _get_user_certificate(request,
                          user,
                          course_key,
                          course_overview,
                          preview_mode=None):
    """
    Retrieves user's certificate from db. Creates one in case of preview mode.
    Returns None if there is no certificate generated for given user
    otherwise returns `GeneratedCertificate` instance.

    We use the course_overview instead of the course descriptor here, so we get the certificate_available_date and
    certificates_display_behavior validation logic, rather than the raw data from the course descriptor.
    """
    user_certificate = None
    if preview_mode:
        # certificate is being previewed from studio
        if request.user.has_perm(PREVIEW_CERTIFICATES, course_overview):
            if not settings.FEATURES.get("ENABLE_V2_CERT_DISPLAY_SETTINGS"):
                if course_overview.certificate_available_date and not course_overview.self_paced:
                    modified_date = course_overview.certificate_available_date
                else:
                    modified_date = datetime.now().date()
            else:
                if (course_overview.certificates_display_behavior
                        == CertificatesDisplayBehaviors.END_WITH_DATE
                        and course_overview.certificate_available_date
                        and not course_overview.self_paced):
                    modified_date = course_overview.certificate_available_date
                elif course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END:
                    modified_date = course_overview.end
                else:
                    modified_date = datetime.now().date()
            user_certificate = GeneratedCertificate(
                mode=preview_mode,
                verify_uuid=str(uuid4().hex),
                modified_date=modified_date,
                created_date=datetime.now().date(),
            )
    elif certificates_viewable_for_course(course_overview):
        # certificate is being viewed by learner or public
        try:
            user_certificate = GeneratedCertificate.eligible_certificates.get(
                user=user,
                course_id=course_key,
                status=CertificateStatuses.downloadable)
        except GeneratedCertificate.DoesNotExist:
            pass

    return user_certificate
Ejemplo n.º 11
0
def _listen_for_failing_grade(sender, user, course_id, grade, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a learner failing a course, mark the cert as notpassing
    if it is currently passing,
    downstream signal from COURSE_GRADE_CHANGED
    """
    cert = GeneratedCertificate.certificate_for_student(user, course_id)
    if cert is not None:
        if CertificateStatuses.is_passing_status(cert.status):
            cert.mark_notpassing(grade.percent)
            log.info(u'Certificate marked not passing for {user} : {course} via failing grade: {grade}'.format(
                user=user.id,
                course=course_id,
                grade=grade
            ))
Ejemplo n.º 12
0
    def test_refundable_when_certificate_exists(self, cutoff_date):
        """ Assert that enrollment is not refundable once a certificat has been generated."""

        cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(days=1)

        assert self.enrollment.refundable()

        GeneratedCertificateFactory.create(
            user=self.user,
            course_id=self.course.id,
            status=CertificateStatuses.downloadable,
            mode='verified'
        )

        assert not self.enrollment.refundable()
        assert not self.enrollment.\
            refundable(user_already_has_certs_for=GeneratedCertificate.course_ids_with_certs_for_user(self.user))

        # Assert that can_refund overrides this and allows refund
        self.enrollment.can_refund = True
        assert self.enrollment.refundable()
        assert self.enrollment.refundable(
            user_already_has_certs_for=GeneratedCertificate.course_ids_with_certs_for_user(self.user)
        )
Ejemplo n.º 13
0
 def test_checkpoint_matches(self, checkpoint, required_badges):
     """
     Make sure the proper badges are awarded at the right checkpoints.
     """
     user = UserFactory()
     courses = [CourseFactory() for _i in range(required_badges)]
     for course in courses:
         GeneratedCertificate(
             user=user,
             course_id=course.location.course_key,
             status=CertificateStatuses.downloadable).save()
     assertions = user.badgeassertion_set.all().order_by('id')
     self.assertEqual(user.badgeassertion_set.all().count(), checkpoint)
     self.assertEqual(assertions[checkpoint - 1].badge_class,
                      self.badge_classes[checkpoint - 1])
Ejemplo n.º 14
0
def _can_generate_certificate_for_status(user, course_key):
    """
    Check if the user's certificate status can handle regular (non-allowlist) certificate generation
    """
    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    if cert is None:
        return True

    if cert.status == CertificateStatuses.downloadable:
        log.info(f'Certificate with status {cert.status} already exists for {user.id} : {course_key}, and is not '
                 f'eligible for generation. Certificate cannot be generated as it is already in a final state.')
        return False

    log.info(f'Certificate with status {cert.status} already exists for {user.id} : {course_key}, and is eligible for '
             f'generation')
    return True
Ejemplo n.º 15
0
def _generate_certificate(user, course_key):
    """
    Generate a certificate for this user, in this course run.
    """
    # Retrieve the existing certificate for the learner if it exists
    existing_certificate = GeneratedCertificate.certificate_for_student(
        user, course_key)

    profile = UserProfile.objects.get(user=user)
    profile_name = profile.name

    course = modulestore().get_course(course_key, depth=0)
    course_grade = CourseGradeFactory().read(user, course)
    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
        user, course_key)

    # Retain the `verify_uuid` from an existing certificate if possible, this will make it possible for the learner to
    # keep the existing URL to their certificate
    if existing_certificate and existing_certificate.verify_uuid:
        uuid = existing_certificate.verify_uuid
    else:
        uuid = uuid4().hex

    cert, created = GeneratedCertificate.objects.update_or_create(
        user=user,
        course_id=course_key,
        defaults={
            'user': user,
            'course_id': course_key,
            'mode': enrollment_mode,
            'name': profile_name,
            'status': CertificateStatuses.downloadable,
            'grade': course_grade.percent,
            'download_url': '',
            'key': '',
            'verify_uuid': uuid,
            'error_reason': ''
        })

    if created:
        created_msg = 'Certificate was created.'
    else:
        created_msg = 'Certificate already existed and was updated.'
    log.info(
        f'Generated certificate with status {cert.status} for {user.id} : {course_key}. {created_msg}'
    )
    return cert
Ejemplo n.º 16
0
def is_certificate_invalidated(student, course_key):
    """Check whether the certificate belonging to the given student (in given course) has been invalidated.

    Arguments:
        student (user object): logged-in user
        course_key (CourseKey): The course identifier.

    Returns:
        Boolean denoting whether the certificate has been invalidated for this learner.
    """
    log.info(f"Checking if student {student.id} has an invalidated certificate in course {course_key}.")

    certificate = GeneratedCertificate.certificate_for_student(student, course_key)
    if certificate:
        return CertificateInvalidation.has_certificate_invalidation(student, course_key)

    return False
def _generate_certificate(user, course_key, status, enrollment_mode,
                          course_grade):
    """
    Generate a certificate for this user, in this course run.

    This method takes things like grade and enrollment mode as parameters because these are used to determine if the
    user is eligible for a certificate, and they're also saved in the cert itself. We want the cert to reflect the
    values that were used when determining if it was eligible for generation.
    """
    # Retrieve the existing certificate for the learner if it exists
    existing_certificate = GeneratedCertificate.certificate_for_student(
        user, course_key)

    preferred_name = get_preferred_certificate_name(user)

    # Retain the `verify_uuid` from an existing certificate if possible, this will make it possible for the learner to
    # keep the existing URL to their certificate
    if existing_certificate and existing_certificate.verify_uuid:
        uuid = existing_certificate.verify_uuid
    else:
        uuid = uuid4().hex

    cert, created = GeneratedCertificate.objects.update_or_create(
        user=user,
        course_id=course_key,
        defaults={
            'user': user,
            'course_id': course_key,
            'mode': enrollment_mode,
            'name': preferred_name,
            'status': status,
            'grade': course_grade,
            'download_url': '',
            'key': '',
            'verify_uuid': uuid,
            'error_reason': ''
        })

    if created:
        created_msg = 'Certificate was created.'
    else:
        created_msg = 'Certificate already existed and was updated.'
    log.info(
        f'Generated certificate with status {cert.status}, mode {cert.mode} and grade {cert.grade} for {user.id} '
        f': {course_key}. {created_msg}')
    return cert
Ejemplo n.º 18
0
def fire_ungenerated_certificate_task(user, course_key, expected_verification_status=None):
    """
    Helper function to fire certificate generation task.
    Auto-generation of certificates is available for following course modes:
        1- VERIFIED
        2- CREDIT_MODE
        3- PROFESSIONAL
        4- NO_ID_PROFESSIONAL_MODE

    Certificate generation task is fired to either generate a certificate
    when there is no generated certificate for user in a particular course or
    update a certificate if it has 'unverified' status.

    Task is fired to attempt an update to a certificate
    with 'unverified' status as this method is called when a user is
    successfully verified, any certificate associated
    with such user can now be verified.

    NOTE: Purpose of restricting other course modes (HONOR and AUDIT) from auto-generation is to reduce
    traffic to workers.
    """

    allowed_enrollment_modes_list = [
        CourseMode.VERIFIED,
        CourseMode.CREDIT_MODE,
        CourseMode.PROFESSIONAL,
        CourseMode.NO_ID_PROFESSIONAL_MODE,
        CourseMode.MASTERS,
    ]
    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
    cert = GeneratedCertificate.certificate_for_student(user, course_key)

    generate_learner_certificate = (
        enrollment_mode in allowed_enrollment_modes_list and (cert is None or cert.status == 'unverified')
    )

    if generate_learner_certificate:
        kwargs = {
            'student': unicode(user.id),
            'course_key': unicode(course_key)
        }
        if expected_verification_status:
            kwargs['expected_verification_status'] = unicode(expected_verification_status)
        generate_certificate.apply_async(countdown=CERTIFICATE_DELAY_SECONDS, kwargs=kwargs)
        return True
Ejemplo n.º 19
0
    def is_entitlement_regainable(self, entitlement):
        """
        Determines from the policy if an entitlement can still be regained by the user, if they choose
        to by leaving and regaining their entitlement within policy.regain_period days from start date of
        the course or their redemption, whichever comes later, and the expiration period hasn't passed yet
        """
        if entitlement.expired_at:
            return False

        if entitlement.enrollment_course_run:
            if GeneratedCertificate.certificate_for_student(
                    entitlement.user_id, entitlement.enrollment_course_run.course_id) is not None:
                return False

            # This is >= because a days_until_expiration 0 means that the expiration day has not fully passed yet
            # and that the entitlement should not be expired as there is still time
            return self.get_days_until_expiration(entitlement) >= 0
        return False
Ejemplo n.º 20
0
def is_certificate_invalid(student, course_key):
    """Check that whether the student in the course has been invalidated
    for receiving certificates.

    Arguments:
        student (user object): logged-in user
        course_key (CourseKey): The course identifier.

    Returns:
        Boolean denoting whether the student in the course is invalidated
        to receive certificates
    """
    is_invalid = False
    certificate = GeneratedCertificate.certificate_for_student(student, course_key)
    if certificate is not None:
        is_invalid = CertificateInvalidation.has_certificate_invalidation(student, course_key)

    return is_invalid
Ejemplo n.º 21
0
def is_certificate_invalid(student, course_key):
    """Check that whether the student in the course has been invalidated
    for receiving certificates.

    Arguments:
        student (user object): logged-in user
        course_key (CourseKey): The course identifier.

    Returns:
        Boolean denoting whether the student in the course is invalidated
        to receive certificates
    """
    is_invalid = False
    certificate = GeneratedCertificate.certificate_for_student(student, course_key)
    if certificate is not None:
        is_invalid = CertificateInvalidation.has_certificate_invalidation(student, course_key)

    return is_invalid
def listen_for_passing_grade(sender, user, course_id, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a signal indicating that the user has passed a course run.

    If needed, generate a certificate task.
    """
    if not auto_certificate_generation_enabled():
        return

    cert = GeneratedCertificate.certificate_for_student(user, course_id)
    if cert is not None and CertificateStatuses.is_passing_status(cert.status):
        log.info(
            f'The cert status is already passing for user {user.id} : {course_id}. Passing grade signal will be '
            f'ignored.')
        return
    log.info(
        f'Attempt will be made to generate a course certificate for {user.id} : {course_id} as a passing grade '
        f'was received.')
    return generate_certificate_task(user, course_id)
Ejemplo n.º 23
0
def _listen_for_failing_grade(sender, user, course_id, grade, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a signal indicating that the user has failed a course run.

    If needed, mark the certificate as notpassing.
    """
    if is_on_certificate_allowlist(user, course_id):
        log.info(
            f'User {user.id} is on the allowlist for {course_id}. The failing grade will not affect the '
            f'certificate.')
        return

    cert = GeneratedCertificate.certificate_for_student(user, course_id)
    if cert is not None:
        if CertificateStatuses.is_passing_status(cert.status):
            cert.mark_notpassing(grade.percent)
            log.info(
                'Certificate marked not passing for {user} : {course} via failing grade: {grade}'
                .format(user=user.id, course=course_id, grade=grade))
Ejemplo n.º 24
0
 def test_group_matches(self):
     """
     Make sure the proper badges are awarded when groups are completed.
     """
     user = UserFactory()
     items = list(self.config_map.items())
     for badge_class, course_keys in items:
         for i, key in enumerate(course_keys):
             GeneratedCertificate(
                 user=user, course_id=key, status=CertificateStatuses.downloadable
             ).save()
             # We don't award badges until all three are set.
             if i + 1 == len(course_keys):
                 self.assertTrue(badge_class.get_for_user(user))
             else:
                 self.assertFalse(badge_class.get_for_user(user))
     # pylint: disable=no-member
     classes = [badge.badge_class.id for badge in user.badgeassertion_set.all()]
     source_classes = [badge.id for badge in self.badge_classes]
     self.assertEqual(classes, source_classes)
Ejemplo n.º 25
0
def _listen_for_failing_grade(sender, user, course_id, grade, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a learner failing a course, mark the cert as notpassing
    if it is currently passing,
    downstream signal from COURSE_GRADE_CHANGED
    """
    if is_using_certificate_allowlist_and_is_on_allowlist(user, course_id):
        log.info('{course_id} is using allowlist certificates, and the user {user_id} is on its allowlist. The '
                 'failing grade will not affect the certificate.'.format(course_id=course_id, user_id=user.id))
        return

    cert = GeneratedCertificate.certificate_for_student(user, course_id)
    if cert is not None:
        if CertificateStatuses.is_passing_status(cert.status):
            cert.mark_notpassing(grade.percent)
            log.info('Certificate marked not passing for {user} : {course} via failing grade: {grade}'.format(
                user=user.id,
                course=course_id,
                grade=grade
            ))
Ejemplo n.º 26
0
def _can_generate_certificate_for_status(user, course_key, enrollment_mode):
    """
    Check if the user's certificate status can handle regular (non-allowlist) certificate generation
    """
    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    if cert is None:
        return True

    if cert.status == CertificateStatuses.downloadable:
        if not _is_mode_now_eligible(enrollment_mode, cert):
            log.info(
                f'Certificate with status {cert.status} already exists for {user.id} : {course_key}, and is not '
                f'eligible for generation. Certificate cannot be generated as it is already in a final state. The '
                f'current enrollment mode is {enrollment_mode} and the existing cert mode is {cert.mode}'
            )
            return False

    log.info(
        f'Certificate with status {cert.status} already exists for {user.id} : {course_key}, and is eligible for '
        f'generation. The current enrollment mode is {enrollment_mode} and the existing cert mode is {cert.mode}'
    )
    return True
Ejemplo n.º 27
0
    def test_ineligible_cert_whitelisted(self):
        """Test that audit mode students can receive a certificate if they are whitelisted."""
        # Enroll as audit
        CourseEnrollmentFactory(user=self.user_2,
                                course_id=self.course.id,
                                is_active=True,
                                mode='audit')
        # Whitelist student
        CertificateWhitelistFactory(course_id=self.course.id, user=self.user_2)

        # Generate certs
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)

        # Assert cert generated correctly
        self.assertTrue(mock_send.called)
        certificate = GeneratedCertificate.certificate_for_student(
            self.user_2, self.course.id)
        self.assertIsNotNone(certificate)
        self.assertEqual(certificate.mode, 'audit')
Ejemplo n.º 28
0
    def test_ineligible_cert_whitelisted(self):
        """Test that audit mode students can receive a certificate if they are whitelisted."""
        # Enroll as audit
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode='audit'
        )
        # Whitelist student
        CertificateWhitelistFactory(course_id=self.course.id, user=self.user_2)

        # Generate certs
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)

        # Assert cert generated correctly
        self.assertTrue(mock_send.called)
        certificate = GeneratedCertificate.certificate_for_student(self.user_2, self.course.id)
        self.assertIsNotNone(certificate)
        self.assertEqual(certificate.mode, 'audit')
Ejemplo n.º 29
0
def _listen_for_failing_grade(sender, user, course_id, grade, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a signal indicating that the user has failed a course run.

    If needed, mark the certificate as notpassing.
    """
    if is_on_certificate_allowlist(user, course_id):
        log.info(
            f'User {user.id} is on the allowlist for {course_id}. The failing grade will not affect the '
            f'certificate.')
        return

    cert = GeneratedCertificate.certificate_for_student(user, course_id)
    if cert is not None:
        if CertificateStatuses.is_passing_status(cert.status):
            enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
                user, course_id)
            cert.mark_notpassing(mode=enrollment_mode,
                                 grade=grade.percent,
                                 source='notpassing_signal')
            log.info(
                f'Certificate marked not passing for {user.id} : {course_id} via failing grade'
            )
Ejemplo n.º 30
0
def _set_v2_cert_status(user, course_key):
    """
    Determine the V2 certificate status for this user, in this course run.

    This is used when a downloadable cert cannot be generated, but we want to provide more info about why it cannot
    be generated.
    """
    if not _can_set_v2_cert_status(user, course_key):
        return None

    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    status = _get_cert_status_common(user, course_key, cert)
    if status is not None:
        return status

    if IDVerificationService.user_is_verified(user) and not _has_passing_grade(
            user, course_key) and cert is not None:
        if cert.status != CertificateStatuses.notpassing:
            course_grade = _get_course_grade(user, course_key)
            cert.mark_notpassing(course_grade.percent,
                                 source='certificate_generation')
        return CertificateStatuses.notpassing

    return None
Ejemplo n.º 31
0
def listen_for_passing_grade(sender, user, course_id, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a signal indicating that the user has passed a course run.

    If needed, generate a certificate task.
    """
    if not auto_certificate_generation_enabled():
        return

    if can_generate_certificate_task(user, course_id):
        cert = GeneratedCertificate.certificate_for_student(user, course_id)
        if cert is not None and CertificateStatuses.is_passing_status(cert.status):
            log.info(f'{course_id} is using V2 certificates, and the cert status is already passing for user '
                     f'{user.id}. Passing grade signal will be ignored.')
            return
        log.info(f'{course_id} is using V2 certificates. Attempt will be made to generate a V2 certificate for '
                 f'{user.id} as a passing grade was received.')
        return generate_certificate_task(user, course_id)

    if _fire_ungenerated_certificate_task(user, course_id):
        log.info('Certificate generation task initiated for {user} : {course} via passing grade'.format(
            user=user.id,
            course=course_id
        ))
Ejemplo n.º 32
0
def _set_v2_cert_status(user, course_key):
    """
    Determine the V2 certificate status for this user, in this course run.

    This is used when a downloadable cert cannot be generated, but we want to provide more info about why it cannot
    be generated.
    """
    if not _can_set_v2_cert_status(user, course_key):
        return None

    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    status = _get_cert_status_common(user, course_key, cert)
    if status is not None:
        return status

    course_grade = _get_course_grade(user, course_key)
    if not course_grade.passed:
        if cert is None:
            cert = GeneratedCertificate.objects.create(user=user, course_id=course_key)
        if cert.status != CertificateStatuses.notpassing:
            cert.mark_notpassing(course_grade.percent)
        return CertificateStatuses.notpassing

    return None
Ejemplo n.º 33
0
def _section_certificates(course):
    """Section information for the certificates panel.

    The certificates panel allows global staff to generate
    example certificates and enable self-generated certificates
    for a course.

    Arguments:
        course (Course)

    Returns:
        dict

    """
    example_cert_status = None
    html_cert_enabled = certs_api.has_html_certificates_enabled(course)
    if html_cert_enabled:
        can_enable_for_course = True
    else:
        example_cert_status = certs_api.example_certificates_status(course.id)

        # Allow the user to enable self-generated certificates for students
        # *only* once a set of example certificates has been successfully generated.
        # If certificates have been misconfigured for the course (for example, if
        # the PDF template hasn't been uploaded yet), then we don't want
        # to turn on self-generated certificates for students!
        can_enable_for_course = (
            example_cert_status is not None and
            all(
                cert_status['status'] == 'success'
                for cert_status in example_cert_status
            )
        )
    instructor_generation_enabled = settings.FEATURES.get('CERTIFICATES_INSTRUCTOR_GENERATION', False)
    certificate_statuses_with_count = {
        certificate['status']: certificate['count']
        for certificate in GeneratedCertificate.get_unique_statuses(course_key=course.id)
    }

    return {
        'section_key': 'certificates',
        'section_display_name': _('Certificates'),
        'example_certificate_status': example_cert_status,
        'can_enable_for_course': can_enable_for_course,
        'enabled_for_course': certs_api.cert_generation_enabled(course.id),
        'is_self_paced': course.self_paced,
        'instructor_generation_enabled': instructor_generation_enabled,
        'html_cert_enabled': html_cert_enabled,
        'active_certificate': certs_api.get_active_web_certificate(course),
        'certificate_statuses_with_count': certificate_statuses_with_count,
        'status': CertificateStatuses,
        'certificate_generation_history':
            CertificateGenerationHistory.objects.filter(course_id=course.id).order_by("-created"),
        'urls': {
            'generate_example_certificates': reverse(
                'generate_example_certificates',
                kwargs={'course_id': course.id}
            ),
            'enable_certificate_generation': reverse(
                'enable_certificate_generation',
                kwargs={'course_id': course.id}
            ),
            'start_certificate_generation': reverse(
                'start_certificate_generation',
                kwargs={'course_id': course.id}
            ),
            'start_certificate_regeneration': reverse(
                'start_certificate_regeneration',
                kwargs={'course_id': course.id}
            ),
            'list_instructor_tasks_url': reverse(
                'list_instructor_tasks',
                kwargs={'course_id': course.id}
            ),
        }
    }
Ejemplo n.º 34
0
def get_extra_course_about_context(request, course):
    """
    Get all the extra context for the course_about page

    Arguments:
        request (Request): Request object
        course (CourseOverview): Course Overview object to add data to the context

    Returns:
        dict: Returns an empty dict if it is the testing environment otherwise returns a dict with added context
    """
    if is_testing_environment():
        return {}

    user = request.user
    course_language_names = []
    enrolled_course_group_course = None
    enroll_popup_message = cannot_enroll_message = ''

    multilingual_course = MultilingualCourse.objects.all(
    ).multilingual_course_with_course_id(course.id)
    if multilingual_course:
        course_group_courses = multilingual_course.multilingual_course_group.multilingual_courses
        course_language_codes = course_group_courses.open_multilingual_courses(
        ).language_codes_with_course_ids()
        course_language_names = get_language_names_from_codes(
            course_language_codes)

        enrolled_course_group_course = course_group_courses.all(
        ).enrolled_course(user)
        if enrolled_course_group_course:
            course_display_name = enrolled_course_group_course.course.display_name_with_default

            enroll_popup_message = Text(
                _('Warning: If you wish to change the language of this course, your progress in '
                  'the following course(s) will be erased.{line_break}{course_name}'
                  )).format(course_name=course_display_name,
                            line_break=HTML('<br>'))

    course_enrollment_count = CourseEnrollment.objects.enrollment_counts(
        course.id).get('total')

    course_requirements = course_grade = certificate = None
    if user.is_authenticated:
        course_requirements = get_pre_requisite_courses_not_completed(
            user, [course.id])
        course_grade = CourseGradeFactory().read(user, course_key=course.id)
        certificate = GeneratedCertificate.certificate_for_student(
            user, course.id)

    has_generated_cert_for_any_other_course_group_course = False
    if enrolled_course_group_course and enrolled_course_group_course.course != course:
        has_generated_cert_for_any_other_course_group_course = GeneratedCertificate.objects.filter(
            course_id=enrolled_course_group_course.course.id,
            user=user).exists()

        if has_generated_cert_for_any_other_course_group_course:
            cannot_enroll_message = _(
                'You cannot enroll in this course version as you have already earned a '
                'certificate for another version of this course!')

    context = {
        'course_languages':
        course_language_names,
        'course_requirements':
        course_requirements,
        'total_enrollments':
        course_enrollment_count,
        'self_paced':
        course.self_paced,
        'effort':
        course.effort,
        'is_course_passed':
        course_grade and getattr(course_grade, 'passed', False),
        'has_certificate':
        certificate,
        'has_user_enrolled_in_course_group_courses':
        bool(enrolled_course_group_course),
        'has_generated_cert_for_any_course_group_course':
        has_generated_cert_for_any_other_course_group_course,
        'enroll_popup_message':
        enroll_popup_message,
        'cannot_enroll_message':
        cannot_enroll_message,
    }

    return context
Ejemplo n.º 35
0
def _section_certificates(course):
    """Section information for the certificates panel.

    The certificates panel allows global staff to generate
    example certificates and enable self-generated certificates
    for a course.

    Arguments:
        course (Course)

    Returns:
        dict

    """
    example_cert_status = None
    html_cert_enabled = certs_api.has_html_certificates_enabled(course)
    if html_cert_enabled:
        can_enable_for_course = True
    else:
        example_cert_status = certs_api.example_certificates_status(course.id)

        # Allow the user to enable self-generated certificates for students
        # *only* once a set of example certificates has been successfully generated.
        # If certificates have been misconfigured for the course (for example, if
        # the PDF template hasn't been uploaded yet), then we don't want
        # to turn on self-generated certificates for students!
        can_enable_for_course = (
            example_cert_status is not None and
            all(
                cert_status['status'] == 'success'
                for cert_status in example_cert_status
            )
        )
    instructor_generation_enabled = settings.FEATURES.get('CERTIFICATES_INSTRUCTOR_GENERATION', False)
    certificate_statuses_with_count = {
        certificate['status']: certificate['count']
        for certificate in GeneratedCertificate.get_unique_statuses(course_key=course.id)
    }

    return {
        'section_key': 'certificates',
        'section_display_name': _('Certificates'),
        'example_certificate_status': example_cert_status,
        'can_enable_for_course': can_enable_for_course,
        'enabled_for_course': certs_api.cert_generation_enabled(course.id),
        'is_self_paced': course.self_paced,
        'instructor_generation_enabled': instructor_generation_enabled,
        'html_cert_enabled': html_cert_enabled,
        'active_certificate': certs_api.get_active_web_certificate(course),
        'certificate_statuses_with_count': certificate_statuses_with_count,
        'status': CertificateStatuses,
        'certificate_generation_history':
            CertificateGenerationHistory.objects.filter(course_id=course.id).order_by("-created"),
        'urls': {
            'generate_example_certificates': reverse(
                'generate_example_certificates',
                kwargs={'course_id': course.id}
            ),
            'enable_certificate_generation': reverse(
                'enable_certificate_generation',
                kwargs={'course_id': course.id}
            ),
            'start_certificate_generation': reverse(
                'start_certificate_generation',
                kwargs={'course_id': course.id}
            ),
            'start_certificate_regeneration': reverse(
                'start_certificate_regeneration',
                kwargs={'course_id': course.id}
            ),
            'list_instructor_tasks_url': reverse(
                'list_instructor_tasks',
                kwargs={'course_id': course.id}
            ),
        }
    }