Merge pull request #29922 from openedx/bseverino/integrity-signature-setting
[MST-1348] Replace integrity signature flag with django setting
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']),
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}, '
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user