def _listen_for_id_verification_status_changed(sender, user, **kwargs): # pylint: disable=unused-argument """ Catches a track change signal, determines user status, calls _fire_ungenerated_certificate_task for passing grades """ if not auto_certificate_generation_enabled(): return user_enrollments = CourseEnrollment.enrollments_for_user(user=user) grade_factory = CourseGradeFactory() expected_verification_status = IDVerificationService.user_status(user) expected_verification_status = expected_verification_status['status'] for enrollment in user_enrollments: if is_using_certificate_allowlist_and_is_on_allowlist( user, enrollment.course_id): log.info( f'{enrollment.course_id} is using allowlist certificates, and the user {user.id} is on its ' f'allowlist. Attempt will be made to generate an allowlist certificate. Id verification status ' f'is {expected_verification_status}') generate_allowlist_certificate_task(user, enrollment.course_id) elif grade_factory.read(user=user, course=enrollment.course_overview).passed: if _fire_ungenerated_certificate_task( user, enrollment.course_id, expected_verification_status): message = ( u'Certificate generation task initiated for {user} : {course} via track change ' + u'with verification status of {status}') log.info( message.format(user=user.id, course=enrollment.course_id, status=expected_verification_status))
def handle(self, *args, **options): # database args will override cmd line args if options['args_from_database']: options = self.get_args_from_database() # Since we're optionally using database args we can't simply make users required in the arguments if not options["user"]: raise CommandError("You must specify a list of users") course_key = options['course_key'] if not course_key: raise CommandError("You must specify a course-key") # Parse the serialized course key into a CourseKey try: course_key = CourseKey.from_string(course_key) except InvalidKeyError as e: raise CommandError("You must specify a valid course-key") from e # Loop over each user, and ask that a cert be generated for them users_str = options['user'] for user_id in users_str: user = None try: user = User.objects.get(id=user_id) except User.DoesNotExist: log.warning('User {user} could not be found'.format(user=user_id)) if user is not None: log.info( 'Calling generate_allowlist_certificate_task for {user} : {course}'.format( user=user.id, course=course_key )) generate_allowlist_certificate_task(user, course_key)
def _listen_for_enrollment_mode_change(sender, user, course_key, mode, **kwargs): # pylint: disable=unused-argument """ Listen for the signal indicating that a user's enrollment mode has changed. If possible, grant the user a course certificate. Note that we intentionally do not revoke certificates here, even if the user has moved to the audit track. """ if modes_api.is_eligible_for_certificate(mode): if is_using_certificate_allowlist_and_is_on_allowlist( user, course_key): log.info( f'{course_key} is using allowlist certificates, and the user {user.id} is on its allowlist. ' f'Attempt will be made to generate an allowlist certificate since the enrollment mode is now ' f'{mode}.') generate_allowlist_certificate_task(user, course_key)
def test_handle_valid(self): """ Test handling of a valid user/course run combo """ assert _can_generate_allowlist_certificate(self.user, self.course_run_key) assert generate_allowlist_certificate_task(self.user, self.course_run_key)
def test_handle_invalid(self): """ Test handling of an invalid user/course run combo """ self.assertFalse( can_generate_allowlist_certificate(self.user, self.course_run_key)) self.assertFalse( generate_allowlist_certificate_task(self.user, self.course_run_key))
def test_handle_invalid(self): """ Test handling of an invalid user/course run combo """ u = UserFactory() assert not _can_generate_allowlist_certificate(u, self.course_run_key) assert not generate_allowlist_certificate_task(u, self.course_run_key) assert not generate_certificate_task(u, self.course_run_key) assert _set_allowlist_cert_status(u, self.course_run_key) is None
def handle(self, *args, **options): # Parse the serialized course key into a CourseKey course_key = options['course_key'] if not course_key: raise CommandError("You must specify a course-key") try: course_key = CourseKey.from_string(course_key) except InvalidKeyError as e: raise CommandError("You must specify a valid course-key") from e # Loop over each user, and ask that a cert be generated for them users_str = options['user'] for user_identifier in users_str: user = _get_user_from_identifier(user_identifier) if user is not None: log.info( 'Calling generate_allowlist_certificate_task for {user} : {course}' .format(user=user.id, course=course_key)) generate_allowlist_certificate_task(user, course_key)
def _listen_for_certificate_allowlist_append(sender, instance, **kwargs): # pylint: disable=unused-argument """ Listen for a user being added to or modified on the allowlist """ if not auto_certificate_generation_enabled(): return if is_on_certificate_allowlist(instance.user, instance.course_id): log.info( f'User {instance.user.id} is now on the allowlist for course {instance.course_id}. Attempt will be ' f'made to generate an allowlist certificate.') return generate_allowlist_certificate_task(instance.user, instance.course_id)
def _listen_for_certificate_whitelist_append(sender, instance, **kwargs): # pylint: disable=unused-argument """ Listen for a user being added to or modified on the whitelist (allowlist) """ if not auto_certificate_generation_enabled(): return if is_using_certificate_allowlist_and_is_on_allowlist(instance.user, instance.course_id): log.info(f'{instance.course_id} is using allowlist certificates, and the user {instance.user.id} is now on ' f'its allowlist. Attempt will be made to generate an allowlist certificate.') return generate_allowlist_certificate_task(instance.user, instance.course_id) if _fire_ungenerated_certificate_task(instance.user, instance.course_id): log.info('Certificate generation task initiated for {user} : {course} via whitelist'.format( user=instance.user.id, course=instance.course_id ))
def test_generate_allowlist_certificate_fail(self): """ Test stop certificate process by raising a filter exception when the user is in the allow list. Expected result: - CertificateCreationRequested is triggered and executes TestStopCertificateGenerationStep. - The certificate is not generated. """ CertificateAllowlistFactory.create(course_id=self.course_run.id, user=self.user) certificate_generated = generate_allowlist_certificate_task(self.user, self.course_run.id) self.assertFalse(certificate_generated) self.assertFalse( GeneratedCertificate.objects.filter( user=self.user, course_id=self.course_run.id, mode=CourseMode.HONOR, ) )
def listen_for_passing_grade(sender, user, course_id, **kwargs): # pylint: disable=unused-argument """ Listen for a learner passing a course, send cert generation task, downstream signal from COURSE_GRADE_CHANGED """ if is_using_certificate_allowlist_and_is_on_allowlist(user, course_id): log.info( f'{course_id} is using allowlist certificates, and the user {user.id} is on its allowlist. Attempt ' f'will be made to generate an allowlist certificate as a passing grade was received.' ) return generate_allowlist_certificate_task(user, course_id) if not auto_certificate_generation_enabled(): return if _fire_ungenerated_certificate_task(user, course_id): log.info( u'Certificate generation task initiated for {user} : {course} via passing grade' .format(user=user.id, course=course_id))
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)) if is_using_certificate_allowlist_and_is_on_allowlist(user, course_key): log.info( '{course} is using allowlist certificates, and the user {user} is on its allowlist. Attempt will be ' 'made to generate an allowlist certificate.'.format( course=course_key, user=user.id)) generate_allowlist_certificate_task(user, course_key) return True log.info( '{course} is not using allowlist certificates (or user {user} is not on its allowlist). The normal ' 'generation logic will be followed.'.format(course=course_key, user=user.id)) allowed_enrollment_modes_list = [ CourseMode.VERIFIED, CourseMode.CREDIT_MODE, CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE, CourseMode.MASTERS, CourseMode.EXECUTIVE_EDUCATION, ] 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)) return False