def _listen_for_id_verification_status_changed(sender, user, **kwargs): # pylint: disable=unused-argument """ Listen for a signal indicating that the user's id verification status has changed. If needed, generate a certificate task. """ 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 can_generate_certificate_task(user, enrollment.course_id): log.info( f'{enrollment.course_id} is using V2 certificates. Attempt will be made to generate a V2 ' f'certificate for {user.id}. Id verification status is {expected_verification_status}' ) generate_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 = ( 'Certificate generation task initiated for {user} : {course} via track change ' + '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): if not options.get('user'): raise CommandError('You must specify a list of users') course_key = options.get('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_certificate_task for {user} : {course}'. format(user=user.id, course=course_key)) generate_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 can_generate_certificate_task(user, course_key): log.info(f'{course_key} is using V2 certificates. Attempt will be made to generate a V2 certificate for ' f'{user.id} since the enrollment mode is now {mode}.') generate_certificate_task(user, course_key)
def _listen_for_id_verification_status_changed(sender, user, **kwargs): # pylint: disable=unused-argument """ Listen for a signal indicating that the user's id verification status has changed. """ if not auto_certificate_generation_enabled(): return user_enrollments = CourseEnrollment.enrollments_for_user(user=user) expected_verification_status = IDVerificationService.user_status(user) expected_verification_status = expected_verification_status['status'] for enrollment in user_enrollments: log.info( f'Attempt will be made to generate a course certificate for {user.id} : {enrollment.course_id}. Id ' f'verification status is {expected_verification_status}') generate_certificate_task(user, enrollment.course_id)
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.') try: return generate_certificate_task(user, course_id) except CertificateGenerationNotAllowed as e: log.exception( "Certificate generation not allowed for user %s in course %s", str(user), course_id, ) return False
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))
def test_handle_valid(self): """ Test handling of a valid user/course run combo. """ assert _can_generate_v2_certificate(self.user, self.course_run_key) assert can_generate_certificate_task(self.user, self.course_run_key) assert generate_certificate_task(self.user, self.course_run_key)
def _regenerate_certs(certs, batch_size, sleep_seconds, count): """ Triggers generate certificate task for a given set of certificates """ for cert in certs: user = User.objects.get(id=cert.user_id) generate_certificate_task(user, cert.course_id, generation_mode='batch', delay_seconds=0) count += 1 if count % batch_size == 0: log.info( f'Regenerated {count} unverified certificates. Sleeping for {sleep_seconds} seconds.' ) time.sleep(sleep_seconds) return count
def test_handle_invalid(self): """ Test handling of an invalid user/course run combo """ assert not _can_generate_v2_certificate(self.user, self.course_run_key) assert not can_generate_certificate_task(self.user, self.course_run_key) assert not generate_certificate_task(self.user, self.course_run_key) assert not generate_regular_certificate_task(self.user, self.course_run_key)
def test_certificate_creation_filter_prevent_generation(self): """ Test prevent the user's certificate generation through a pipeline step. Expected result: - CertificateCreationRequested is triggered and executes TestStopCertificateGenerationStep. - The certificate is not generated. """ with self.assertRaises(CertificateGenerationNotAllowed): generate_certificate_task( self.user, self.course_run.id, generation_mode=CourseMode.HONOR, ) self.assertFalse( GeneratedCertificate.objects.filter( user=self.user, course_id=self.course_run.id, mode=CourseMode.HONOR, ) )
def _regenerate_certs(certs, batch_size, sleep_seconds, count, check_integrity_signature_flag): """ Triggers generate certificate task for a given set of certificates """ for cert in certs: # not ideal to replicate this check, but we have to in the case that we are regenerating certs for all courses. if check_integrity_signature_flag and not is_integrity_signature_enabled(cert.course_id): log.warning( f'Skipping regenerating cert_id={cert.id} because {cert.course_id} does not have honor code enabled' ) else: user = User.objects.get(id=cert.user_id) generate_certificate_task(user, cert.course_id, generation_mode='batch', delay_seconds=0) count += 1 if count % batch_size == 0: log.info(f'Regenerated {count} unverified certificates. Sleeping for {sleep_seconds} seconds.') time.sleep(sleep_seconds) return count
def test_handle_valid(self): """ Test handling of a valid user/course run combo. """ assert _can_generate_regular_certificate(self.user, self.course_run_key, self.enrollment_mode, self.grade) assert generate_certificate_task(self.user, self.course_run_key)
def test_handle_invalid(self): """ Test handling of an invalid user/course run combo """ other_user = UserFactory() assert not _can_generate_v2_certificate(other_user, self.course_run_key) assert not generate_certificate_task(other_user, self.course_run_key) assert not generate_regular_certificate_task(other_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 test_handle_valid(self): """ Test handling of a valid user/course run combo. Note: these assertions are placeholders for now. They will be updated as the implementation is added. """ assert not _can_generate_v2_certificate(self.user, self.course_run_key) assert can_generate_certificate_task(self.user, self.course_run_key) assert not generate_certificate_task(self.user, self.course_run_key) assert not generate_regular_certificate_task(self.user, self.course_run_key)
def handle(self, *args, **options): # database args will override cmd line args if options['args_from_database']: options = self.get_args_from_database() if not options.get('user'): raise CommandError('You must specify a list of users') course_key = options.get('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(f'User {user_id} could not be found') if user is not None: log.info( 'Calling generate_certificate_task for {user} : {course}'. format(user=user.id, course=course_key)) try: generate_certificate_task(user, course_key) except CertificateGenerationNotAllowed as e: log.exception( "Certificate generation not allowed for user %s in course %s", user.id, course_key, )
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 not auto_certificate_generation_enabled(): return if can_generate_certificate_task(user, course_id): 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 ))
def test_certificate_generation_without_filter_configuration(self): """ Test usual certificate process, without filter's intervention. Expected result: - CertificateCreationRequested does not have any effect on the certificate generation process. - The certificate generation process ends successfully. """ cert_gen_task_created = generate_certificate_task( self.user, self.course_run.id, generation_mode=CourseMode.HONOR, ) certificate = GeneratedCertificate.objects.get( user=self.user, course_id=self.course_run.id, ) self.assertTrue(cert_gen_task_created) self.assertEqual(CourseMode.HONOR, certificate.mode)
def test_certificate_creation_filter_executed(self): """ Test whether the student certificate filter is triggered before the user's certificate creation process. Expected result: - CertificateCreationRequested is triggered and executes TestCertificatePipelineStep. - The certificate generates with no-id-professional mode instead of honor mode. """ cert_gen_task_created = generate_certificate_task( self.user, self.course_run.id, generation_mode=CourseMode.HONOR, ) certificate = GeneratedCertificate.objects.get( user=self.user, course_id=self.course_run.id, ) self.assertTrue(cert_gen_task_created) self.assertEqual(CourseMode.NO_ID_PROFESSIONAL_MODE, certificate.mode)
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): log.info( f'Attempt will be made to generate a course certificate for {user.id} : {course_key} since the ' f'enrollment mode is now {mode}.') try: return generate_certificate_task(user, course_key) except CertificateGenerationNotAllowed as e: log.exception( "Certificate generation not allowed for user %s in course %s", str(user), course_key, ) return False
def test_handle_valid_general_methods(self): """ Test handling of a valid user/course run combo for the general (non-allowlist) generation methods """ assert can_generate_certificate_task(self.user, self.course_run_key) assert generate_certificate_task(self.user, self.course_run_key)
def test_handle_no_grade(self): """ Test handling when the grade is none """ with mock.patch(GET_GRADE_METHOD, return_value=None): assert generate_certificate_task(self.user, self.course_run_key)