From 2c80a76f8e43d0ed9f74fdae175f4dd48fc34f95 Mon Sep 17 00:00:00 2001 From: Waheed Ahmed <waheed.ahmed@arbisoft.com> Date: Tue, 14 May 2019 15:34:40 +0500 Subject: [PATCH] Add feature flag to disable honor mode certificates. Added a feature flag to disable honor mode certificates for edx.org, by default set to false to allow honor mode certificates for open community. PROD-269 --- common/djangoapps/course_modes/models.py | 7 +++++- .../course_modes/tests/test_models.py | 23 ++++++++++++------- lms/djangoapps/certificates/models.py | 1 + lms/djangoapps/courseware/tests/test_views.py | 18 +++++++++------ lms/djangoapps/courseware/views/views.py | 15 +++++++++++- lms/envs/common.py | 12 ++++++++++ 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index f3d96392c72..8f0a0cbbfd6 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -759,7 +759,12 @@ class CourseMode(models.Model): GeneratedCertificate records with mode='audit' which are eligible. """ - return mode_slug != cls.AUDIT + ineligible_modes = [cls.AUDIT] + + if settings.FEATURES['DISABLE_HONOR_CERTIFICATES']: + ineligible_modes.append(cls.HONOR) + + return mode_slug not in ineligible_modes def to_tuple(self): """ diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py index 4afba862698..7dbc967b33e 100644 --- a/common/djangoapps/course_modes/tests/test_models.py +++ b/common/djangoapps/course_modes/tests/test_models.py @@ -456,17 +456,24 @@ class CourseModeModelTest(TestCase): self.assertIsNone(verified_mode.expiration_datetime) @ddt.data( - (CourseMode.AUDIT, False), - (CourseMode.HONOR, True), - (CourseMode.VERIFIED, True), - (CourseMode.CREDIT_MODE, True), - (CourseMode.PROFESSIONAL, True), - (CourseMode.NO_ID_PROFESSIONAL_MODE, True), + (False, CourseMode.AUDIT, False), + (False, CourseMode.HONOR, True), + (False, CourseMode.VERIFIED, True), + (False, CourseMode.CREDIT_MODE, True), + (False, CourseMode.PROFESSIONAL, True), + (False, CourseMode.NO_ID_PROFESSIONAL_MODE, True), + (True, CourseMode.AUDIT, False), + (True, CourseMode.HONOR, False), + (True, CourseMode.VERIFIED, True), + (True, CourseMode.CREDIT_MODE, True), + (True, CourseMode.PROFESSIONAL, True), + (True, CourseMode.NO_ID_PROFESSIONAL_MODE, True), ) @ddt.unpack - def test_eligible_for_cert(self, mode_slug, expected_eligibility): + def test_eligible_for_cert(self, disable_honor_cert, mode_slug, expected_eligibility): """Verify that non-audit modes are eligible for a cert.""" - self.assertEqual(CourseMode.is_eligible_for_certificate(mode_slug), expected_eligibility) + with override_settings(FEATURES={'DISABLE_HONOR_CERTIFICATES': disable_honor_cert}): + self.assertEqual(CourseMode.is_eligible_for_certificate(mode_slug), expected_eligibility) @ddt.data( (CourseMode.AUDIT, False), diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index f090998bccb..40b5c196a69 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -90,6 +90,7 @@ class CertificateStatuses(object): auditing = 'auditing' audit_passing = 'audit_passing' audit_notpassing = 'audit_notpassing' + honor_passing = 'honor_passing' unverified = 'unverified' invalidated = 'invalidated' requesting = 'requesting' diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 2ec0aa3962a..0bed68db6b3 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -93,6 +93,9 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, chec QUERY_COUNT_TABLE_BLACKLIST = WAFFLE_TABLES +FEATURES_WITH_DISABLE_HONOR_CERTIFICATE = settings.FEATURES.copy() +FEATURES_WITH_DISABLE_HONOR_CERTIFICATE['DISABLE_HONOR_CERTIFICATES'] = True + class TestJumpTo(ModuleStoreTestCase): """ @@ -1694,13 +1697,15 @@ class ProgressPageTests(ProgressPageBaseTests): @patch('courseware.views.views.is_course_passed', PropertyMock(return_value=True)) @patch('lms.djangoapps.certificates.api.get_active_web_certificate', PropertyMock(return_value=True)) - def test_message_for_audit_mode(self): + @override_settings(FEATURES=FEATURES_WITH_DISABLE_HONOR_CERTIFICATE) + @ddt.data(CourseMode.AUDIT, CourseMode.HONOR) + def test_message_for_ineligible_mode(self, course_mode): """ Verify that message appears on progress page, if learner is enrolled - in audit mode. + in an ineligible mode. """ user = UserFactory.create() self.assertTrue(self.client.login(username=user.username, password='test')) - CourseEnrollmentFactory(user=user, course_id=self.course.id, mode=CourseMode.AUDIT) + CourseEnrollmentFactory(user=user, course_id=self.course.id, mode=course_mode) with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_create: course_grade = mock_create.return_value @@ -1709,10 +1714,9 @@ class ProgressPageTests(ProgressPageBaseTests): response = self._get_progress_page() - self.assertContains( - response, - u'You are enrolled in the audit track for this course. The audit track does not include a certificate.' - ) + expected_message = (u'You are enrolled in the {mode} track for this course. ' + u'The {mode} track does not include a certificate.').format(mode=course_mode) + self.assertContains(response, expected_message) def test_invalidated_cert_data(self): """ diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index e4434b986d8..fccf7255820 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -135,6 +135,19 @@ AUDIT_PASSING_CERT_DATA = CertData( cert_web_view_url=None ) +HONOR_PASSING_CERT_DATA = CertData( + CertificateStatuses.honor_passing, + _('Your enrollment: Honor track'), + _('You are enrolled in the honor track for this course. The honor track does not include a certificate.'), + download_url=None, + cert_web_view_url=None +) + +INELIGIBLE_PASSING_CERT_DATA = { + CourseMode.AUDIT: AUDIT_PASSING_CERT_DATA, + CourseMode.HONOR: HONOR_PASSING_CERT_DATA +} + GENERATING_CERT_DATA = CertData( CertificateStatuses.generating, _("We're working on it..."), @@ -1089,7 +1102,7 @@ def _get_cert_data(student, course, enrollment_mode, course_grade=None): returns dict if course certificate is available else None. """ if not CourseMode.is_eligible_for_certificate(enrollment_mode): - return AUDIT_PASSING_CERT_DATA + return INELIGIBLE_PASSING_CERT_DATA.get(enrollment_mode) certificates_enabled_for_course = certs_api.cert_generation_enabled(course.id) if course_grade is None: diff --git a/lms/envs/common.py b/lms/envs/common.py index 5195b9a7efe..a5e4b00631d 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -197,6 +197,18 @@ FEATURES = { # Toggle to enable certificates of courses on dashboard 'ENABLE_VERIFIED_CERTIFICATES': False, + # .. toggle_name: DISABLE_HONOR_CERTIFICATES + # .. toggle_type: feature_flag + # .. toggle_default: False + # .. toggle_description: Set to True to disable honor certificates. Typically used when your installation only allows verified certificates, like courses.edx.org. + # .. toggle_category: certificates + # .. toggle_use_cases: open_edx + # .. toggle_creation_date: 2019-05-14 + # .. toggle_expiration_date: None + # .. toggle_tickets: https://openedx.atlassian.net/browse/PROD-269 + # .. toggle_status: supported + 'DISABLE_HONOR_CERTIFICATES': False, # Toggle to disable honor certificates + # for acceptance and load testing 'AUTOMATIC_AUTH_FOR_TESTING': False, -- GitLab