def _is_mode_now_eligible(enrollment_mode, cert): """ Check if the current enrollment mode is now eligible, while the enrollment mode on the cert is NOT eligible """ if modes_api.is_eligible_for_certificate( enrollment_mode) and not modes_api.is_eligible_for_certificate( cert.mode): return True return False
def _can_generate_certificate_common(user, course_key, enrollment_mode): """ Check if a course certificate can be generated (created if it doesn't already exist, or updated if it does exist) for this user, in this course run. This method contains checks that are common to both allowlist and regular course certificates. """ if CertificateInvalidation.has_certificate_invalidation(user, course_key): # The invalidation list prevents certificate generation log.info( f'{user.id} : {course_key} is on the certificate invalidation list. Certificate cannot be generated.' ) return False if enrollment_mode is None: log.info( f'{user.id} : {course_key} does not have an enrollment. Certificate cannot be generated.' ) return False is_eligible_for_cert = modes_api.is_eligible_for_certificate( enrollment_mode) if not is_eligible_for_cert: log.info( f'{user.id} : {course_key} has an enrollment mode of {enrollment_mode}, which is not eligible for a ' f'certificate. Certificate cannot be generated.') return False # If the IDV check fails we then check if the course-run requires ID verification. Honor and Professional-No-ID # modes do not require IDV for certificate generation. if _required_verification_missing(user): if enrollment_mode not in CourseMode.NON_VERIFIED_MODES: log.info( f'{user.id} does not have a verified id. Certificate cannot be generated for {course_key}.' ) return False log.info( f'{user.id} : {course_key} is eligible for a certificate without requiring a verified ID. ' 'Skipping results of the ID verification check.') if not _can_generate_certificate_for_status(user, course_key, enrollment_mode): return False course_overview = get_course_overview_or_none(course_key) if not course_overview: log.info( f'{course_key} does not a course overview. Certificate cannot be generated for {user.id}.' ) return False if not has_html_certificates_enabled(course_overview): log.info( f'{course_key} does not have HTML certificates enabled. Certificate cannot be generated for ' f'{user.id}.') return False return True
def test_add_cert_with_honor_certificates(self, mode): """Test certificates generations for honor and audit modes.""" template_name = 'certificate-template-{id.org}-{id.course}.pdf'.format( id=self.course.id) mock_send = self.add_cert_to_queue(mode) if modes_api.is_eligible_for_certificate(mode): self.assert_certificate_generated(mock_send, mode, template_name) else: self.assert_ineligible_certificate_generated(mock_send, mode)
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( f'{course_key} is not using the certificate allowlist. Allowlist certificate cannot be generated' f'for {user.id}.') return False if not _is_on_certificate_allowlist(user, course_key): log.info( f'{user.id} : {course_key} is not on the certificate allowlist. Allowlist certificate cannot be ' f'generated.') return False if not auto_certificate_generation_enabled(): # Automatic certificate generation is globally disabled log.info( f'Automatic certificate generation is globally disabled. Allowlist certificate cannot be generated' f'for {user.id} : {course_key}.') return False if CertificateInvalidation.has_certificate_invalidation(user, course_key): # The invalidation list overrides the allowlist log.info( f'{user.id} : {course_key} is on the certificate invalidation list. Allowlist certificate cannot be ' f'generated.') return False enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user( user, course_key) if enrollment_mode is None: log.info( f'{user.id} : {course_key} does not have an enrollment. Allowlist certificate cannot be generated.' ) return False if not modes_api.is_eligible_for_certificate(enrollment_mode): log.info( f'{user.id} : {course_key} has an enrollment mode of {enrollment_mode}, which is not eligible for an ' f'allowlist certificate. Certificate cannot be generated.') return False if not IDVerificationService.user_is_verified(user): log.info( f'{user.id} does not have a verified id. Allowlist certificate cannot be generated for {course_key}.' ) return False log.info(f'{user.id} : {course_key} is on the certificate allowlist') return _can_generate_allowlist_certificate_for_status(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 _can_generate_certificate_common(user, course_key): """ Check if a course certificate can be generated (created if it doesn't already exist, or updated if it does exist) for this user, in this course run. This method contains checks that are common to both allowlist and V2 regular course certificates. """ if CertificateInvalidation.has_certificate_invalidation(user, course_key): # The invalidation list prevents certificate generation log.info( f'{user.id} : {course_key} is on the certificate invalidation list. Certificate cannot be generated.' ) return False enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user( user, course_key) if enrollment_mode is None: log.info( f'{user.id} : {course_key} does not have an enrollment. Certificate cannot be generated.' ) return False if not modes_api.is_eligible_for_certificate(enrollment_mode): log.info( f'{user.id} : {course_key} has an enrollment mode of {enrollment_mode}, which is not eligible for a ' f'certificate. Certificate cannot be generated.') return False if not IDVerificationService.user_is_verified(user): log.info( f'{user.id} does not have a verified id. Certificate cannot be generated for {course_key}.' ) return False if not _can_generate_certificate_for_status(user, course_key): return False course_overview = get_course_overview_or_none(course_key) if not course_overview: log.info( f'{course_key} does not a course overview. Certificate cannot be generated for {user.id}.' ) return False if not has_html_certificates_enabled(course_overview): log.info( f'{course_key} does not have HTML certificates enabled. Certificate cannot be generated for ' f'{user.id}.') return False return True
def _can_set_cert_status_common(user, course_key): """ Determine whether we can set a custom (non-downloadable) cert status """ if _is_cert_downloadable(user, course_key): return False enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key) if enrollment_mode is None: return False if not modes_api.is_eligible_for_certificate(enrollment_mode): return False course_overview = get_course_overview(course_key) if not has_html_certificates_enabled_from_course_overview(course_overview): return False return True
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 add_cert(self, student, course_id, forced_grade=None, template_file=None, generate_pdf=True): """ Request a new certificate for a student. Arguments: student - User.object course_id - courseenrollment.course_id (CourseKey) forced_grade - a string indicating a grade parameter to pass with the certificate request. If this is given, grading will be skipped. generate_pdf - Boolean should a message be sent in queue to generate certificate PDF Will change the certificate status to 'generating' or `downloadable` in case of web view certificates. The course must not be a CCX. Certificate must be in the 'unavailable', 'error', 'deleted' or 'generating' state. If a student has a passing grade or is in the whitelist table for the course a request will be made for a new cert. If a student does not have a passing grade the status will change to status.notpassing Returns the newly created certificate instance """ if hasattr(course_id, 'ccx'): LOGGER.warning( ("Cannot create certificate generation task for user %s " "in the course '%s'; " "certificates are not allowed for CCX courses."), student.id, str(course_id)) return None valid_statuses = [ status.generating, status.unavailable, status.deleted, status.error, status.notpassing, status.downloadable, status.auditing, status.audit_passing, status.audit_notpassing, status.unverified, ] cert_status_dict = certificate_status_for_student(student, course_id) cert_status = cert_status_dict.get('status') download_url = cert_status_dict.get('download_url') cert = None if download_url: self._log_pdf_cert_generation_discontinued_warning( student.id, course_id, cert_status, download_url) return None if cert_status not in valid_statuses: LOGGER.warning( ("Cannot create certificate generation task for user %s " "in the course '%s'; " "the certificate status '%s' is not one of %s."), student.id, str(course_id), cert_status, str(valid_statuses)) return None profile = UserProfile.objects.get(user=student) profile_name = profile.name # Needed for access control in grading. self.request.user = student self.request.session = {} is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists() course_grade = CourseGradeFactory().read(student, course_key=course_id) enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user( student, course_id) mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES user_is_verified = IDVerificationService.user_is_verified(student) cert_mode = enrollment_mode is_eligible_for_certificate = modes_api.is_eligible_for_certificate( enrollment_mode, cert_status) if is_whitelisted and not is_eligible_for_certificate: # check if audit certificates are enabled for audit mode is_eligible_for_certificate = enrollment_mode != CourseMode.AUDIT or \ not settings.FEATURES['DISABLE_AUDIT_CERTIFICATES'] unverified = False # For credit mode generate verified certificate if cert_mode in (CourseMode.CREDIT_MODE, CourseMode.MASTERS): cert_mode = CourseMode.VERIFIED if template_file is not None: template_pdf = template_file elif mode_is_verified and user_is_verified: template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format( id=course_id) elif mode_is_verified and not user_is_verified: template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format( id=course_id) if CourseMode.mode_for_course(course_id, CourseMode.HONOR): cert_mode = GeneratedCertificate.MODES.honor else: unverified = True else: # honor code and audit students template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format( id=course_id) LOGGER.info(( "Certificate generated for student %s in the course: %s with template: %s. " "given template: %s, " "user is verified: %s, " "mode is verified: %s," "generate_pdf is: %s"), student.username, str(course_id), template_pdf, template_file, user_is_verified, mode_is_verified, generate_pdf) cert, __ = GeneratedCertificate.objects.get_or_create( user=student, course_id=course_id) cert.mode = cert_mode cert.user = student cert.grade = course_grade.percent cert.course_id = course_id cert.name = profile_name cert.download_url = '' # Strip HTML from grade range label grade_contents = forced_grade or course_grade.letter_grade passing = False try: grade_contents = lxml.html.fromstring( grade_contents).text_content() passing = True except (TypeError, XMLSyntaxError, ParserError) as exc: LOGGER.info(("Could not retrieve grade for student %s " "in the course '%s' " "because an exception occurred while parsing the " "grade contents '%s' as HTML. " "The exception was: '%s'"), student.id, str(course_id), grade_contents, str(exc)) # Check if the student is whitelisted if is_whitelisted: LOGGER.info("Student %s is whitelisted in '%s'", student.id, str(course_id)) passing = True # If this user's enrollment is not eligible to receive a # certificate, mark it as such for reporting and # analytics. Only do this if the certificate is new, or # already marked as ineligible -- we don't want to mark # existing audit certs as ineligible. cutoff = settings.AUDIT_CERT_CUTOFF_DATE if (cutoff and cert.created_date >= cutoff ) and not is_eligible_for_certificate: cert.status = status.audit_passing if passing else status.audit_notpassing cert.save() LOGGER.info( "Student %s with enrollment mode %s is not eligible for a certificate.", student.id, enrollment_mode) return cert # If they are not passing, short-circuit and don't generate cert elif not passing: cert.status = status.notpassing cert.save() LOGGER.info( ("Student %s does not have a grade for '%s', " "so their certificate status has been set to '%s'. " "No certificate generation task was sent to the XQueue."), student.id, str(course_id), cert.status) return cert if unverified: cert.status = status.unverified cert.save() LOGGER.info( ("User %s has a verified enrollment in course %s " "but is missing ID verification. " "Certificate status has been set to unverified"), student.id, str(course_id), ) return cert # Finally, generate the certificate and send it off. return self._generate_cert(cert, student, grade_contents, template_pdf, generate_pdf)
def _can_generate_v2_certificate(user, course_key): """ Check if a v2 course 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_v2_course_certificates(course_key): # This course run is not using the v2 course certificate feature log.info( f'{course_key} is not using v2 course certificates. Certificate cannot be generated.' ) return False if not auto_certificate_generation_enabled(): # Automatic certificate generation is globally disabled log.info( f'Automatic certificate generation is globally disabled. Certificate cannot be generated for ' f'{user.id} : {course_key}.') return False if CertificateInvalidation.has_certificate_invalidation(user, course_key): # The invalidation list prevents certificate generation log.info( f'{user.id} : {course_key} is on the certificate invalidation list. Certificate cannot be generated.' ) return False enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user( user, course_key) if enrollment_mode is None: log.info( f'{user.id} : {course_key} does not have an enrollment. Certificate cannot be generated.' ) return False if not modes_api.is_eligible_for_certificate(enrollment_mode): log.info( f'{user.id} : {course_key} has an enrollment mode of {enrollment_mode}, which is not eligible for an ' f'allowlist certificate. Certificate cannot be generated.') return False if not IDVerificationService.user_is_verified(user): log.info( f'{user.id} does not have a verified id. Certificate cannot be generated for {course_key}.' ) return False if _is_ccx_course(course_key): log.info( f'{course_key} is a CCX course. Certificate cannot be generated for {user.id}.' ) return False course = modulestore().get_course(course_key, depth=0) if _is_beta_tester(user, course): log.info( f'{user.id} is a beta tester in {course_key}. Certificate cannot be generated.' ) return False if not _has_passing_grade(user, course): log.info( f'{user.id} does not have a passing grade in {course_key}. Certificate cannot be generated.' ) return False if not _can_generate_certificate_for_status(user, course_key): return False log.info(f'V2 certificate can be generated for {user.id} : {course_key}') return True