From 90f5e42dbd5fe0cbd0385fd864afbd73858b0f77 Mon Sep 17 00:00:00 2001 From: Christie Rice <8483753+crice100@users.noreply.github.com> Date: Tue, 16 Mar 2021 09:37:27 -0400 Subject: [PATCH] MICROBA-1055 Require a valid enrollment mode, even on the allowlist (#27012) --- common/djangoapps/course_modes/api.py | 12 ++++++++++++ .../decisions/001-allowlist-requirements.rst | 13 ++++++++----- .../certificates/generation_handler.py | 6 ++++++ lms/djangoapps/certificates/queue.py | 3 ++- .../tests/test_generation_handler.py | 17 +++++++++++++++++ lms/djangoapps/certificates/tests/test_queue.py | 3 ++- 6 files changed, 47 insertions(+), 7 deletions(-) diff --git a/common/djangoapps/course_modes/api.py b/common/djangoapps/course_modes/api.py index 8da8167edef..d560779deb1 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 db286dc9857..97c80734846 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 da047a59be5..62dd2f51c94 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 31060569b94..d84ad71b8c9 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 6c0f3da5ad6..fbdfb06bc25 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 1e5ba4b77c7..82fd1064aab 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) -- GitLab