From 681c2f1d6ca956f6f6eae96d69cc8d877daa9eef Mon Sep 17 00:00:00 2001
From: alangsto <46360176+alangsto@users.noreply.github.com>
Date: Mon, 4 Apr 2022 08:11:40 -0700
Subject: [PATCH] chore: remove IDV references for proctoring and
student/instructor display (#30146)
Places where IDV is referenced for blocking proctoring or displaying the IDV status to students and instructors should be removed. This includes the use of the ENABLE_INTEGRITY_SIGNATURE setting, which was used in part to deprecate IDV in those specified places. Other areas where ENABLE_INTEGRITY_SIGNATURE is used (such as for showing/hiding the integrity signature, for the verification deadline, or for certs) shall be left as is due to deprecation concerns.
---
common/djangoapps/course_modes/helpers.py | 31 +-
.../course_modes/tests/test_models.py | 74 +--
common/djangoapps/student/tests/test_email.py | 17 +-
.../student/tests/test_verification_status.py | 469 ------------------
common/djangoapps/student/views/dashboard.py | 6 -
common/lib/xmodule/xmodule/seq_module.py | 11 -
.../proctoringrequirements/email/body.html | 17 -
.../proctoringrequirements/email/body.txt | 4 -
lms/djangoapps/instructor/tests/test_api.py | 40 +-
lms/djangoapps/instructor/views/api.py | 9 +-
lms/templates/dashboard.html | 17 +-
.../_dashboard_certificate_information.html | 8 +-
.../dashboard/_dashboard_course_listing.html | 47 +-
.../_dashboard_status_verification.html | 57 ---
requirements/edx/base.txt | 2 +-
requirements/edx/development.txt | 2 +-
requirements/edx/testing.txt | 2 +-
themes/edx.org/lms/templates/dashboard.html | 18 +-
18 files changed, 51 insertions(+), 780 deletions(-)
delete mode 100644 common/djangoapps/student/tests/test_verification_status.py
delete mode 100644 lms/templates/dashboard/_dashboard_status_verification.html
diff --git a/common/djangoapps/course_modes/helpers.py b/common/djangoapps/course_modes/helpers.py
index b56f1f71df..c9a7e0926e 100644
--- a/common/djangoapps/course_modes/helpers.py
+++ b/common/djangoapps/course_modes/helpers.py
@@ -10,7 +10,6 @@ from requests.exceptions import ConnectionError, Timeout # pylint: disable=rede
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.commerce.utils import ecommerce_api_client
DISPLAY_VERIFIED = "verified"
@@ -21,7 +20,7 @@ DISPLAY_PROFESSIONAL = "professional"
LOGGER = logging.getLogger(__name__)
-def enrollment_mode_display(mode, verification_status, course_id):
+def enrollment_mode_display(mode, course_id):
""" Select appropriate display strings and CSS classes.
Uses mode and verification status to select appropriate display strings and CSS classes
@@ -38,19 +37,13 @@ def enrollment_mode_display(mode, verification_status, course_id):
image_alt = ''
enrollment_title = ''
enrollment_value = ''
- display_mode = _enrollment_mode_display(mode, verification_status, course_id)
+ display_mode = _enrollment_mode_display(mode, course_id)
if display_mode == DISPLAY_VERIFIED:
- 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
- image_alt = _("ID Verified Ribbon/Badge")
- elif verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED]:
- enrollment_title = _("Your verification is pending")
- enrollment_value = _("Verified: Pending Verification")
- show_image = True
- image_alt = _("ID verification pending")
+ enrollment_title = _("You're enrolled as a verified student")
+ enrollment_value = _("Verified")
+ show_image = True
+ image_alt = _("ID Verified Ribbon/Badge")
elif display_mode == DISPLAY_HONOR:
enrollment_title = _("You're enrolled as an honor code student")
enrollment_value = _("Honor Code")
@@ -67,7 +60,7 @@ def enrollment_mode_display(mode, verification_status, course_id):
}
-def _enrollment_mode_display(enrollment_mode, verification_status, course_id):
+def _enrollment_mode_display(enrollment_mode, course_id):
"""Checking enrollment mode and status and returns the display mode
Args:
enrollment_mode (str): enrollment mode.
@@ -79,15 +72,9 @@ def _enrollment_mode_display(enrollment_mode, verification_status, course_id):
course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(course_id)]
if enrollment_mode == CourseMode.VERIFIED:
- if (
- 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
- elif DISPLAY_HONOR in course_mode_slugs:
+ display_mode = DISPLAY_VERIFIED
+ if DISPLAY_HONOR in course_mode_slugs:
display_mode = DISPLAY_HONOR
- else:
- display_mode = DISPLAY_AUDIT
elif enrollment_mode in [CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE]:
display_mode = DISPLAY_PROFESSIONAL
else:
diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py
index 14faaf907d..267b0259e1 100644
--- a/common/djangoapps/course_modes/tests/test_models.py
+++ b/common/djangoapps/course_modes/tests/test_models.py
@@ -11,7 +11,6 @@ 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
@@ -342,49 +341,15 @@ class CourseModeModelTest(TestCase):
assert exc.messages == ['Professional education modes are not allowed to have expiration_datetime set.']
@ddt.data(
- ("verified", "verify_need_to_verify", True),
- ("verified", "verify_submitted", True),
- ("verified", "verify_approved", True),
- ("verified", 'dummy', True),
- ("verified", None, True),
- ('honor', None, True),
- ('honor', 'dummy', True),
- ('audit', None, True),
- ('professional', None, True),
- ('no-id-professional', None, True),
- ('no-id-professional', 'dummy', True),
- ("verified", "verify_need_to_verify", False),
- ("verified", "verify_submitted", False),
- ("verified", "verify_approved", False),
- ("verified", 'dummy', False),
- ("verified", None, False),
- ('honor', None, False),
- ('honor', 'dummy', False),
- ('audit', None, False),
- ('professional', None, False),
- ('no-id-professional', None, False),
- ('no-id-professional', 'dummy', False)
+ "verified",
+ "honor",
+ "audit",
+ "professional",
+ "no-id-professional",
)
- @ddt.unpack
- def test_enrollment_mode_display(self, mode, verification_status, enable_integrity_signature):
-
- 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_integrity_signature)
- assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
- 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_integrity_signature)
- elif mode == "honor":
- assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
- 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_integrity_signature)
- elif mode == "professional":
- assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
- self._enrollment_display_modes_dicts(mode, mode, enable_integrity_signature)
+ def test_enrollment_mode_display(self, mode):
+ assert enrollment_mode_display(mode, self.course_key) == \
+ self._enrollment_display_modes_dicts(mode)
@ddt.data(
(['honor', 'verified', 'credit'], ['honor', 'verified']),
@@ -408,30 +373,23 @@ class CourseModeModelTest(TestCase):
all_modes = CourseMode.modes_for_course_dict(self.course_key, only_selectable=False)
self.assertCountEqual(list(all_modes.keys()), available_modes)
- def _enrollment_display_modes_dicts(self, mode, dict_type, enable_flag):
+ def _enrollment_display_modes_dicts(self, mode):
"""
Helper function to generate the enrollment display mode dict.
"""
dict_keys = ['enrollment_title', 'enrollment_value', 'show_image', 'image_alt', 'display_mode']
display_values = {
- "verify_need_to_verify": ["Your verification is pending", "Verified: Pending Verification", True,
- 'ID verification pending', 'verified'],
- "verify_approved": ["You're enrolled as a verified student", "Verified", True, 'ID Verified Ribbon/Badge',
- 'verified'],
- "verify_none": ["", "", False, '', 'audit'],
+ "verified": ["You're enrolled as a verified student", "Verified", True, 'ID Verified Ribbon/Badge',
+ 'verified'],
"honor": ["You're enrolled as an honor code student", "Honor Code", False, '', 'honor'],
"audit": ["", "", False, '', 'audit'],
"professional": ["You're enrolled as a professional education student", "Professional Ed", False, '',
- 'professional']
+ 'professional'],
+ "no-id-professional": ["You're enrolled as a professional education student", "Professional Ed", False, '',
+ 'professional'],
}
- if mode == 'verified' and enable_flag:
- return dict(list(zip(dict_keys, display_values.get('verify_approved'))))
- elif dict_type in ['verify_need_to_verify', 'verify_submitted']:
- return dict(list(zip(dict_keys, display_values.get('verify_need_to_verify'))))
- elif dict_type is None or dict_type == 'dummy':
- return dict(list(zip(dict_keys, display_values.get('verify_none'))))
- else:
- return dict(list(zip(dict_keys, display_values.get(dict_type))))
+
+ return dict(list(zip(dict_keys, display_values.get(mode))))
def test_expiration_datetime_explicitly_set(self):
""" Verify that setting the expiration_date property sets the explicit flag. """
diff --git a/common/djangoapps/student/tests/test_email.py b/common/djangoapps/student/tests/test_email.py
index dde8a0daad..4c1774251e 100644
--- a/common/djangoapps/student/tests/test_email.py
+++ b/common/djangoapps/student/tests/test_email.py
@@ -29,7 +29,6 @@ from common.djangoapps.student.views import (
)
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.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
@@ -237,7 +236,6 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas
send_proctoring_requirements_email(context)
self._assert_email()
- @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',
@@ -259,13 +257,10 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas
assert message.subject == f"Proctoring requirements for {self.course.display_name}"
- appears, does_not_appear = self._get_fragments()
+ appears = self._get_fragments()
for fragment in appears:
self.assertIn(fragment, text)
self.assertIn(fragment, html)
- for fragment in does_not_appear:
- self.assertNotIn(fragment, text)
- self.assertNotIn(fragment, html)
def _get_fragments(self):
"""
@@ -273,7 +268,6 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas
"""
course_module = modulestore().get_course(self.course.id)
proctoring_provider = capwords(course_module.proctoring_provider.replace('_', ' '))
- id_verification_url = IDVerificationService.get_verify_location()
fragments = [
(
"You are enrolled in {} at {}. This course contains proctored exams.".format(
@@ -292,14 +286,7 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas
),
settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('faq', ''),
]
- idv_fragments = [
- escape("Before taking a graded proctored exam, you must have approved ID verification photos."),
- id_verification_url,
- ]
- if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
- fragments.extend(idv_fragments)
- return (fragments, [])
- return (fragments, idv_fragments)
+ return fragments
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS")
diff --git a/common/djangoapps/student/tests/test_verification_status.py b/common/djangoapps/student/tests/test_verification_status.py
deleted file mode 100644
index 372e4724be..0000000000
--- a/common/djangoapps/student/tests/test_verification_status.py
+++ /dev/null
@@ -1,469 +0,0 @@
-"""Tests for per-course verification status on the dashboard. """
-
-
-import unittest
-from datetime import datetime, timedelta
-from unittest.mock import patch
-
-import ddt
-import six
-from django.conf import settings
-from django.test import override_settings
-from django.urls import reverse
-from django.utils.timezone import now
-from pytz import UTC
-
-from common.djangoapps.course_modes.tests.factories import CourseModeFactory
-from common.djangoapps.student.helpers import (
- VERIFY_STATUS_APPROVED,
- VERIFY_STATUS_MISSED_DEADLINE,
- VERIFY_STATUS_NEED_TO_REVERIFY,
- VERIFY_STATUS_NEED_TO_VERIFY,
- VERIFY_STATUS_RESUBMITTED,
- VERIFY_STATUS_SUBMITTED
-)
-from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
-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
-
-
-@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
-@override_settings(PLATFORM_NAME='edX')
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-@ddt.ddt
-class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
- """Tests for per-course verification status on the dashboard. """
-
- PAST = 'past'
- FUTURE = 'future'
- DATES = {
- PAST: datetime.now(UTC) - timedelta(days=5),
- FUTURE: datetime.now(UTC) + timedelta(days=5),
- None: None,
- }
-
- URLCONF_MODULES = ['lms.djangoapps.verify_student.urls']
-
- def setUp(self):
- # Invoke UrlResetMixin
- super().setUp()
-
- self.user = UserFactory(password="edx")
- self.course = CourseFactory.create()
- success = self.client.login(username=self.user.username, password="edx")
- assert success, 'Did not log in successfully'
- self.dashboard_url = reverse('dashboard')
-
- def test_enrolled_as_non_verified(self):
- self._setup_mode_and_enrollment(None, "audit")
-
- # Expect that the course appears on the dashboard
- # without any verification messaging
- self._assert_course_verification_status(None)
-
- def test_no_verified_mode_available(self):
- # Enroll the student in a verified mode, but don't
- # create any verified course mode.
- # This won't happen unless someone deletes a course mode,
- # but if so, make sure we handle it gracefully.
- CourseEnrollmentFactory(
- course_id=self.course.id,
- user=self.user,
- mode="verified"
- )
-
- # Continue to show the student as needing to verify.
- # The student is enrolled as verified, so we might as well let them
- # complete verification. We'd need to change their enrollment mode
- # anyway to ensure that the student is issued the correct kind of certificate.
- self._assert_course_verification_status(VERIFY_STATUS_NEED_TO_VERIFY)
-
- def test_need_to_verify_no_expiration(self):
- self._setup_mode_and_enrollment(None, "verified")
-
- # Since the student has not submitted a photo verification,
- # the student should see a "need to verify" message
- self._assert_course_verification_status(VERIFY_STATUS_NEED_TO_VERIFY)
-
- # Start the photo verification process, but do not submit
- # Since we haven't submitted the verification, we should still
- # see the "need to verify" message
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- self._assert_course_verification_status(VERIFY_STATUS_NEED_TO_VERIFY)
-
- # Upload images, but don't submit to the verification service
- # We should still need to verify
- attempt.mark_ready()
- self._assert_course_verification_status(VERIFY_STATUS_NEED_TO_VERIFY)
-
- def test_need_to_verify_expiration(self):
- self._setup_mode_and_enrollment(self.DATES[self.FUTURE], "verified")
- response = self.client.get(self.dashboard_url)
- self.assertContains(response, self.BANNER_ALT_MESSAGES[VERIFY_STATUS_NEED_TO_VERIFY])
- self.assertContains(response, "You only have 4 days left to verify for this course.")
-
- @ddt.data(None, FUTURE)
- def test_waiting_approval(self, expiration):
- self._setup_mode_and_enrollment(self.DATES[expiration], "verified")
-
- # The student has submitted a photo verification
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
-
- # Now the student should see a "verification submitted" message
- self._assert_course_verification_status(VERIFY_STATUS_SUBMITTED)
-
- @ddt.data(None, FUTURE)
- def test_fully_verified(self, expiration):
- self._setup_mode_and_enrollment(self.DATES[expiration], "verified")
-
- # The student has an approved verification
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
- attempt.approve()
-
- # Expect that the successfully verified message is shown
- self._assert_course_verification_status(VERIFY_STATUS_APPROVED)
-
- # Check that the "verification good until" date is displayed
- response = self.client.get(self.dashboard_url)
- self.assertContains(response, attempt.expiration_datetime.strftime("%m/%d/%Y"))
-
- @patch("lms.djangoapps.verify_student.services.is_verification_expiring_soon")
- def test_verify_resubmit_button_on_dashboard(self, mock_expiry):
- mock_expiry.return_value = True
- SoftwareSecurePhotoVerification.objects.create(
- user=self.user,
- status='approved',
- expiration_date=now() + timedelta(days=1)
- )
- response = self.client.get(self.dashboard_url)
- self.assertContains(response, "Resubmit Verification")
-
- mock_expiry.return_value = False
- response = self.client.get(self.dashboard_url)
- self.assertNotContains(response, "Resubmit Verification")
-
- def test_missed_verification_deadline(self):
- # Expiration date in the past
- self._setup_mode_and_enrollment(self.DATES[self.PAST], "verified")
-
- # The student does NOT have an approved verification
- # so the status should show that the student missed the deadline.
- self._assert_course_verification_status(VERIFY_STATUS_MISSED_DEADLINE)
-
- def test_missed_verification_deadline_verification_was_expired(self):
- # Expiration date in the past
- self._setup_mode_and_enrollment(self.DATES[self.PAST], "verified")
-
- # Create a verification, but the expiration date of the verification
- # occurred before the deadline.
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
- attempt.approve()
- attempt.expiration_date = self.DATES[self.PAST] - timedelta(days=900)
- attempt.save()
-
- # The student didn't have an approved verification at the deadline,
- # so we should show that the student missed the deadline.
- self._assert_course_verification_status(VERIFY_STATUS_MISSED_DEADLINE)
-
- def test_missed_verification_deadline_but_later_verified(self):
- # Expiration date in the past
- self._setup_mode_and_enrollment(self.DATES[self.PAST], "verified")
-
- # Successfully verify, but after the deadline has already passed
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
- attempt.approve()
- attempt.expiration_date = self.DATES[self.PAST] - timedelta(days=900)
- attempt.save()
-
- # The student didn't have an approved verification at the deadline,
- # so we should show that the student missed the deadline.
- self._assert_course_verification_status(VERIFY_STATUS_MISSED_DEADLINE)
-
- def test_verification_denied(self):
- # Expiration date in the future
- self._setup_mode_and_enrollment(self.DATES[self.FUTURE], "verified")
-
- # Create a verification with the specified status
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
- attempt.deny("Not valid!")
-
- # Since this is not a status we handle, don't display any
- # messaging relating to verification
- self._assert_course_verification_status(None)
-
- def test_verification_error(self):
- # Expiration date in the future
- self._setup_mode_and_enrollment(self.DATES[self.FUTURE], "verified")
-
- # Create a verification with the specified status
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.status = "must_retry"
- attempt.system_error("Error!")
-
- # Since this is not a status we handle, don't display any
- # messaging relating to verification
- self._assert_course_verification_status(None)
-
- @override_settings(VERIFY_STUDENT={"DAYS_GOOD_FOR": 5, "EXPIRING_SOON_WINDOW": 10})
- def test_verification_will_expire_by_deadline(self):
- # Expiration date in the future
- self._setup_mode_and_enrollment(self.DATES[self.FUTURE], "verified")
-
- # Create a verification attempt that:
- # 1) Is current (submitted in the last year)
- # 2) Will expire by the deadline for the course
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
- attempt.approve()
- attempt.save()
-
- # Verify that learner can submit photos if verification is set to expire soon.
- self._assert_course_verification_status(VERIFY_STATUS_NEED_TO_REVERIFY)
-
- @override_settings(VERIFY_STUDENT={"DAYS_GOOD_FOR": 5, "EXPIRING_SOON_WINDOW": 10})
- def test_reverification_submitted_with_current_approved_verificaiton(self):
- # Expiration date in the future
- self._setup_mode_and_enrollment(self.DATES[self.FUTURE], "verified")
-
- # Create a verification attempt that is approved but expiring soon
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
- attempt.approve()
- attempt.save()
-
- # Verify that learner can submit photos if verification is set to expire soon.
- self._assert_course_verification_status(VERIFY_STATUS_NEED_TO_REVERIFY)
-
- # Submit photos for reverification
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
-
- # Expect that learner has submitted photos for reverfication and their
- # previous verification is set to expired soon.
- self._assert_course_verification_status(VERIFY_STATUS_RESUBMITTED)
-
- def test_verification_occurred_after_deadline(self):
- # Expiration date in the past
- self._setup_mode_and_enrollment(self.DATES[self.PAST], "verified")
-
- # The deadline has passed, and we've asked the student
- # to reverify (through the support team).
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
-
- # Expect that the user's displayed enrollment mode is verified.
- self._assert_course_verification_status(VERIFY_STATUS_APPROVED)
-
- def test_with_two_verifications(self):
- # checking if a user has two verification and but most recent verification course deadline is expired
-
- self._setup_mode_and_enrollment(self.DATES[self.FUTURE], "verified")
-
- # The student has an approved verification
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
- attempt.approve()
- # Making created at to previous date to differentiate with 2nd attempt.
- attempt.created_at = datetime.now(UTC) - timedelta(days=1)
- attempt.save()
-
- # Expect that the successfully verified message is shown
- self._assert_course_verification_status(VERIFY_STATUS_APPROVED)
-
- # Check that the "verification good until" date is displayed
- response = self.client.get(self.dashboard_url)
- self.assertContains(response, attempt.expiration_datetime.strftime("%m/%d/%Y"))
-
- # Adding another verification with different course.
- # Its created_at is greater than course deadline.
- course2 = CourseFactory.create()
- CourseModeFactory.create(
- course_id=course2.id,
- mode_slug="verified",
- expiration_datetime=self.DATES[self.PAST]
- )
- CourseEnrollmentFactory(
- course_id=course2.id,
- user=self.user,
- mode="verified"
- )
-
- # The student has an approved verification
- attempt2 = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt2.mark_ready()
- attempt2.submit()
- attempt2.approve()
- attempt2.save()
-
- # Mark the attemp2 as approved so its date will appear on dasboard.
- self._assert_course_verification_status(VERIFY_STATUS_APPROVED)
- response2 = self.client.get(self.dashboard_url)
- self.assertContains(response2, attempt2.expiration_datetime.strftime("%m/%d/%Y"))
- self.assertContains(response2, attempt2.expiration_datetime.strftime("%m/%d/%Y"), count=2)
-
- @patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True})
- @ddt.data(
- None,
- 'past',
- 'future'
- )
- def test_verify_message_idv_disabled(self, deadline_key):
- if deadline_key:
- self._setup_mode_and_enrollment(self.DATES[deadline_key], "verified")
- else:
- self._setup_mode_and_enrollment(None, "verified")
-
- self._assert_course_verification_status(None, "verified")
-
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- self._assert_course_verification_status(None, "verified")
- attempt.mark_ready()
- self._assert_course_verification_status(None, "verified")
- attempt.submit()
- self._assert_course_verification_status(None, "verified")
- attempt.approve()
- self._assert_course_verification_status(None, "verified")
- attempt.expiration_date = self.DATES[self.PAST] - timedelta(days=900)
- attempt.save()
- self._assert_course_verification_status(None, "verified")
-
- @ddt.data(True, False)
- def test_integrity_disables_sidebar(self, enable_integrity_signature):
- self._setup_mode_and_enrollment(None, "verified")
-
- #no sidebar when no IDV yet
- response = self.client.get(self.dashboard_url)
- self.assertNotContains(response, "profile-sidebar")
-
- # The student has an approved verification
- attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
- attempt.mark_ready()
- attempt.submit()
- attempt.approve()
-
- # 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 enable_integrity_signature:
- self.assertNotContains(response, "profile-sidebar")
- else:
- self.assertContains(response, "profile-sidebar")
-
- def _setup_mode_and_enrollment(self, deadline, enrollment_mode):
- """Create a course mode and enrollment.
-
- Arguments:
- deadline (datetime): The deadline for submitting your verification.
- enrollment_mode (str): The mode of the enrollment.
-
- """
- CourseModeFactory.create(
- course_id=self.course.id,
- mode_slug="verified",
- expiration_datetime=deadline
- )
- CourseEnrollmentFactory(
- course_id=self.course.id,
- user=self.user,
- mode=enrollment_mode
- )
- VerificationDeadline.set_deadline(self.course.id, deadline)
-
- BANNER_ALT_MESSAGES = {
- VERIFY_STATUS_NEED_TO_VERIFY: "ID verification pending",
- VERIFY_STATUS_SUBMITTED: "ID verification pending",
- VERIFY_STATUS_APPROVED: "ID Verified Ribbon/Badge",
- }
-
- NOTIFICATION_MESSAGES = {
- VERIFY_STATUS_NEED_TO_VERIFY: [
- "You still need to verify for this course.",
- "Verification not yet complete"
- ],
- VERIFY_STATUS_SUBMITTED: ["You have submitted your verification information."],
- VERIFY_STATUS_RESUBMITTED: ["You have submitted your reverification information."],
- VERIFY_STATUS_APPROVED: ["You have successfully verified your ID with edX"],
- VERIFY_STATUS_NEED_TO_REVERIFY: ["Your current verification will expire soon."]
- }
-
- MODE_CLASSES = {
- None: "audit",
- VERIFY_STATUS_NEED_TO_VERIFY: "verified",
- VERIFY_STATUS_SUBMITTED: "verified",
- VERIFY_STATUS_APPROVED: "verified",
- VERIFY_STATUS_MISSED_DEADLINE: "audit",
- VERIFY_STATUS_NEED_TO_REVERIFY: "audit",
- VERIFY_STATUS_RESUBMITTED: "audit"
- }
-
- def _assert_course_verification_status(self, status, enrollment_mode=None):
- """Check whether the specified verification status is shown on the dashboard.
-
- Arguments:
- status (str): One of the verification status constants.
- If None, check that *none* of the statuses are displayed.
-
- Raises:
- AssertionError
-
- """
- response = self.client.get(self.dashboard_url)
-
- # Sanity check: verify that the course is on the page
- self.assertContains(response, str(self.course.id))
-
- # Verify that the correct banner is rendered on the dashboard
- alt_text = self.BANNER_ALT_MESSAGES.get(status)
- if alt_text:
- self.assertContains(response, alt_text)
-
- mode = enrollment_mode if enrollment_mode else self.MODE_CLASSES[status]
-
- # Verify that the correct banner color is rendered
- self.assertContains(
- response,
- f"
{% trans "Thank you," as tmsg %}{{ tmsg | force_escape }}
diff --git a/common/templates/student/edx_ace/proctoringrequirements/email/body.txt b/common/templates/student/edx_ace/proctoringrequirements/email/body.txt
index 0c45632fea..74b0d00d25 100644
--- a/common/templates/student/edx_ace/proctoringrequirements/email/body.txt
+++ b/common/templates/student/edx_ace/proctoringrequirements/email/body.txt
@@ -8,10 +8,6 @@
{% blocktrans %}To view proctoring instructions and requirements, please visit: {{ proctoring_requirements_url }}{% endblocktrans %}
-{% if idv_required %}
-{% blocktrans %}Before taking a graded proctored exam, you must have approved ID verification photos. You can submit ID verification photos here: {{ id_verification_url }}{% endblocktrans %}
-{% endif %}
-
{% trans "Thank you," %}
{% blocktrans %}The {{ platform_name }} Team {% endblocktrans %}
diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index 77bef6407a..e4955524d6 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -2808,32 +2808,24 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
response = self.client.post(url, {})
self.assertContains(response, success_status)
- @ddt.data(
- True,
- False
- )
@valid_problem_location
- 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)
+ def test_idv_retirement_student_features_report(self):
+ 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 enable_integrity_signature:
- 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]
+
+ 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 81b9b6b487..b59b425ceb 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -1425,8 +1425,7 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red
query_features = [
'id', 'username', 'name', 'email', 'language', 'location',
'year_of_birth', 'gender', 'level_of_education', 'mailing_address',
- 'goals', 'enrollment_mode', 'verification_status',
- 'last_login', 'date_joined', 'external_user_key'
+ 'goals', 'enrollment_mode', 'last_login', 'date_joined', 'external_user_key'
]
# Provide human-friendly and translatable names for these features. These names
@@ -1445,7 +1444,6 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red
'mailing_address': _('Mailing Address'),
'goals': _('Goals'),
'enrollment_mode': _('Enrollment Mode'),
- 'verification_status': _('Verification Status'),
'last_login': _('Last Login'),
'date_joined': _('Date Joined'),
'external_user_key': _('External User Key'),
@@ -1460,11 +1458,6 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red
query_features.append('team')
query_features_names['team'] = _('Team')
- if settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
- if 'verification_status' in query_features:
- query_features.remove('verification_status')
- query_features_names.pop('verification_status')
-
# For compatibility reasons, city and country should always appear last.
query_features.append('city')
query_features_names['city'] = _('City')
diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html
index 5c4443c6bb..59c5a77e58 100644
--- a/lms/templates/dashboard.html
+++ b/lms/templates/dashboard.html
@@ -214,13 +214,12 @@ from common.djangoapps.student.models import CourseEnrollment
course_mode_info = all_course_modes.get(session_id)
is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid)
is_course_voucher_refundable = (session_id in enrolled_courses_voucher_refundable)
- course_verification_status = verification_status_by_course.get(session_id, {})
course_requirements = courses_requirements_not_met.get(session_id)
related_programs = inverted_programs.get(six.text_type(entitlement.course_uuid if is_unfulfilled_entitlement else session_id))
show_consent_link = (session_id in consent_required_courses)
resume_button_url = resume_button_urls[dashboard_index]
%>
- <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_refund_entitlement=can_refund_entitlement, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_voucher_refundable=is_course_voucher_refundable, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name, resume_button_url=resume_button_url, partner_managed_enrollment=partner_managed_enrollment' />
+ <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_refund_entitlement=can_refund_entitlement, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_voucher_refundable=is_course_voucher_refundable, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name, resume_button_url=resume_button_url, partner_managed_enrollment=partner_managed_enrollment' />
% endfor
% if show_load_all_courses_link:
@@ -301,20 +300,6 @@ from common.djangoapps.student.models import CourseEnrollment
<%include file='dashboard/_dashboard_announcements.html' />
% endif
- % if display_sidebar_on_dashboard:
-