diff --git a/common/djangoapps/course_modes/api.py b/common/djangoapps/course_modes/api.py index 8da8167edef399f5f2b196044ca9394148dc2b95..d560779deb149821bc6461891064d95202371e06 100644 --- a/common/djangoapps/course_modes/api.py +++ b/common/djangoapps/course_modes/api.py @@ -15,3 +15,15 @@ def get_paid_modes_for_course(course_run_id): A list of paid modes (strings) that the course has attached to it. """ return _CourseMode.paid_modes_for_course(course_run_id) + + +def is_eligible_for_certificate(mode_slug, status=None): + """ + Returns whether or not the given mode_slug is eligible for a + certificate. Currently all modes other than 'audit' grant a + certificate. Note that audit enrollments which existed prior + to December 2015 *were* given certificates, so there will be + GeneratedCertificate records with mode='audit' which are + eligible. + """ + return _CourseMode.is_eligible_for_certificate(mode_slug, status) diff --git a/lms/djangoapps/certificates/docs/decisions/001-allowlist-requirements.rst b/lms/djangoapps/certificates/docs/decisions/001-allowlist-requirements.rst index db286dc98575a379f0115a37153209bc459f78cb..97c8073484626d15ff04703da7cb8eefaacb1c50 100644 --- a/lms/djangoapps/certificates/docs/decisions/001-allowlist-requirements.rst +++ b/lms/djangoapps/certificates/docs/decisions/001-allowlist-requirements.rst @@ -22,12 +22,15 @@ won't necessarily have a course certificate available to them. To receive a downloadable allowlist course certificate, the following things must be true at the time the certificate is generated: -* The user must be enrolled in the course +* The user must have an enrollment in the course + + * The enrollment mode must be eligible for a certificate + * The enrollment does not need to be active + * The user must have an approved, unexpired, ID verification -* The user must be on the allowlist for the course run (see the CertificateWhitelist model) -* The user must not have an invalidated certificate for the course run (see the CertificateInvalidation model) -* Certificate generation must be enabled for the course run -* Automatic certificate generation must be enabled +* The user must be on the allowlist for the course run (see the *CertificateWhitelist* model) +* The user must not have an invalidated certificate for the course run (see the *CertificateInvalidation* model) +* Automatic certificate generation must be globally enabled Note: the above requirements were written for the allowlist, which assumes the CourseWaffleFlag *certificates_revamp.use_allowlist* has been enabled for the diff --git a/lms/djangoapps/certificates/generation_handler.py b/lms/djangoapps/certificates/generation_handler.py index da047a59be5449695212bb37ae843aa50891132d..62dd2f51c94a08ef78161d4c17f3cac9acd21e56 100644 --- a/lms/djangoapps/certificates/generation_handler.py +++ b/lms/djangoapps/certificates/generation_handler.py @@ -10,6 +10,7 @@ import logging from edx_toggles.toggles import LegacyWaffleFlagNamespace +from common.djangoapps.course_modes import api as modes_api from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.certificates.models import ( CertificateInvalidation, @@ -164,6 +165,11 @@ def _can_generate_allowlist_certificate(user, course_key): 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.') return False diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 31060569b948fb974db16af0f44f9b0504a6fecc..d84ad71b8c98a6756a8488b780912efc40996d57 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -15,6 +15,7 @@ from lxml.etree import ParserError, XMLSyntaxError from requests.auth import HTTPBasicAuth from capa.xqueue_interface import XQueueInterface, make_hashkey, make_xheader +from common.djangoapps.course_modes import api as modes_api from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollment, UserProfile from lms.djangoapps.certificates.models import CertificateStatuses as status @@ -289,7 +290,7 @@ class XQueueCertInterface: user_is_verified = IDVerificationService.user_is_verified(student) cert_mode = enrollment_mode - is_eligible_for_certificate = CourseMode.is_eligible_for_certificate(enrollment_mode, cert_status) + 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 \ diff --git a/lms/djangoapps/certificates/tests/test_generation_handler.py b/lms/djangoapps/certificates/tests/test_generation_handler.py index 6c0f3da5ad655c0bf39134b410b6151c2291165d..fbdfb06bc254d9e37ed38b7be6a981a51d43f049 100644 --- a/lms/djangoapps/certificates/tests/test_generation_handler.py +++ b/lms/djangoapps/certificates/tests/test_generation_handler.py @@ -195,6 +195,23 @@ class AllowlistTests(ModuleStoreTestCase): CertificateWhitelistFactory.create(course_id=key, user=u) assert not _can_generate_allowlist_certificate(u, key) + def test_can_generate_audit(self): + """ + Test handling when user is enrolled in audit mode + """ + u = UserFactory() + cr = CourseFactory() + key = cr.id # pylint: disable=no-member + CourseEnrollmentFactory( + user=u, + course_id=key, + is_active=True, + mode="audit", + ) + CertificateWhitelistFactory.create(course_id=key, user=u) + + assert not _can_generate_allowlist_certificate(u, key) + def test_can_generate_not_whitelisted(self): """ Test handling when user is not whitelisted diff --git a/lms/djangoapps/certificates/tests/test_queue.py b/lms/djangoapps/certificates/tests/test_queue.py index 1e5ba4b77c72efbe2ad9b2d26f2ad47a6bcc9a02..82fd1064aab2c71127331cbfc03b893a98a8e00c 100644 --- a/lms/djangoapps/certificates/tests/test_queue.py +++ b/lms/djangoapps/certificates/tests/test_queue.py @@ -21,6 +21,7 @@ from testfixtures import LogCapture # and verify that items are being correctly added to the queue # in our `XQueueCertInterface` implementation. from capa.xqueue_interface import XQueueInterface +from common.djangoapps.course_modes import api as modes_api from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from lms.djangoapps.certificates.models import ( @@ -91,7 +92,7 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): id=self.course.id ) mock_send = self.add_cert_to_queue(mode) - if CourseMode.is_eligible_for_certificate(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)