diff --git a/cms/envs/common.py b/cms/envs/common.py index efdec46117..850eb4c5d0 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -472,6 +472,18 @@ FEATURES = { # .. toggle_target_removal_date: 2021-10-01 # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MICROBA-1405' 'ENABLE_V2_CERT_DISPLAY_SETTINGS': False, + + # .. toggle_name: FEATURES['ENABLE_INTEGRITY_SIGNATURE'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Whether to replace ID verification course/certificate requirement + # with an in-course Honor Code agreement + # (https://github.com/edx/edx-name-affirmation) + # .. toggle_use_cases: open_edx + # .. toggle_creation_date: 2022-02-15 + # .. toggle_target_removal_date: None + # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1348' + 'ENABLE_INTEGRITY_SIGNATURE': False, } # .. toggle_name: ENABLE_COPPA_COMPLIANCE diff --git a/common/djangoapps/course_modes/helpers.py b/common/djangoapps/course_modes/helpers.py index e5d9a306c0..b56f1f71df 100644 --- a/common/djangoapps/course_modes/helpers.py +++ b/common/djangoapps/course_modes/helpers.py @@ -11,7 +11,6 @@ from slumber.exceptions import SlumberBaseException from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.helpers import VERIFY_STATUS_APPROVED, VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED # lint-amnesty, pylint: disable=line-too-long -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangoapps.commerce.utils import ecommerce_api_client DISPLAY_VERIFIED = "verified" @@ -42,7 +41,7 @@ def enrollment_mode_display(mode, verification_status, course_id): display_mode = _enrollment_mode_display(mode, verification_status, course_id) if display_mode == DISPLAY_VERIFIED: - if is_integrity_signature_enabled(course_id) or verification_status == VERIFY_STATUS_APPROVED: + if settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') or verification_status == VERIFY_STATUS_APPROVED: enrollment_title = _("You're enrolled as a verified student") enrollment_value = _("Verified") show_image = True @@ -81,7 +80,7 @@ def _enrollment_mode_display(enrollment_mode, verification_status, course_id): if enrollment_mode == CourseMode.VERIFIED: if ( - is_integrity_signature_enabled(course_id) + settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') or verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED] ): display_mode = DISPLAY_VERIFIED diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py index d7b2cdd20e..14faaf907d 100644 --- a/common/djangoapps/course_modes/tests/test_models.py +++ b/common/djangoapps/course_modes/tests/test_models.py @@ -11,10 +11,10 @@ from datetime import timedelta from unittest.mock import patch import ddt +from django.conf import settings from django.core.exceptions import ValidationError from django.test import TestCase, override_settings from django.utils.timezone import now -from edx_toggles.toggles.testutils import override_waffle_flag from opaque_keys.edx.locator import CourseLocator from common.djangoapps.course_modes.helpers import enrollment_mode_display @@ -25,7 +25,6 @@ from common.djangoapps.course_modes.models import ( invalidate_course_mode_cache ) from common.djangoapps.course_modes.tests.factories import CourseModeFactory -from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order @@ -367,25 +366,25 @@ class CourseModeModelTest(TestCase): ('no-id-professional', 'dummy', False) ) @ddt.unpack - def test_enrollment_mode_display(self, mode, verification_status, enable_flag): + def test_enrollment_mode_display(self, mode, verification_status, enable_integrity_signature): - with override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=enable_flag): + with patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature): if mode == "verified": assert enrollment_mode_display(mode, verification_status, self.course_key) ==\ - self._enrollment_display_modes_dicts(mode, verification_status, enable_flag) + self._enrollment_display_modes_dicts(mode, verification_status, enable_integrity_signature) assert enrollment_mode_display(mode, verification_status, self.course_key) ==\ - self._enrollment_display_modes_dicts(mode, verification_status, enable_flag) + self._enrollment_display_modes_dicts(mode, verification_status, enable_integrity_signature) assert enrollment_mode_display(mode, verification_status, self.course_key) ==\ - self._enrollment_display_modes_dicts(mode, verification_status, enable_flag) + self._enrollment_display_modes_dicts(mode, verification_status, enable_integrity_signature) elif mode == "honor": assert enrollment_mode_display(mode, verification_status, self.course_key) ==\ - self._enrollment_display_modes_dicts(mode, mode, enable_flag) + self._enrollment_display_modes_dicts(mode, mode, enable_integrity_signature) elif mode == "audit": assert enrollment_mode_display(mode, verification_status, self.course_key) ==\ - self._enrollment_display_modes_dicts(mode, mode, enable_flag) + self._enrollment_display_modes_dicts(mode, mode, enable_integrity_signature) elif mode == "professional": assert enrollment_mode_display(mode, verification_status, self.course_key) ==\ - self._enrollment_display_modes_dicts(mode, mode, enable_flag) + self._enrollment_display_modes_dicts(mode, mode, enable_integrity_signature) @ddt.data( (['honor', 'verified', 'credit'], ['honor', 'verified']), diff --git a/common/djangoapps/student/email_helpers.py b/common/djangoapps/student/email_helpers.py index 8accbfcc04..b13590cafc 100644 --- a/common/djangoapps/student/email_helpers.py +++ b/common/djangoapps/student/email_helpers.py @@ -9,7 +9,6 @@ from django.conf import settings from lms.djangoapps.verify_student.services import IDVerificationService from openedx.core.djangoapps.ace_common.template_context import get_base_template_context -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangoapps.enrollments.api import is_enrollment_valid_for_proctoring from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order @@ -56,7 +55,7 @@ def generate_proctoring_requirements_email_context(user, course_id): 'course_name': course_module.display_name, 'proctoring_provider': capwords(course_module.proctoring_provider.replace('_', ' ')), 'proctoring_requirements_url': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('faq', ''), - 'idv_required': not is_integrity_signature_enabled(course_id), + 'idv_required': not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'), 'id_verification_url': IDVerificationService.get_verify_location(), } diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py index 56781937f6..71b46d5af5 100644 --- a/common/djangoapps/student/helpers.py +++ b/common/djangoapps/student/helpers.py @@ -50,7 +50,6 @@ from lms.djangoapps.instructor import access from lms.djangoapps.verify_student.models import VerificationDeadline from lms.djangoapps.verify_student.services import IDVerificationService from lms.djangoapps.verify_student.utils import is_verification_expiring_soon, verification_for_datetime -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangoapps.content.block_structure.exceptions import UsageKeyNotInBlockStructure from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.theming.helpers import get_themes @@ -117,14 +116,8 @@ def check_verify_status_by_course(user, course_enrollments): """ status_by_course = {} - # Before retriving verification data, if the integrity_signature feature is enabled for the course - # let's bypass all logic below. Filter down to those course with integrity_signature not enabled. - enabled_course_enrollments = [] - for enrollment in course_enrollments: - if not is_integrity_signature_enabled(enrollment.course_id): - enabled_course_enrollments.append(enrollment) - - if len(enabled_course_enrollments) == 0: + # If integrity signature is enabled, this is a no-op because IDV is not required + if settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'): return status_by_course # Retrieve all verifications for the user, sorted in descending @@ -144,7 +137,7 @@ def check_verify_status_by_course(user, course_enrollments): ) recent_verification_datetime = None - for enrollment in enabled_course_enrollments: + for enrollment in course_enrollments: # If the user hasn't enrolled as verified, then the course # won't display state related to its verification status. diff --git a/common/djangoapps/student/tests/test_email.py b/common/djangoapps/student/tests/test_email.py index d6155e8a40..dde8a0daad 100644 --- a/common/djangoapps/student/tests/test_email.py +++ b/common/djangoapps/student/tests/test_email.py @@ -15,7 +15,6 @@ from django.test import TransactionTestCase, override_settings from django.test.client import RequestFactory from django.urls import reverse from django.utils.html import escape -from edx_toggles.toggles.testutils import override_waffle_flag from common.djangoapps.edxmako.shortcuts import marketing_link from common.djangoapps.student.email_helpers import generate_proctoring_requirements_email_context @@ -32,7 +31,6 @@ from common.djangoapps.third_party_auth.views import inactive_user_view from common.djangoapps.util.testing import EventTestMixin from lms.djangoapps.verify_student.services import IDVerificationService from openedx.core.djangoapps.ace_common.tests.mixins import EmailTemplateTagMixin -from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE, is_integrity_signature_enabled from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme from openedx.core.djangolib.testing.utils import CacheIsolationMixin, CacheIsolationTestCase @@ -239,7 +237,7 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas send_proctoring_requirements_email(context) self._assert_email() - @override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True) + @patch.dict('django.conf.settings.FEATURES', {'ENABLE_INTEGRITY_SIGNATURE': True}) def test_send_proctoring_requirements_email_honor(self): self.course = CourseFactory( display_name='honor code on course', @@ -298,7 +296,7 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas escape("Before taking a graded proctored exam, you must have approved ID verification photos."), id_verification_url, ] - if not is_integrity_signature_enabled(self.course.id): + if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'): fragments.extend(idv_fragments) return (fragments, []) return (fragments, idv_fragments) diff --git a/common/djangoapps/student/tests/test_verification_status.py b/common/djangoapps/student/tests/test_verification_status.py index 1c767a8670..372e4724be 100644 --- a/common/djangoapps/student/tests/test_verification_status.py +++ b/common/djangoapps/student/tests/test_verification_status.py @@ -11,7 +11,6 @@ from django.conf import settings from django.test import override_settings from django.urls import reverse from django.utils.timezone import now -from edx_toggles.toggles.testutils import override_waffle_flag from pytz import UTC from common.djangoapps.course_modes.tests.factories import CourseModeFactory @@ -28,7 +27,6 @@ from common.djangoapps.util.testing import UrlResetMixin from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order -from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE @patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True}) @@ -320,7 +318,7 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase): self.assertContains(response2, attempt2.expiration_datetime.strftime("%m/%d/%Y")) self.assertContains(response2, attempt2.expiration_datetime.strftime("%m/%d/%Y"), count=2) - @override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True) + @patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True}) @ddt.data( None, 'past', @@ -347,7 +345,7 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase): self._assert_course_verification_status(None, "verified") @ddt.data(True, False) - def test_integrity_disables_sidebar(self, integrity_flag): + def test_integrity_disables_sidebar(self, enable_integrity_signature): self._setup_mode_and_enrollment(None, "verified") #no sidebar when no IDV yet @@ -360,11 +358,10 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase): attempt.submit() attempt.approve() - # sidebar only appears after IDV if integrity is not on - with patch('common.djangoapps.student.views.dashboard.is_integrity_signature_enabled', - return_value=integrity_flag): + # sidebar only appears after IDV if integrity signature setting is not on + with patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': enable_integrity_signature}): response = self.client.get(self.dashboard_url) - if integrity_flag: + if enable_integrity_signature: self.assertNotContains(response, "profile-sidebar") else: self.assertContains(response, "profile-sidebar") diff --git a/common/djangoapps/student/views/dashboard.py b/common/djangoapps/student/views/dashboard.py index 56a77c57f1..7cbf7988c4 100644 --- a/common/djangoapps/student/views/dashboard.py +++ b/common/djangoapps/student/views/dashboard.py @@ -29,7 +29,6 @@ from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.courseware.access import has_access from lms.djangoapps.experiments.utils import get_dashboard_course_info, get_experiment_user_metadata_context from lms.djangoapps.verify_student.services import IDVerificationService -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangoapps.catalog.utils import ( get_programs, get_pseudo_session_for_entitlement, @@ -749,16 +748,8 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem else: redirect_message = '' - all_integrity_enabled = True - if not course_enrollments: - all_integrity_enabled = is_integrity_signature_enabled(None) - for enrollment in course_enrollments: - if not is_integrity_signature_enabled(enrollment.course_id): - all_integrity_enabled = False - break - valid_verification_statuses = ['approved', 'must_reverify', 'pending', 'expired'] - display_sidebar_on_dashboard = not all_integrity_enabled and \ + display_sidebar_on_dashboard = not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') and \ verification_status['status'] in valid_verification_statuses and \ verification_status['should_display'] diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index e7dfdd1a0c..6ffec46032 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -35,7 +35,6 @@ from xmodule.x_module import ( ) from common.djangoapps.xblock_django.constants import ATTR_KEY_USER_ID, ATTR_KEY_USER_IS_STAFF -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from .exceptions import NotFoundError from .fields import Date @@ -950,7 +949,7 @@ class SequenceBlock( 'allow_proctoring_opt_out': self.allow_proctoring_opt_out, 'due_date': self.due, 'grace_period': self.graceperiod, # lint-amnesty, pylint: disable=no-member - 'is_integrity_signature_enabled': is_integrity_signature_enabled(course_id), + 'is_integrity_signature_enabled': settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'), } # inject the user's credit requirements and fulfillments diff --git a/lms/djangoapps/certificates/generation_handler.py b/lms/djangoapps/certificates/generation_handler.py index 4bf08fbdda..d2641e3abf 100644 --- a/lms/djangoapps/certificates/generation_handler.py +++ b/lms/djangoapps/certificates/generation_handler.py @@ -7,6 +7,7 @@ cannot be generated, a message is logged and no further action is taken. """ import logging +from django.conf import settings from common.djangoapps.course_modes import api as modes_api from common.djangoapps.course_modes.models import CourseMode @@ -22,7 +23,6 @@ from lms.djangoapps.certificates.utils import has_html_certificates_enabled from lms.djangoapps.grades.api import CourseGradeFactory from lms.djangoapps.instructor.access import is_beta_tester from lms.djangoapps.verify_student.services import IDVerificationService -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none log = logging.getLogger(__name__) @@ -179,7 +179,7 @@ def _can_generate_certificate_common(user, course_key, enrollment_mode): # 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(course_key, user): + 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 @@ -232,7 +232,7 @@ def _set_regular_cert_status(user, course_key, enrollment_mode, course_grade): if status is not None: return status - if not _required_verification_missing(course_key, user) \ + if not _required_verification_missing(user) \ and not _is_passing_grade(course_grade) \ and cert is not None: if cert.status != CertificateStatuses.notpassing: @@ -255,8 +255,8 @@ def _get_cert_status_common(user, course_key, enrollment_mode, course_grade, cer cert.invalidate(mode=enrollment_mode, source='certificate_generation') return CertificateStatuses.unavailable - if _required_verification_missing(course_key, user) and _has_passing_grade_or_is_allowlisted(user, course_key, - course_grade): + if _required_verification_missing(user) and _has_passing_grade_or_is_allowlisted(user, course_key, + course_grade): if cert is None: _generate_certificate_task(user=user, course_key=course_key, enrollment_mode=enrollment_mode, course_grade=course_grade, status=CertificateStatuses.unverified, @@ -416,8 +416,8 @@ def _is_mode_now_eligible(enrollment_mode, cert): return False -def _required_verification_missing(course_key, user): +def _required_verification_missing(user): """ Return true if IDV is required for this course and the user does not have it """ - return not is_integrity_signature_enabled(course_key) and not IDVerificationService.user_is_verified(user) + return not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') and not IDVerificationService.user_is_verified(user) diff --git a/lms/djangoapps/certificates/management/commands/regenerate_noidv_cert.py b/lms/djangoapps/certificates/management/commands/regenerate_noidv_cert.py index 51f072489a..527e4680a1 100644 --- a/lms/djangoapps/certificates/management/commands/regenerate_noidv_cert.py +++ b/lms/djangoapps/certificates/management/commands/regenerate_noidv_cert.py @@ -13,7 +13,6 @@ from opaque_keys.edx.keys import CourseKey from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.generation_handler import generate_certificate_task from lms.djangoapps.certificates.models import GeneratedCertificate -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled log = logging.getLogger(__name__) User = get_user_model() @@ -106,10 +105,6 @@ def _handle_course(course_key, batch_size, sleep_seconds, count): Returns how many certs were originally unverified and regenerated. """ - if not is_integrity_signature_enabled(course_key): - log.warning(f'Skipping {course_key} which does not have honor code enabled') - return 0 - certs = GeneratedCertificate.objects.filter( course_id=course_key, status=CertificateStatuses.unverified, @@ -117,7 +112,7 @@ def _handle_course(course_key, batch_size, sleep_seconds, count): log.info(f'Regenerating {len(certs)} unverified certificates for {course_key}') - return _regenerate_certs(certs, batch_size, sleep_seconds, count, False) + return _regenerate_certs(certs, batch_size, sleep_seconds, count) def _handle_all_courses(batch_size, sleep_seconds, excluded_keys): @@ -130,24 +125,18 @@ def _handle_all_courses(batch_size, sleep_seconds, excluded_keys): .exclude(course_id__in=excluded_keys) log.info(f'Regenerating {len(certs)} unverified certificates in all courses except for excluded keys') - return _regenerate_certs(certs, batch_size, sleep_seconds, 0, True) + return _regenerate_certs(certs, batch_size, sleep_seconds, 0) -def _regenerate_certs(certs, batch_size, sleep_seconds, count, check_integrity_signature_flag): +def _regenerate_certs(certs, batch_size, sleep_seconds, count): """ 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) + 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 diff --git a/lms/djangoapps/certificates/management/commands/tests/test_regenerate_noidv_cert.py b/lms/djangoapps/certificates/management/commands/tests/test_regenerate_noidv_cert.py index 4783fd3940..918fb077d9 100644 --- a/lms/djangoapps/certificates/management/commands/tests/test_regenerate_noidv_cert.py +++ b/lms/djangoapps/certificates/management/commands/tests/test_regenerate_noidv_cert.py @@ -5,6 +5,7 @@ Tests for the cert_generation command from unittest import mock import pytest +from django.conf import settings from django.core.management import CommandError, call_command from common.djangoapps.course_modes.models import CourseMode @@ -15,19 +16,15 @@ from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFact from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order -COMMAND_INTEGRITY_ENABLED = \ - 'lms.djangoapps.certificates.management.commands.regenerate_noidv_cert.is_integrity_signature_enabled' -INTEGRITY_ENABLED_METHOD = 'lms.djangoapps.certificates.generation_handler.is_integrity_signature_enabled' ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified' PASSING_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_passing_grade' WEB_CERTS_METHOD = 'lms.djangoapps.certificates.generation_handler.has_html_certificates_enabled' -# base setup is unverified users, honor code (integrity) turned on wherever imports it +# base setup is unverified users, integrity signature turned on, # and normal passing grade certificates for convenience +@mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=True) @mock.patch(ID_VERIFIED_METHOD, mock.Mock(return_value=False)) -@mock.patch(INTEGRITY_ENABLED_METHOD, mock.Mock(return_value=True)) -@mock.patch(COMMAND_INTEGRITY_ENABLED, mock.Mock(return_value=True)) @mock.patch(PASSING_GRADE_METHOD, mock.Mock(return_value=True)) @mock.patch(WEB_CERTS_METHOD, mock.Mock(return_value=True)) class RegenerateNoIDVCertTests(ModuleStoreTestCase): @@ -104,31 +101,6 @@ class RegenerateNoIDVCertTests(ModuleStoreTestCase): regenerated = call_command("regenerate_noidv_cert", "-c", course_run_key) self.assertEqual('0', regenerated) - def test_regeneration_honor_off(self): - """ - If a course does not have the honor code enabled, no point regenerating - """ - course_run = CourseFactory() - course_run_key = course_run.id - - user = UserFactory() - CourseEnrollmentFactory( - user=user, - course_id=course_run_key, - is_active=True, - mode=CourseMode.VERIFIED, - ) - GeneratedCertificateFactory( - user=user, - course_id=course_run_key, - mode=GeneratedCertificate.MODES.verified, - status=CertificateStatuses.unverified - ) - - with mock.patch(COMMAND_INTEGRITY_ENABLED, mock.Mock(return_value=False)): - regenerated = call_command("regenerate_noidv_cert", "-c", course_run_key) - self.assertEqual('0', regenerated) - def _multisetup(self): """ setup certs across two course runs diff --git a/lms/djangoapps/certificates/tests/test_generation_handler.py b/lms/djangoapps/certificates/tests/test_generation_handler.py index ee96cff0d0..93ca9a553c 100644 --- a/lms/djangoapps/certificates/tests/test_generation_handler.py +++ b/lms/djangoapps/certificates/tests/test_generation_handler.py @@ -38,13 +38,11 @@ BETA_TESTER_METHOD = 'lms.djangoapps.certificates.generation_handler.is_beta_tes COURSE_OVERVIEW_METHOD = 'lms.djangoapps.certificates.generation_handler.get_course_overview_or_none' CCX_COURSE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_ccx_course' GET_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._get_course_grade' -INTEGRITY_ENABLED_METHOD = 'lms.djangoapps.certificates.generation_handler.is_integrity_signature_enabled' ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified' PASSING_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_passing_grade' WEB_CERTS_METHOD = 'lms.djangoapps.certificates.generation_handler.has_html_certificates_enabled' -@mock.patch(INTEGRITY_ENABLED_METHOD, mock.Mock(return_value=False)) @mock.patch(ID_VERIFIED_METHOD, mock.Mock(return_value=True)) @mock.patch(WEB_CERTS_METHOD, mock.Mock(return_value=True)) @ddt.ddt @@ -204,15 +202,15 @@ class AllowlistTests(ModuleStoreTestCase): assert generate_certificate_task(self.user, self.course_run_key) @ddt.data(False, True) - def test_can_generate_not_verified(self, idv_retired): + def test_can_generate_not_verified(self, enable_integrity_signature): """ Test handling when the user's id is not verified """ with mock.patch(ID_VERIFIED_METHOD, return_value=False), \ - mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired): - self.assertEqual(idv_retired, + mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature): + self.assertEqual(enable_integrity_signature, _can_generate_allowlist_certificate(self.user, self.course_run_key, self.enrollment_mode)) - self.assertIsNot(idv_retired, + self.assertIsNot(enable_integrity_signature, _set_allowlist_cert_status( self.user, self.course_run_key, self.enrollment_mode, self.grade) == CertificateStatuses.unverified) @@ -358,7 +356,7 @@ class AllowlistTests(ModuleStoreTestCase): assert not _can_generate_allowlist_certificate(self.user, course_run_key, enrollment_mode) -@mock.patch(INTEGRITY_ENABLED_METHOD, mock.Mock(return_value=False)) +@mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=False) @mock.patch(ID_VERIFIED_METHOD, mock.Mock(return_value=True)) @mock.patch(CCX_COURSE_METHOD, mock.Mock(return_value=False)) @mock.patch(PASSING_GRADE_METHOD, mock.Mock(return_value=True)) @@ -516,7 +514,7 @@ class CertificateTests(ModuleStoreTestCase): assert _can_generate_certificate_for_status(None, None, None) @ddt.data(False, True) - def test_can_generate_not_verified_cert(self, idv_retired): + def test_can_generate_not_verified_cert(self, enable_integrity_signature): """ Test handling when the user's id is not verified and they have a cert """ @@ -535,14 +533,16 @@ class CertificateTests(ModuleStoreTestCase): ) with mock.patch(ID_VERIFIED_METHOD, return_value=False), \ - mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired): - self.assertEqual(idv_retired, _can_generate_regular_certificate(u, self.course_run_key, - self.enrollment_mode, self.grade)) - self.assertIsNot(idv_retired, _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, - self.grade) == CertificateStatuses.unverified) + mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature): + self.assertEqual( + enable_integrity_signature, + _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade) + ) + regular_cert_status = _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) + self.assertIsNot(enable_integrity_signature, regular_cert_status == CertificateStatuses.unverified) @ddt.data(False, True) - def test_can_generate_not_verified_no_cert(self, idv_retired): + def test_can_generate_not_verified_no_cert(self, enable_integrity_signature): """ Test handling when the user's id is not verified and they don't have a cert """ @@ -555,14 +555,16 @@ class CertificateTests(ModuleStoreTestCase): ) with mock.patch(ID_VERIFIED_METHOD, return_value=False), \ - mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired): - self.assertEqual(idv_retired, _can_generate_regular_certificate(u, self.course_run_key, - self.enrollment_mode, self.grade)) - self.assertIsNot(idv_retired, _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, - self.grade) == CertificateStatuses.unverified) + mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature): + self.assertEqual( + enable_integrity_signature, + _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade) + ) + regular_cert_status = _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) + self.assertIsNot(enable_integrity_signature, regular_cert_status == CertificateStatuses.unverified) @ddt.data(False, True) - def test_can_generate_not_verified_not_passing(self, idv_retired): + def test_can_generate_not_verified_not_passing(self, enable_integrity_signature): """ Test handling when the user's id is not verified and the user is not passing """ @@ -581,17 +583,17 @@ class CertificateTests(ModuleStoreTestCase): ) with mock.patch(ID_VERIFIED_METHOD, return_value=False), \ - mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired), \ + mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature), \ mock.patch(PASSING_GRADE_METHOD, return_value=False): assert not _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade) - if idv_retired: + if enable_integrity_signature: assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) \ == CertificateStatuses.notpassing else: assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) is None @ddt.data(False, True) - def test_can_generate_not_verified_not_passing_allowlist(self, idv_retired): + def test_can_generate_not_verified_not_passing_allowlist(self, enable_integrity_signature): """ Test handling when the user's id is not verified and the user is not passing but is on the allowlist """ @@ -611,10 +613,10 @@ class CertificateTests(ModuleStoreTestCase): CertificateAllowlistFactory(course_id=self.course_run_key, user=u) with mock.patch(ID_VERIFIED_METHOD, return_value=False), \ - mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired), \ + mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature), \ mock.patch(PASSING_GRADE_METHOD, return_value=False): assert not _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade) - if idv_retired: + if enable_integrity_signature: assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) == CertificateStatuses.notpassing else: diff --git a/lms/djangoapps/certificates/tests/test_webview_views.py b/lms/djangoapps/certificates/tests/test_webview_views.py index 33530d1b7f..89d75e866d 100644 --- a/lms/djangoapps/certificates/tests/test_webview_views.py +++ b/lms/djangoapps/certificates/tests/test_webview_views.py @@ -1643,34 +1643,33 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) self.assertNotContains(response, verified_name) @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) - @patch('lms.djangoapps.certificates.views.webview.is_integrity_signature_enabled') @ddt.data( True, False ) - def test_verified_certificate_description(self, integrity_signature_enabled, mock_integrity_signature): + def test_verified_certificate_description(self, enable_integrity_signature): """ Test that for a verified cert, the correct language is used when the integrity signature feature is enabled. """ - mock_integrity_signature.return_value = integrity_signature_enabled - self._add_course_certificates(count=1, signatory_count=2, is_active=True) - self._create_custom_template_with_verified_description() - self.cert.mode = 'verified' - self.cert.save() - test_url = get_certificate_url( - user_id=self.user.id, - course_id=str(self.course.id), - uuid=self.cert.verify_uuid - ) + with patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature): + self._add_course_certificates(count=1, signatory_count=2, is_active=True) + self._create_custom_template_with_verified_description() + self.cert.mode = 'verified' + self.cert.save() + test_url = get_certificate_url( + user_id=self.user.id, + course_id=str(self.course.id), + uuid=self.cert.verify_uuid + ) - response = self.client.get(test_url) - assert response.status_code == 200 - if not integrity_signature_enabled: - self.assertContains(response, 'identity of the learner has been checked and is valid') - self.assertNotContains(response, 'Integrity signature enabled') - else: - self.assertNotContains(response, 'identity of the learner has been checked and is valid') - self.assertContains(response, 'Integrity signature enabled') + response = self.client.get(test_url) + assert response.status_code == 200 + if not enable_integrity_signature: + self.assertContains(response, 'identity of the learner has been checked and is valid') + self.assertNotContains(response, 'Integrity signature enabled') + else: + self.assertNotContains(response, 'identity of the learner has been checked and is valid') + self.assertContains(response, 'Integrity signature enabled') class CertificateEventTests(CommonCertificatesTestCase, EventTrackingTestCase): diff --git a/lms/djangoapps/certificates/views/webview.py b/lms/djangoapps/certificates/views/webview.py index 287d87f7c8..bf284a60ef 100644 --- a/lms/djangoapps/certificates/views/webview.py +++ b/lms/djangoapps/certificates/views/webview.py @@ -49,7 +49,6 @@ from lms.djangoapps.certificates.utils import ( get_certificate_url, get_preferred_certificate_name ) -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangoapps.catalog.api import get_course_run_details from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none from openedx.core.djangoapps.lang_pref.api import get_closest_released_language @@ -85,7 +84,7 @@ def get_certificate_description(mode, certificate_type, platform_name, course_ke "{platform_name} and has completed all of the required tasks for this course " "under its guidelines. ").format(cert_type=certificate_type, platform_name=platform_name) - if not is_integrity_signature_enabled(course_key): + if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'): certificate_type_description += _("A {cert_type} certificate also indicates that the " "identity of the learner has been checked and " "is valid.").format(cert_type=certificate_type) @@ -253,7 +252,7 @@ def _update_course_context(request, context, course, platform_name): context['accomplishment_copy_course_name'] = accomplishment_copy_course_name course_number = course.display_coursenumber if course.display_coursenumber else course.number context['course_number'] = course_number - context['is_integrity_signature_enabled_for_course'] = is_integrity_signature_enabled(course.location.course_key) + context['is_integrity_signature_enabled_for_course'] = settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') if context['organization_long_name']: # Translators: This text represents the description of course context['accomplishment_copy_course_description'] = _('a course of study offered by {partner_short_name}, ' diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index ce8d8e3a2d..055d7dc470 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -24,7 +24,6 @@ from lms.djangoapps.certificates.api import get_active_web_certificate, can_show from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, can_show_verified_upgrade from lms.djangoapps.verify_student.models import VerificationDeadline from lms.djangoapps.verify_student.services import IDVerificationService -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangolib.markup import HTML, Text from openedx.features.course_duration_limits.access import get_user_course_expiration_date from openedx.features.course_experience import RELATIVE_DATES_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages @@ -721,7 +720,7 @@ class VerificationDeadlineDate(DateSummary): is_active and mode == 'verified' and self.verification_status in ('expired', 'none', 'must_reverify') and - not is_integrity_signature_enabled(self.course_id) + not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') ) @lazy diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index e3188824a0..2ea337d639 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -3,10 +3,12 @@ from datetime import datetime, timedelta +from unittest.mock import patch import crum import ddt import waffle # lint-amnesty, pylint: disable=invalid-django-waffle-import +from django.conf import settings from django.contrib.messages.middleware import MessageMiddleware from django.test import RequestFactory from django.urls import reverse @@ -40,7 +42,6 @@ from lms.djangoapps.courseware.models import ( from lms.djangoapps.verify_student.models import VerificationDeadline from lms.djangoapps.verify_student.services import IDVerificationService from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory -from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration @@ -656,7 +657,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): block = VerificationDeadlineDate(course, user) assert not block.is_allowed - @override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True) + @patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True}) def test_verification_deadline_with_integrity_signature(self): course = create_course_run(days_till_start=-1) user = create_user() diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 8124d8e3ac..09b15d370c 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -99,7 +99,6 @@ from lms.djangoapps.grades.config.waffle import waffle_switch as grades_waffle_s from lms.djangoapps.instructor.access import allow_access from lms.djangoapps.verify_student.models import VerificationDeadline from lms.djangoapps.verify_student.services import IDVerificationService -from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory, ProgramFactory from openedx.core.djangoapps.content.course_overviews.models import CourseOverview @@ -1930,7 +1929,7 @@ class ProgressPageTests(ProgressPageBaseTests): assert response.title == 'Your certificate will be available soon!' @ddt.data(True, False) - def test_no_certs_generated_and_not_verified(self, waffle_override): + def test_no_certs_generated_and_not_verified(self, enable_integrity_signature): """ Verify if the learner is not ID Verified, and the certs are not yet generated, but the learner is eligible, the get_cert_data would return cert status Unverified @@ -1939,14 +1938,14 @@ class ProgressPageTests(ProgressPageBaseTests): CertificateGenerationCourseSetting( course_key=self.course.id, self_generation_enabled=True ).save() - with override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=waffle_override): + with patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature): with patch( 'lms.djangoapps.certificates.api.certificate_downloadable_status', return_value=self.mock_certificate_downloadable_status() ): response = views.get_cert_data(self.user, self.course, CourseMode.VERIFIED, MagicMock(passed=True)) - if waffle_override: + if enable_integrity_signature: assert response.cert_status == 'requesting' assert response.title == 'Congratulations, you qualified for a certificate!' else: diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 0ea4121819..7a274e4784 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -106,7 +106,6 @@ from lms.djangoapps.instructor.enrollment import uses_shib from lms.djangoapps.instructor.views.api import require_global_staff from lms.djangoapps.survey import views as survey_views from lms.djangoapps.verify_student.services import IDVerificationService -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangoapps.catalog.utils import get_programs, get_programs_with_type from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.credit.api import ( @@ -1254,8 +1253,8 @@ def _downloadable_certificate_message(course, cert_downloadable_status): # lint return _downloadable_cert_data(download_url=cert_downloadable_status['download_url']) -def _missing_required_verification(student, enrollment_mode, course_key): - return not is_integrity_signature_enabled(course_key) and ( +def _missing_required_verification(student, enrollment_mode): + return not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') and ( enrollment_mode in CourseMode.VERIFIED_MODES and not IDVerificationService.user_is_verified(student) ) @@ -1278,7 +1277,7 @@ def _certificate_message(student, course, enrollment_mode): # lint-amnesty, pyl if cert_downloadable_status['is_downloadable']: return _downloadable_certificate_message(course, cert_downloadable_status) - if _missing_required_verification(student, enrollment_mode, course.id): + if _missing_required_verification(student, enrollment_mode): return UNVERIFIED_CERT_DATA return REQUESTING_CERT_DATA diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index e4c5110e8c..77bef6407a 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -2812,28 +2812,28 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment True, False ) - @patch('lms.djangoapps.instructor.views.api.is_integrity_signature_enabled') @valid_problem_location - def test_idv_retirement_student_features_report(self, toggle_value, mock_toggle): - mock_toggle.return_value = toggle_value - kwargs = {'course_id': str(self.course.id)} - kwargs.update({'csv': '/csv'}) - url = reverse('get_students_features', kwargs=kwargs) - success_status = 'The enrolled learner profile report is being created.' - with patch('lms.djangoapps.instructor_task.api.submit_calculate_students_features_csv') as mock_task_endpoint: - CourseFinanceAdminRole(self.course.id).add_users(self.instructor) - response = self.client.post(url, {}) - self.assertContains(response, success_status) + def test_idv_retirement_student_features_report(self, enable_integrity_signature): + with patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': enable_integrity_signature}): + kwargs = {'course_id': str(self.course.id)} + kwargs.update({'csv': '/csv'}) + url = reverse('get_students_features', kwargs=kwargs) + success_status = 'The enrolled learner profile report is being created.' + with patch('lms.djangoapps.instructor_task.api.submit_calculate_students_features_csv') \ + as mock_task_endpoint: + CourseFinanceAdminRole(self.course.id).add_users(self.instructor) + response = self.client.post(url, {}) + self.assertContains(response, success_status) - # assert that if the integrity signature is enabled, the verification - # status is not included as a query feature - args = mock_task_endpoint.call_args.args - self.assertEqual(len(args), 3) - query_features = args[2] - if not toggle_value: - self.assertIn('verification_status', query_features) - else: - self.assertNotIn('verification_status', query_features) + # assert that if the integrity signature is enabled, the verification + # status is not included as a query feature + args = mock_task_endpoint.call_args.args + self.assertEqual(len(args), 3) + query_features = args[2] + if not enable_integrity_signature: + self.assertIn('verification_status', query_features) + else: + self.assertNotIn('verification_status', query_features) def test_get_ora2_responses_success(self): url = reverse('export_ora2_data', kwargs={'course_id': str(self.course.id)}) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 81fef7e8f5..67cc72b68b 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -102,7 +102,6 @@ from lms.djangoapps.instructor_analytics import basic as instructor_analytics_ba from lms.djangoapps.instructor_task import api as task_api from lms.djangoapps.instructor_task.api_helper import AlreadyRunningError, QueueConnectionError from lms.djangoapps.instructor_task.models import ReportStore -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, is_course_cohorted from openedx.core.djangoapps.course_groups.models import CourseUserGroup @@ -1461,7 +1460,7 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red query_features.append('team') query_features_names['team'] = _('Team') - if is_integrity_signature_enabled(course_key): + if settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'): if 'verification_status' in query_features: query_features.remove('verification_status') query_features_names.pop('verification_status') diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py index 41c65f7f84..c0d51f623a 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py @@ -2080,7 +2080,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): 'failed': 0, 'skipped': 2 } - with self.assertNumQueries(83): + with self.assertNumQueries(76): self.assertCertificatesGenerated(task_input, expected_results) @ddt.data( diff --git a/lms/envs/common.py b/lms/envs/common.py index 8bac719afb..21cb9dc6e8 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -948,6 +948,18 @@ FEATURES = { # .. toggle_target_removal_date: 2021-10-01 # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MICROBA-1405' 'ENABLE_V2_CERT_DISPLAY_SETTINGS': False, + + # .. toggle_name: FEATURES['ENABLE_INTEGRITY_SIGNATURE'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Whether to replace ID verification course/certificate requirement + # with an in-course Honor Code agreement + # (https://github.com/edx/edx-name-affirmation) + # .. toggle_use_cases: open_edx + # .. toggle_creation_date: 2022-02-15 + # .. toggle_target_removal_date: None + # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1348' + 'ENABLE_INTEGRITY_SIGNATURE': False, } # Specifies extra XBlock fields that should available when requested via the Course Blocks API diff --git a/openedx/core/djangoapps/agreements/tests/test_views.py b/openedx/core/djangoapps/agreements/tests/test_views.py index fb6c489a3a..7056f7bf5a 100644 --- a/openedx/core/djangoapps/agreements/tests/test_views.py +++ b/openedx/core/djangoapps/agreements/tests/test_views.py @@ -3,11 +3,12 @@ Tests for agreements views """ from datetime import datetime, timedelta +from unittest.mock import patch +from django.conf import settings from django.urls import reverse from rest_framework.test import APITestCase from rest_framework import status -from edx_toggles.toggles.testutils import override_waffle_flag from freezegun import freeze_time from common.djangoapps.student.tests.factories import UserFactory, AdminFactory @@ -16,14 +17,13 @@ from openedx.core.djangoapps.agreements.api import ( create_integrity_signature, get_integrity_signatures_for_course, ) -from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE from openedx.core.djangolib.testing.utils import skip_unless_lms from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order @skip_unless_lms -@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True) +@patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True}) class IntegritySignatureViewTests(APITestCase, ModuleStoreTestCase): """ Tests for the Integrity Signature View @@ -157,7 +157,7 @@ class IntegritySignatureViewTests(APITestCase, ModuleStoreTestCase): ) self._assert_response(response, status.HTTP_200_OK, self.user, self.course_id) - @override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=False) + @patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': False}) def test_404_for_no_waffle_flag(self): self._create_signature(self.user.username, self.course_id) response = self.client.get( @@ -209,7 +209,7 @@ class IntegritySignatureViewTests(APITestCase, ModuleStoreTestCase): self.assertEqual(len(signatures), 1) self.assertEqual(signatures[0].user.username, self.USERNAME) - @override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=False) + @patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': False}) def test_post_integrity_signature_no_waffle_flag(self): response = self.client.post( reverse( diff --git a/openedx/core/djangoapps/agreements/toggles.py b/openedx/core/djangoapps/agreements/toggles.py deleted file mode 100644 index 571435470f..0000000000 --- a/openedx/core/djangoapps/agreements/toggles.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Toggles for the Agreements app -""" - -from opaque_keys.edx.keys import CourseKey - -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag - - -# .. toggle_name: agreements.enable_integrity_signature -# .. toggle_implementation: CourseWaffleFlag -# .. toggle_default: False -# .. toggle_description: Supports rollout of the integrity signature feature -# .. toggle_use_cases: temporary, open_edx -# .. toggle_creation_date: 2021-05-07 -# .. toggle_target_removal_date: None -# .. toggle_warnings: None -# .. toggle_tickets: MST-786 - -ENABLE_INTEGRITY_SIGNATURE = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - 'agreements', 'enable_integrity_signature', __name__, -) - - -def is_integrity_signature_enabled(course_key): - if isinstance(course_key, str): - course_key = CourseKey.from_string(course_key) - return ENABLE_INTEGRITY_SIGNATURE.is_enabled(course_key) diff --git a/openedx/core/djangoapps/agreements/views.py b/openedx/core/djangoapps/agreements/views.py index 1f4b3c6828..26eb6c4489 100644 --- a/openedx/core/djangoapps/agreements/views.py +++ b/openedx/core/djangoapps/agreements/views.py @@ -2,6 +2,7 @@ Views served by the Agreements app """ +from django.conf import settings from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from rest_framework import status from rest_framework.views import APIView @@ -17,7 +18,6 @@ from openedx.core.djangoapps.agreements.api import ( get_integrity_signature, ) from openedx.core.djangoapps.agreements.serializers import IntegritySignatureSerializer -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled def is_user_course_or_global_staff(user, course_id): @@ -67,8 +67,7 @@ class IntegritySignatureView(AuthenticatedAPIView): If a username is not given, it should default to the requesting user (or masqueraded user). Only staff should be able to access this endpoint for other users. """ - # check that waffle flag is enabled - if not is_integrity_signature_enabled(CourseKey.from_string(course_id)): + if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'): return Response( status=status.HTTP_404_NOT_FOUND, ) @@ -111,8 +110,7 @@ class IntegritySignatureView(AuthenticatedAPIView): created_at: "2021-04-23T18:25:43.511Z" } """ - # check that waffle flag is enabled - if not is_integrity_signature_enabled(CourseKey.from_string(course_id)): + if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'): return Response( status=status.HTTP_404_NOT_FOUND, ) diff --git a/openedx/core/djangoapps/courseware_api/tests/test_views.py b/openedx/core/djangoapps/courseware_api/tests/test_views.py index db7ba2e0e3..1cd484b31e 100644 --- a/openedx/core/djangoapps/courseware_api/tests/test_views.py +++ b/openedx/core/djangoapps/courseware_api/tests/test_views.py @@ -43,7 +43,6 @@ from common.djangoapps.student.models import ( from common.djangoapps.student.roles import CourseInstructorRole from common.djangoapps.student.tests.factories import CourseEnrollmentCelebrationFactory, UserFactory from openedx.core.djangoapps.agreements.api import create_integrity_signature -from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE User = get_user_model() @@ -362,7 +361,7 @@ class CourseApiTestViews(BaseCoursewareTests, MasqueradeMixin): ('audit', True, False, False), ) @ddt.unpack - @override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, True) + @mock.patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True}) def test_user_needs_integrity_signature( self, enrollment_mode, is_staff, has_integrity_signature, needs_integrity_signature, ): @@ -398,7 +397,7 @@ class CourseApiTestViews(BaseCoursewareTests, MasqueradeMixin): (3, True), ) @ddt.unpack - @override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, True) + @mock.patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True}) def test_course_staff_masquerade(self, masquerade_group_id, needs_signature): self.user.is_staff = True self.user.save() diff --git a/openedx/core/djangoapps/courseware_api/views.py b/openedx/core/djangoapps/courseware_api/views.py index f0b78b0e19..30850a2eba 100644 --- a/openedx/core/djangoapps/courseware_api/views.py +++ b/openedx/core/djangoapps/courseware_api/views.py @@ -3,6 +3,7 @@ Course API Views """ from completion.exceptions import UnavailableCompletionData from completion.utilities import get_key_to_last_completed_block +from django.conf import settings from django.urls import reverse from django.utils.translation import gettext as _ from edx_django_utils.cache import TieredCache @@ -48,7 +49,6 @@ from lms.djangoapps.courseware.views.views import get_cert_data from lms.djangoapps.grades.api import CourseGradeFactory from lms.djangoapps.verify_student.services import IDVerificationService from openedx.core.djangoapps.agreements.api import get_integrity_signature -from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled as integrity_signature_toggle from openedx.core.djangoapps.courseware_api.utils import get_celebrations_dict from openedx.core.djangoapps.programs.utils import ProgramProgressMeter from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser @@ -319,15 +319,18 @@ class CoursewareMeta: @property def is_integrity_signature_enabled(self): """ - Course waffle flag for the integrity signature feature. + Django setting for the integrity signature feature. """ - return integrity_signature_toggle(self.course_key) + return settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE', False) @property def user_needs_integrity_signature(self): """ Boolean describing whether the user needs to sign the integrity agreement for a course. """ + if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'): + return False + integrity_signature_required = ( self.enrollment_object # Master's enrollments are excluded here as honor code is handled separately @@ -342,13 +345,11 @@ class CoursewareMeta: self.course_masquerade ) - if ( - integrity_signature_toggle(self.course_key) - and integrity_signature_required - ): + if integrity_signature_required: signature = get_integrity_signature(self.effective_user.username, str(self.course_key)) if not signature: return True + return False @property