Merge pull request #17835 from edx/bexline/sso_id_verification
ENT-942 Implement an abstraction layer for PhotoVerification class methods
This commit is contained in:
@@ -10,10 +10,10 @@ from datetime import datetime
|
||||
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse
|
||||
from django.core.validators import ValidationError, validate_email
|
||||
from django.contrib.auth import authenticate, load_backend, login, logout
|
||||
from django.core.validators import ValidationError
|
||||
from django.contrib.auth import load_backend
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.utils import http
|
||||
@@ -35,13 +35,14 @@ from lms.djangoapps.certificates.models import ( # pylint: disable=import-error
|
||||
certificate_status_for_student
|
||||
)
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
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.certificates.api import certificates_viewable_for_course
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.theming import helpers as theming_helpers
|
||||
from openedx.core.djangoapps.theming.helpers import get_themes
|
||||
from student.models import (
|
||||
CourseEnrollment,
|
||||
LinkedInAddToProfileConfiguration,
|
||||
PasswordHistory,
|
||||
Registration,
|
||||
@@ -111,18 +112,18 @@ def check_verify_status_by_course(user, course_enrollments):
|
||||
|
||||
# Retrieve all verifications for the user, sorted in descending
|
||||
# order by submission datetime
|
||||
verifications = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
verifications = IDVerificationService.verifications_for_user(user)
|
||||
|
||||
# Check whether the user has an active or pending verification attempt
|
||||
# To avoid another database hit, we re-use the queryset we have already retrieved.
|
||||
has_active_or_pending = SoftwareSecurePhotoVerification.user_has_valid_or_pending(
|
||||
has_active_or_pending = IDVerificationService.user_has_valid_or_pending(
|
||||
user, queryset=verifications
|
||||
)
|
||||
|
||||
# Retrieve expiration_datetime of most recent approved verification
|
||||
# To avoid another database hit, we re-use the queryset we have already retrieved.
|
||||
expiration_datetime = SoftwareSecurePhotoVerification.get_expiration_datetime(user, verifications)
|
||||
verification_expiring_soon = SoftwareSecurePhotoVerification.is_verification_expiring_soon(expiration_datetime)
|
||||
expiration_datetime = IDVerificationService.get_expiration_datetime(user, verifications)
|
||||
verification_expiring_soon = is_verification_expiring_soon(expiration_datetime)
|
||||
|
||||
# Retrieve verification deadlines for the enrolled courses
|
||||
enrolled_course_keys = [enrollment.course_id for enrollment in course_enrollments]
|
||||
@@ -140,7 +141,7 @@ def check_verify_status_by_course(user, course_enrollments):
|
||||
# This could be None if the course doesn't have a deadline.
|
||||
deadline = course_deadlines.get(enrollment.course_id)
|
||||
|
||||
relevant_verification = SoftwareSecurePhotoVerification.verification_for_datetime(deadline, verifications)
|
||||
relevant_verification = verification_for_datetime(deadline, verifications)
|
||||
|
||||
# Picking the max verification datetime on each iteration only with approved status
|
||||
if relevant_verification is not None and relevant_verification.status == "approved":
|
||||
@@ -177,13 +178,12 @@ def check_verify_status_by_course(user, course_enrollments):
|
||||
)
|
||||
if status is None and not submitted:
|
||||
if deadline is None or deadline > datetime.now(UTC):
|
||||
if SoftwareSecurePhotoVerification.user_is_verified(user):
|
||||
if verification_expiring_soon:
|
||||
# The user has an active verification, but the verification
|
||||
# is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
|
||||
# Tell the student to reverify.
|
||||
status = VERIFY_STATUS_NEED_TO_REVERIFY
|
||||
else:
|
||||
if IDVerificationService.user_is_verified(user) and verification_expiring_soon:
|
||||
# The user has an active verification, but the verification
|
||||
# is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
|
||||
# Tell the student to reverify.
|
||||
status = VERIFY_STATUS_NEED_TO_REVERIFY
|
||||
elif not IDVerificationService.user_is_verified(user):
|
||||
status = VERIFY_STATUS_NEED_TO_VERIFY
|
||||
else:
|
||||
# If a user currently has an active or pending verification,
|
||||
@@ -525,7 +525,7 @@ def _cert_info(user, course_overview, cert_status):
|
||||
'can_unenroll': status not in DISABLE_UNENROLL_CERT_STATES,
|
||||
}
|
||||
|
||||
if not status == default_status and course_overview.end_of_course_survey_url is not None:
|
||||
if status != default_status and course_overview.end_of_course_survey_url is not None:
|
||||
status_dict.update({
|
||||
'show_survey_button': True,
|
||||
'survey_url': process_survey_link(course_overview.end_of_course_survey_url, user)})
|
||||
|
||||
@@ -11,10 +11,10 @@ from completion.utilities import get_key_to_last_completed_course_block
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse, reverse_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import UTC
|
||||
@@ -27,7 +27,7 @@ from courseware.access import has_access
|
||||
from edxmako.shortcuts import render_to_response, render_to_string
|
||||
from entitlements.models import CourseEntitlement
|
||||
from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification # pylint: disable=import-error
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps import monitoring_utils
|
||||
from openedx.core.djangoapps.catalog.utils import (
|
||||
get_programs,
|
||||
@@ -339,6 +339,9 @@ def is_course_blocked(request, redeemed_registration_codes, course_key):
|
||||
|
||||
|
||||
def get_verification_error_reasons_for_display(verification_error_codes):
|
||||
"""
|
||||
Returns the display text for the given verification error codes.
|
||||
"""
|
||||
verification_errors = []
|
||||
verification_error_map = {
|
||||
'photos_mismatched': _('Photos are mismatched'),
|
||||
@@ -713,7 +716,7 @@ def student_dashboard(request):
|
||||
|
||||
# Verification Attempts
|
||||
# Used to generate the "you must reverify for course x" banner
|
||||
verification_status, verification_error_codes = SoftwareSecurePhotoVerification.user_status(user)
|
||||
verification_status, verification_error_codes = IDVerificationService.user_status(user)
|
||||
verification_errors = get_verification_error_reasons_for_display(verification_error_codes)
|
||||
|
||||
# Gets data for midcourse reverifications, if any are necessary or have failed
|
||||
|
||||
@@ -21,7 +21,7 @@ from lms.djangoapps.certificates.models import (
|
||||
)
|
||||
from course_modes.models import CourseMode
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from student.models import CourseEnrollment, UserProfile
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
@@ -271,7 +271,7 @@ class XQueueCertInterface(object):
|
||||
course_grade = CourseGradeFactory().read(student, course)
|
||||
enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id)
|
||||
mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES
|
||||
user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student)
|
||||
user_is_verified = IDVerificationService.user_is_verified(student)
|
||||
cert_mode = enrollment_mode
|
||||
is_eligible_for_certificate = is_whitelisted or CourseMode.is_eligible_for_certificate(enrollment_mode)
|
||||
unverified = False
|
||||
|
||||
@@ -13,7 +13,7 @@ from lms.djangoapps.certificates.models import (
|
||||
)
|
||||
from lms.djangoapps.certificates.tasks import generate_certificate
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.certificates.api import auto_certificate_generation_enabled
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.content.course_overviews.signals import COURSE_PACING_CHANGED
|
||||
@@ -82,7 +82,7 @@ def _listen_for_id_verification_status_changed(sender, user, **kwargs): # pylin
|
||||
|
||||
user_enrollments = CourseEnrollment.enrollments_for_user(user=user)
|
||||
grade_factory = CourseGradeFactory()
|
||||
expected_verification_status, _ = SoftwareSecurePhotoVerification.user_status(user)
|
||||
expected_verification_status, _ = IDVerificationService.user_status(user)
|
||||
for enrollment in user_enrollments:
|
||||
if grade_factory.read(user=user, course=enrollment.course_overview).passed:
|
||||
if fire_ungenerated_certificate_task(user, enrollment.course_id, expected_verification_status):
|
||||
|
||||
@@ -3,7 +3,7 @@ from logging import getLogger
|
||||
|
||||
from celery_utils.persist_on_failure import LoggedPersistOnFailureTask
|
||||
from django.contrib.auth.models import User
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from .api import generate_user_certificates
|
||||
@@ -31,7 +31,7 @@ def generate_certificate(self, **kwargs):
|
||||
course_key = CourseKey.from_string(kwargs.pop('course_key'))
|
||||
expected_verification_status = kwargs.pop('expected_verification_status', None)
|
||||
if expected_verification_status:
|
||||
actual_verification_status, _ = SoftwareSecurePhotoVerification.user_status(student)
|
||||
actual_verification_status, _ = IDVerificationService.user_status(student)
|
||||
if expected_verification_status != actual_verification_status:
|
||||
raise self.retry(kwargs=original_kwargs)
|
||||
generate_user_certificates(student=student, course_key=course_key, **kwargs)
|
||||
|
||||
@@ -40,7 +40,7 @@ class GenerateUserCertificateTest(TestCase):
|
||||
generate_certificate.apply_async(kwargs=kwargs).get()
|
||||
|
||||
@patch('lms.djangoapps.certificates.tasks.generate_user_certificates')
|
||||
@patch('lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_status')
|
||||
@patch('lms.djangoapps.verify_student.services.IDVerificationService.user_status')
|
||||
def test_retry_until_verification_status_updates(self, user_status_mock, generate_user_certs_mock):
|
||||
course_key = 'course-v1:edX+CS101+2017_T2'
|
||||
student = UserFactory()
|
||||
|
||||
@@ -12,7 +12,7 @@ from opaque_keys.edx.locator import CourseLocator
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site
|
||||
from shoppingcart.processors.CyberSource2 import is_user_payment_error
|
||||
@@ -92,7 +92,7 @@ def checkout_receipt(request):
|
||||
'page_title': page_title,
|
||||
'is_payment_complete': is_payment_complete,
|
||||
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
'verified': SoftwareSecurePhotoVerification.verification_valid_or_pending(request.user).exists(),
|
||||
'verified': IDVerificationService.verification_valid_or_pending(request.user).exists(),
|
||||
'error_summary': error_summary,
|
||||
'error_text': error_text,
|
||||
'for_help_text': for_help_text,
|
||||
|
||||
@@ -19,7 +19,8 @@ from pytz import utc
|
||||
|
||||
from course_modes.models import CourseMode, get_cosmetic_verified_display_price
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
from lms.djangoapps.verify_student.models import VerificationDeadline
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.certificates.api import can_show_certificate_available_date_field
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
from openedx.features.course_experience import CourseHomeMessages, UPGRADE_DEADLINE_MESSAGE
|
||||
@@ -626,7 +627,8 @@ class VerificationDeadlineDate(DateSummary):
|
||||
@lazy
|
||||
def verification_status(self):
|
||||
"""Return the verification status for this user."""
|
||||
return SoftwareSecurePhotoVerification.user_status(self.user)[0]
|
||||
status, _ = IDVerificationService.user_status(self.user)
|
||||
return status
|
||||
|
||||
def must_retry(self):
|
||||
"""Return True if the user must re-submit verification, False otherwise."""
|
||||
|
||||
@@ -46,7 +46,7 @@ from lms.djangoapps.grades.signals.signals import SCORE_PUBLISHED
|
||||
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
|
||||
from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
|
||||
from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem
|
||||
from lms.djangoapps.verify_student.services import VerificationService
|
||||
from lms.djangoapps.verify_student.services import XBlockVerificationService
|
||||
from openedx.core.djangoapps.bookmarks.services import BookmarksService
|
||||
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
|
||||
from openedx.core.djangoapps.credit.services import CreditService
|
||||
@@ -756,7 +756,7 @@ def get_module_system_for_user(
|
||||
'fs': FSService(),
|
||||
'field-data': field_data,
|
||||
'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff),
|
||||
'verification': VerificationService(),
|
||||
'verification': XBlockVerificationService(),
|
||||
'proctoring': ProctoringService(),
|
||||
'milestones': milestones_helpers.get_service(),
|
||||
'credit': CreditService(),
|
||||
|
||||
@@ -1363,7 +1363,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
self.store.update_item(self.course, self.user.id)
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="verified")
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as user_verify:
|
||||
user_verify.return_value = True
|
||||
|
||||
@@ -1412,7 +1412,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="verified")
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as user_verify:
|
||||
user_verify.return_value = True
|
||||
|
||||
@@ -1472,7 +1472,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
certs_api.set_cert_generation_enabled(self.course.id, True)
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode=course_mode)
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as user_verify:
|
||||
user_verify.return_value = user_verified
|
||||
with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_create:
|
||||
@@ -1520,7 +1520,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
self.store.update_item(self.course, self.user.id)
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="verified")
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as user_verify:
|
||||
user_verify.return_value = True
|
||||
|
||||
@@ -1568,7 +1568,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
)
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="verified")
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as user_verify:
|
||||
user_verify.return_value = True
|
||||
|
||||
@@ -1593,7 +1593,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
)
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="verified")
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as user_verify:
|
||||
user_verify.return_value = True
|
||||
|
||||
@@ -1675,7 +1675,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
)
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="verified")
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as user_verify:
|
||||
user_verify.return_value = True
|
||||
|
||||
@@ -1723,7 +1723,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
)
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="verified")
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as user_verify:
|
||||
user_verify.return_value = True
|
||||
with patch('lms.djangoapps.certificates.api.certificate_downloadable_status',
|
||||
|
||||
@@ -70,7 +70,7 @@ from lms.djangoapps.experiments.utils import get_experiment_user_metadata_contex
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from lms.djangoapps.instructor.enrollment import uses_shib
|
||||
from lms.djangoapps.instructor.views.api import require_global_staff
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.catalog.utils import get_programs, get_programs_with_type
|
||||
from openedx.core.djangoapps.certificates import api as auto_certs_api
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
@@ -1017,7 +1017,7 @@ def _downloadable_certificate_message(course, cert_downloadable_status):
|
||||
|
||||
def _missing_required_verification(student, enrollment_mode):
|
||||
return (
|
||||
enrollment_mode in CourseMode.VERIFIED_MODES and not SoftwareSecurePhotoVerification.user_is_verified(student)
|
||||
enrollment_mode in CourseMode.VERIFIED_MODES and not IDVerificationService.user_is_verified(student)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from lms.djangoapps.certificates.tests.factories import (
|
||||
from course_modes.models import CourseMode
|
||||
from courseware.tests.factories import GlobalStaffFactory, InstructorFactory, UserFactory
|
||||
from lms.djangoapps.grades.tests.utils import mock_passing_grade
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
@@ -398,10 +398,9 @@ class CertificatesInstructorApiTest(SharedModuleStoreTestCase):
|
||||
|
||||
# Create and assert user's ID verification record.
|
||||
SoftwareSecurePhotoVerificationFactory.create(user=self.user, status=id_verification_status)
|
||||
actual_verification_status = SoftwareSecurePhotoVerification.verification_status_for_user(
|
||||
actual_verification_status = IDVerificationService.verification_status_for_user(
|
||||
self.user,
|
||||
self.course.id,
|
||||
enrollment.mode,
|
||||
enrollment.mode
|
||||
)
|
||||
self.assertEquals(actual_verification_status, verification_output)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import xmodule.graders as xmgraders
|
||||
from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate
|
||||
from courseware.models import StudentModule
|
||||
from lms.djangoapps.grades.context import grading_context_for_course
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from shoppingcart.models import (
|
||||
CouponRedemption,
|
||||
@@ -286,9 +286,8 @@ def enrolled_students_features(course_key, features):
|
||||
if include_enrollment_mode or include_verification_status:
|
||||
enrollment_mode = CourseEnrollment.enrollment_mode_for_user(student, course_key)[0]
|
||||
if include_verification_status:
|
||||
student_dict['verification_status'] = SoftwareSecurePhotoVerification.verification_status_for_user(
|
||||
student_dict['verification_status'] = IDVerificationService.verification_status_for_user(
|
||||
student,
|
||||
course_key,
|
||||
enrollment_mode
|
||||
)
|
||||
if include_enrollment_mode:
|
||||
|
||||
@@ -184,7 +184,7 @@ class TestAnalyticsBasic(ModuleStoreTestCase):
|
||||
# is returned by verification and enrollment code
|
||||
with patch("student.models.CourseEnrollment.enrollment_mode_for_user") as enrollment_patch:
|
||||
with patch(
|
||||
"lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.verification_status_for_user"
|
||||
"lms.djangoapps.verify_student.services.IDVerificationService.verification_status_for_user"
|
||||
) as verify_patch:
|
||||
enrollment_patch.return_value = ["verified"]
|
||||
verify_patch.return_value = "dummy verification status"
|
||||
|
||||
@@ -20,7 +20,7 @@ from lms.djangoapps.grades.context import grading_context, grading_context_for_c
|
||||
from lms.djangoapps.grades.models import PersistentCourseGrade
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from lms.djangoapps.teams.models import CourseTeamMembership
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.content.block_structure.api import get_course_in_cache
|
||||
from openedx.core.djangoapps.course_groups.cohorts import bulk_cache_cohorts, get_cohort, is_course_cohorted
|
||||
from openedx.core.djangoapps.user_api.course_tag.api import BulkCourseTags
|
||||
@@ -170,8 +170,7 @@ class _EnrollmentBulkContext(object):
|
||||
def __init__(self, context, users):
|
||||
CourseEnrollment.bulk_fetch_enrollment_states(users, context.course_id)
|
||||
self.verified_users = [
|
||||
verified.user.id for verified in
|
||||
SoftwareSecurePhotoVerification.verified_query().filter(user__in=users).select_related('user')
|
||||
verified.user.id for verified in IDVerificationService.get_verified_users(users)
|
||||
]
|
||||
|
||||
|
||||
@@ -386,9 +385,8 @@ class CourseGradeReport(object):
|
||||
given user.
|
||||
"""
|
||||
enrollment_mode = CourseEnrollment.enrollment_mode_for_user(user, context.course_id)[0]
|
||||
verification_status = SoftwareSecurePhotoVerification.verification_status_for_user(
|
||||
verification_status = IDVerificationService.verification_status_for_user(
|
||||
user,
|
||||
context.course_id,
|
||||
enrollment_mode,
|
||||
user_is_verified=user.id in bulk_enrollments.verified_users,
|
||||
)
|
||||
|
||||
@@ -27,13 +27,11 @@ from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from model_utils import Choices
|
||||
from model_utils.models import StatusModel, TimeStampedModel
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from lms.djangoapps.verify_student.ssencrypt import (
|
||||
encrypt_and_encode,
|
||||
generate_signed_message,
|
||||
@@ -41,9 +39,9 @@ from lms.djangoapps.verify_student.ssencrypt import (
|
||||
rsa_encrypt
|
||||
)
|
||||
from openedx.core.djangoapps.signals.signals import LEARNER_NOW_VERIFIED
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.storage import get_storage
|
||||
|
||||
from .utils import earliest_allowed_verification_date
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -193,213 +191,6 @@ class PhotoVerification(StatusModel):
|
||||
abstract = True
|
||||
ordering = ['-created_at']
|
||||
|
||||
##### Methods listed in the order you'd typically call them
|
||||
@classmethod
|
||||
def _earliest_allowed_date(cls):
|
||||
"""
|
||||
Returns the earliest allowed date given the settings
|
||||
|
||||
"""
|
||||
days_good_for = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
return datetime.now(pytz.UTC) - timedelta(days=days_good_for)
|
||||
|
||||
@classmethod
|
||||
def user_is_verified(cls, user, earliest_allowed_date=None):
|
||||
"""
|
||||
Return whether or not a user has satisfactorily proved their identity.
|
||||
Depending on the policy, this can expire after some period of time, so
|
||||
a user might have to renew periodically.
|
||||
|
||||
This will check for the user's *initial* verification.
|
||||
"""
|
||||
return cls.verified_query(earliest_allowed_date).filter(user=user).exists()
|
||||
|
||||
@classmethod
|
||||
def verified_query(cls, earliest_allowed_date=None):
|
||||
"""
|
||||
Return a query set for all records with 'approved' state
|
||||
that are still valid according to the earliest_allowed_date
|
||||
value or policy settings.
|
||||
"""
|
||||
return cls.objects.filter(
|
||||
status="approved",
|
||||
created_at__gte=(earliest_allowed_date or cls._earliest_allowed_date()),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def verification_valid_or_pending(cls, user, earliest_allowed_date=None, queryset=None):
|
||||
"""
|
||||
Check whether the user has a complete verification attempt that is
|
||||
or *might* be good. This means that it's approved, been submitted,
|
||||
or would have been submitted but had an non-user error when it was
|
||||
being submitted.
|
||||
It's basically any situation in which the user has signed off on
|
||||
the contents of the attempt, and we have not yet received a denial.
|
||||
This will check for the user's *initial* verification.
|
||||
|
||||
Arguments:
|
||||
user:
|
||||
earliest_allowed_date: earliest allowed date given in the
|
||||
settings
|
||||
queryset: If a queryset is provided, that will be used instead
|
||||
of hitting the database.
|
||||
|
||||
Returns:
|
||||
queryset: queryset of 'PhotoVerification' sorted by 'created_at' in
|
||||
descending order.
|
||||
"""
|
||||
|
||||
valid_statuses = ['submitted', 'approved', 'must_retry']
|
||||
|
||||
if queryset is None:
|
||||
queryset = cls.objects.filter(user=user)
|
||||
|
||||
return queryset.filter(
|
||||
status__in=valid_statuses,
|
||||
created_at__gte=(
|
||||
earliest_allowed_date
|
||||
or cls._earliest_allowed_date()
|
||||
)
|
||||
).order_by('-created_at')
|
||||
|
||||
@classmethod
|
||||
def get_expiration_datetime(cls, user, queryset=None):
|
||||
"""
|
||||
Check whether the user has an approved verification and return the
|
||||
"expiration_datetime" of most recent "approved" verification.
|
||||
|
||||
Arguments:
|
||||
user (Object): User
|
||||
queryset: If a queryset is provided, that will be used instead
|
||||
of hitting the database.
|
||||
|
||||
Returns:
|
||||
expiration_datetime: expiration_datetime of most recent "approved"
|
||||
verification.
|
||||
"""
|
||||
if queryset is None:
|
||||
queryset = cls.objects.filter(user=user)
|
||||
|
||||
photo_verification = queryset.filter(status='approved').first()
|
||||
if photo_verification:
|
||||
return photo_verification.expiration_datetime
|
||||
|
||||
@classmethod
|
||||
def user_has_valid_or_pending(cls, user, earliest_allowed_date=None, queryset=None):
|
||||
"""
|
||||
Check whether the user has an active or pending verification attempt
|
||||
|
||||
Returns:
|
||||
bool: True or False according to existence of valid verifications
|
||||
"""
|
||||
return cls.verification_valid_or_pending(user, earliest_allowed_date, queryset).exists()
|
||||
|
||||
@classmethod
|
||||
def active_for_user(cls, user):
|
||||
"""
|
||||
Return the most recent PhotoVerification that is marked ready (i.e. the
|
||||
user has said they're set, but we haven't submitted anything yet).
|
||||
|
||||
This checks for the original verification.
|
||||
"""
|
||||
# This should only be one at the most, but just in case we create more
|
||||
# by mistake, we'll grab the most recently created one.
|
||||
active_attempts = cls.objects.filter(user=user, status='ready').order_by('-created_at')
|
||||
if active_attempts:
|
||||
return active_attempts[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def user_status(cls, user):
|
||||
"""
|
||||
Returns the status of the user based on their past verification attempts
|
||||
|
||||
If no such verification exists, returns 'none'
|
||||
If verification has expired, returns 'expired'
|
||||
If the verification has been approved, returns 'approved'
|
||||
If the verification process is still ongoing, returns 'pending'
|
||||
If the verification has been denied and the user must resubmit photos, returns 'must_reverify'
|
||||
|
||||
This checks initial verifications
|
||||
"""
|
||||
status = 'none'
|
||||
error_msg = ''
|
||||
|
||||
if cls.user_is_verified(user):
|
||||
status = 'approved'
|
||||
|
||||
elif cls.user_has_valid_or_pending(user):
|
||||
# user_has_valid_or_pending does include 'approved', but if we are
|
||||
# here, we know that the attempt is still pending
|
||||
status = 'pending'
|
||||
|
||||
else:
|
||||
# we need to check the most recent attempt to see if we need to ask them to do
|
||||
# a retry
|
||||
try:
|
||||
attempts = cls.objects.filter(user=user).order_by('-updated_at')
|
||||
attempt = attempts[0]
|
||||
except IndexError:
|
||||
# we return 'none'
|
||||
|
||||
return ('none', error_msg)
|
||||
|
||||
if attempt.created_at < cls._earliest_allowed_date():
|
||||
return (
|
||||
'expired',
|
||||
_("Your {platform_name} verification has expired.").format(
|
||||
platform_name=configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
)
|
||||
)
|
||||
|
||||
# If someone is denied their original verification attempt, they can try to reverify.
|
||||
if attempt.status == 'denied':
|
||||
status = 'must_reverify'
|
||||
|
||||
if attempt.error_msg:
|
||||
error_msg = attempt.parsed_error_msg()
|
||||
|
||||
return (status, error_msg)
|
||||
|
||||
@classmethod
|
||||
def verification_for_datetime(cls, deadline, candidates):
|
||||
"""Find a verification in a set that applied during a particular datetime.
|
||||
|
||||
A verification is considered "active" during a datetime if:
|
||||
1) The verification was created before the datetime, and
|
||||
2) The verification is set to expire after the datetime.
|
||||
|
||||
Note that verification status is *not* considered here,
|
||||
just the start/expire dates.
|
||||
|
||||
If multiple verifications were active at the deadline,
|
||||
returns the most recently created one.
|
||||
|
||||
Arguments:
|
||||
deadline (datetime): The datetime at which the verification applied.
|
||||
If `None`, then return the most recently created candidate.
|
||||
candidates (list of `PhotoVerification`s): Potential verifications to search through.
|
||||
|
||||
Returns:
|
||||
PhotoVerification: A photo verification that was active at the deadline.
|
||||
If no verification was active, return None.
|
||||
|
||||
"""
|
||||
if len(candidates) == 0:
|
||||
return None
|
||||
|
||||
# If there's no deadline, then return the most recently created verification
|
||||
if deadline is None:
|
||||
return candidates[0]
|
||||
|
||||
# Otherwise, look for a verification that was in effect at the deadline,
|
||||
# preferring recent verifications.
|
||||
# If no such verification is found, implicitly return `None`
|
||||
for verification in candidates:
|
||||
if verification.active_at_datetime(deadline):
|
||||
return verification
|
||||
|
||||
@property
|
||||
def expiration_datetime(self):
|
||||
"""Datetime that the verification will expire. """
|
||||
@@ -642,7 +433,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
user=user,
|
||||
status__in=["submitted", "approved"],
|
||||
created_at__gte=(
|
||||
earliest_allowed_date or cls._earliest_allowed_date()
|
||||
earliest_allowed_date or earliest_allowed_verification_date()
|
||||
)
|
||||
).exclude(photo_id_key='')
|
||||
|
||||
@@ -974,34 +765,6 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def verification_status_for_user(cls, user, course_id, user_enrollment_mode, user_is_verified=None):
|
||||
"""
|
||||
Returns the verification status for use in grade report.
|
||||
"""
|
||||
if user_enrollment_mode not in CourseMode.VERIFIED_MODES:
|
||||
return 'N/A'
|
||||
|
||||
if user_is_verified is None:
|
||||
user_is_verified = cls.user_is_verified(user)
|
||||
|
||||
if not user_is_verified:
|
||||
return 'Not ID Verified'
|
||||
else:
|
||||
return 'ID Verified'
|
||||
|
||||
@classmethod
|
||||
def is_verification_expiring_soon(cls, expiration_datetime):
|
||||
"""
|
||||
Returns True if verification is expiring within EXPIRING_SOON_WINDOW.
|
||||
"""
|
||||
if expiration_datetime:
|
||||
if (expiration_datetime - datetime.now(pytz.UTC)).days <= settings.VERIFY_STUDENT.get(
|
||||
"EXPIRING_SOON_WINDOW"):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class VerificationDeadline(TimeStampedModel):
|
||||
"""
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
"""
|
||||
Implementation of "reverification" service to communicate with Reverification XBlock
|
||||
Implementation of abstraction layer for other parts of the system to make queries related to ID Verification.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from student.models import User
|
||||
|
||||
from .models import SoftwareSecurePhotoVerification
|
||||
from .utils import earliest_allowed_verification_date
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VerificationService(object):
|
||||
class XBlockVerificationService(object):
|
||||
"""
|
||||
Learner verification XBlock service
|
||||
Learner verification XBlock service.
|
||||
"""
|
||||
|
||||
def get_status(self, user_id):
|
||||
@@ -33,12 +38,209 @@ class VerificationService(object):
|
||||
'must_reverify' - verification has been denied and user must resubmit photos
|
||||
"""
|
||||
user = User.objects.get(id=user_id)
|
||||
# TODO: provide a photo verification abstraction so that this
|
||||
# isn't hard-coded to use Software Secure.
|
||||
return SoftwareSecurePhotoVerification.user_status(user)
|
||||
return IDVerificationService.user_status(user)
|
||||
|
||||
def reverify_url(self):
|
||||
"""
|
||||
Returns the URL for a user to verify themselves.
|
||||
"""
|
||||
return reverse('verify_student_reverify')
|
||||
|
||||
|
||||
class IDVerificationService(object):
|
||||
"""
|
||||
Learner verification service interface for callers within edx-platform.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def user_is_verified(cls, user, earliest_allowed_date=None):
|
||||
"""
|
||||
Return whether or not a user has satisfactorily proved their identity.
|
||||
Depending on the policy, this can expire after some period of time, so
|
||||
a user might have to renew periodically.
|
||||
|
||||
This will check for the user's *initial* verification.
|
||||
"""
|
||||
return cls.verified_query(earliest_allowed_date).filter(user=user).exists()
|
||||
|
||||
@classmethod
|
||||
def verified_query(cls, earliest_allowed_date=None):
|
||||
"""
|
||||
Return a query set for all records with 'approved' state
|
||||
that are still valid according to the earliest_allowed_date
|
||||
value or policy settings.
|
||||
"""
|
||||
return SoftwareSecurePhotoVerification.objects.filter(
|
||||
status="approved",
|
||||
created_at__gte=(earliest_allowed_date or earliest_allowed_verification_date()),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def verifications_for_user(cls, user):
|
||||
"""
|
||||
Return a query set for all records associated with the given user.
|
||||
"""
|
||||
return SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
|
||||
@classmethod
|
||||
def get_verified_users(cls, users):
|
||||
"""
|
||||
Return the list of user ids that have non expired verifications from the given list of users.
|
||||
"""
|
||||
return cls.verified_query().filter(user__in=users).select_related('user')
|
||||
|
||||
@classmethod
|
||||
def verification_valid_or_pending(cls, user, earliest_allowed_date=None, queryset=None):
|
||||
"""
|
||||
Check whether the user has a complete verification attempt that is
|
||||
or *might* be good. This means that it's approved, been submitted,
|
||||
or would have been submitted but had an non-user error when it was
|
||||
being submitted.
|
||||
It's basically any situation in which the user has signed off on
|
||||
the contents of the attempt, and we have not yet received a denial.
|
||||
This will check for the user's *initial* verification.
|
||||
|
||||
Arguments:
|
||||
user:
|
||||
earliest_allowed_date: earliest allowed date given in the
|
||||
settings
|
||||
queryset: If a queryset is provided, that will be used instead
|
||||
of hitting the database.
|
||||
|
||||
Returns:
|
||||
queryset: queryset of 'PhotoVerification' sorted by 'created_at' in
|
||||
descending order.
|
||||
"""
|
||||
|
||||
valid_statuses = ['submitted', 'approved', 'must_retry']
|
||||
|
||||
if queryset is None:
|
||||
queryset = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
|
||||
return queryset.filter(
|
||||
status__in=valid_statuses,
|
||||
created_at__gte=(
|
||||
earliest_allowed_date
|
||||
or earliest_allowed_verification_date()
|
||||
)
|
||||
).order_by('-created_at')
|
||||
|
||||
@classmethod
|
||||
def get_expiration_datetime(cls, user, queryset=None):
|
||||
"""
|
||||
Check whether the user has an approved verification and return the
|
||||
"expiration_datetime" of most recent "approved" verification.
|
||||
|
||||
Arguments:
|
||||
user (Object): User
|
||||
queryset: If a queryset is provided, that will be used instead
|
||||
of hitting the database.
|
||||
|
||||
Returns:
|
||||
expiration_datetime: expiration_datetime of most recent "approved"
|
||||
verification.
|
||||
"""
|
||||
if queryset is None:
|
||||
queryset = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
|
||||
photo_verification = queryset.filter(status='approved').first()
|
||||
if photo_verification:
|
||||
return photo_verification.expiration_datetime
|
||||
|
||||
@classmethod
|
||||
def user_has_valid_or_pending(cls, user, earliest_allowed_date=None, queryset=None):
|
||||
"""
|
||||
Check whether the user has an active or pending verification attempt
|
||||
|
||||
Returns:
|
||||
bool: True or False according to existence of valid verifications
|
||||
"""
|
||||
return cls.verification_valid_or_pending(user, earliest_allowed_date, queryset).exists()
|
||||
|
||||
@classmethod
|
||||
def active_for_user(cls, user):
|
||||
"""
|
||||
Return the most recent PhotoVerification that is marked ready (i.e. the
|
||||
user has said they're set, but we haven't submitted anything yet).
|
||||
|
||||
This checks for the original verification.
|
||||
"""
|
||||
# This should only be one at the most, but just in case we create more
|
||||
# by mistake, we'll grab the most recently created one.
|
||||
active_attempts = SoftwareSecurePhotoVerification.objects.filter(
|
||||
user=user,
|
||||
status='ready'
|
||||
).order_by('-created_at')
|
||||
|
||||
if active_attempts:
|
||||
return active_attempts[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def user_status(cls, user):
|
||||
"""
|
||||
Returns the status of the user based on their past verification attempts, and any corresponding error messages.
|
||||
|
||||
If no such verification exists, returns 'none'
|
||||
If verification has expired, returns 'expired'
|
||||
If the verification has been approved, returns 'approved'
|
||||
If the verification process is still ongoing, returns 'pending'
|
||||
If the verification has been denied and the user must resubmit photos, returns 'must_reverify'
|
||||
|
||||
This checks initial verifications
|
||||
"""
|
||||
status = 'none'
|
||||
error_msg = ''
|
||||
|
||||
if cls.user_is_verified(user):
|
||||
status = 'approved'
|
||||
|
||||
elif cls.user_has_valid_or_pending(user):
|
||||
# user_has_valid_or_pending does include 'approved', but if we are
|
||||
# here, we know that the attempt is still pending
|
||||
status = 'pending'
|
||||
|
||||
else:
|
||||
# we need to check the most recent attempt to see if we need to ask them to do
|
||||
# a retry
|
||||
try:
|
||||
attempts = SoftwareSecurePhotoVerification.objects.filter(user=user).order_by('-updated_at')
|
||||
attempt = attempts[0]
|
||||
except IndexError:
|
||||
# we return 'none'
|
||||
|
||||
return ('none', error_msg)
|
||||
|
||||
if attempt.created_at < earliest_allowed_verification_date():
|
||||
return (
|
||||
'expired',
|
||||
_("Your {platform_name} verification has expired.").format(
|
||||
platform_name=configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
)
|
||||
)
|
||||
|
||||
# If someone is denied their original verification attempt, they can try to reverify.
|
||||
if attempt.status == 'denied':
|
||||
status = 'must_reverify'
|
||||
|
||||
if attempt.error_msg:
|
||||
error_msg = attempt.parsed_error_msg()
|
||||
|
||||
return (status, error_msg)
|
||||
|
||||
@classmethod
|
||||
def verification_status_for_user(cls, user, user_enrollment_mode, user_is_verified=None):
|
||||
"""
|
||||
Returns the verification status for use in grade report.
|
||||
"""
|
||||
if user_enrollment_mode not in CourseMode.VERIFIED_MODES:
|
||||
return 'N/A'
|
||||
|
||||
if user_is_verified is None:
|
||||
user_is_verified = cls.user_is_verified(user)
|
||||
|
||||
if not user_is_verified:
|
||||
return 'Not ID Verified'
|
||||
else:
|
||||
return 'ID Verified'
|
||||
|
||||
@@ -12,8 +12,6 @@ from freezegun import freeze_time
|
||||
from mock import patch
|
||||
from nose.tools import ( # pylint: disable=no-name-in-module
|
||||
assert_equals,
|
||||
assert_false,
|
||||
assert_is_none,
|
||||
assert_raises,
|
||||
assert_true
|
||||
)
|
||||
@@ -29,7 +27,6 @@ from lms.djangoapps.verify_student.models import (
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
FAKE_SETTINGS = {
|
||||
"SOFTWARE_SECURE": {
|
||||
@@ -233,116 +230,6 @@ class TestPhotoVerification(MockS3Mixin, ModuleStoreTestCase):
|
||||
|
||||
self.assertEqual(attempt.photo_id_key, "fake-photo-id-key")
|
||||
|
||||
def test_active_for_user(self):
|
||||
"""
|
||||
Make sure we can retrive a user's active (in progress) verification
|
||||
attempt.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
|
||||
# This user has no active at the moment...
|
||||
assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user))
|
||||
|
||||
# Create an attempt and mark it ready...
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
attempt.mark_ready()
|
||||
assert_equals(attempt, SoftwareSecurePhotoVerification.active_for_user(user))
|
||||
|
||||
# A new user won't see this...
|
||||
user2 = UserFactory.create()
|
||||
user2.save()
|
||||
assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user2))
|
||||
|
||||
# If it's got a different status, it doesn't count
|
||||
for status in ["submitted", "must_retry", "approved", "denied"]:
|
||||
attempt.status = status
|
||||
attempt.save()
|
||||
assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user))
|
||||
|
||||
# But if we create yet another one and mark it ready, it passes again.
|
||||
attempt_2 = SoftwareSecurePhotoVerification(user=user)
|
||||
attempt_2.mark_ready()
|
||||
assert_equals(attempt_2, SoftwareSecurePhotoVerification.active_for_user(user))
|
||||
|
||||
# And if we add yet another one with a later created time, we get that
|
||||
# one instead. We always want the most recent attempt marked ready()
|
||||
attempt_3 = SoftwareSecurePhotoVerification(
|
||||
user=user,
|
||||
created_at=attempt_2.created_at + timedelta(days=1)
|
||||
)
|
||||
attempt_3.save()
|
||||
|
||||
# We haven't marked attempt_3 ready yet, so attempt_2 still wins
|
||||
assert_equals(attempt_2, SoftwareSecurePhotoVerification.active_for_user(user))
|
||||
|
||||
# Now we mark attempt_3 ready and expect it to come back
|
||||
attempt_3.mark_ready()
|
||||
assert_equals(attempt_3, SoftwareSecurePhotoVerification.active_for_user(user))
|
||||
|
||||
def test_user_is_verified(self):
|
||||
"""
|
||||
Test to make sure we correctly answer whether a user has been verified.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
attempt.save()
|
||||
|
||||
# If it's any of these, they're not verified...
|
||||
for status in ["created", "ready", "denied", "submitted", "must_retry"]:
|
||||
attempt.status = status
|
||||
attempt.save()
|
||||
assert_false(SoftwareSecurePhotoVerification.user_is_verified(user), status)
|
||||
|
||||
attempt.status = "approved"
|
||||
attempt.save()
|
||||
assert_true(SoftwareSecurePhotoVerification.user_is_verified(user), attempt.status)
|
||||
|
||||
def test_user_has_valid_or_pending(self):
|
||||
"""
|
||||
Determine whether we have to prompt this user to verify, or if they've
|
||||
already at least initiated a verification submission.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
|
||||
# If it's any of these statuses, they don't have anything outstanding
|
||||
for status in ["created", "ready", "denied"]:
|
||||
attempt.status = status
|
||||
attempt.save()
|
||||
assert_false(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user), status)
|
||||
|
||||
# Any of these, and we are. Note the benefit of the doubt we're giving
|
||||
# -- must_retry, and submitted both count until we hear otherwise
|
||||
for status in ["submitted", "must_retry", "approved"]:
|
||||
attempt.status = status
|
||||
attempt.save()
|
||||
assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user), status)
|
||||
|
||||
def test_user_status(self):
|
||||
# test for correct status when no error returned
|
||||
user = UserFactory.create()
|
||||
status = SoftwareSecurePhotoVerification.user_status(user)
|
||||
self.assertEquals(status, ('none', ''))
|
||||
|
||||
# test for when one has been created
|
||||
attempt = SoftwareSecurePhotoVerification.objects.create(user=user, status='approved')
|
||||
status = SoftwareSecurePhotoVerification.user_status(user)
|
||||
self.assertEquals(status, ('approved', ''))
|
||||
|
||||
# create another one for the same user, make sure the right one is
|
||||
# returned
|
||||
SoftwareSecurePhotoVerification.objects.create(
|
||||
user=user, status='denied', error_msg='[{"photoIdReasons": ["Not provided"]}]'
|
||||
)
|
||||
status = SoftwareSecurePhotoVerification.user_status(user)
|
||||
self.assertEquals(status, ('approved', ''))
|
||||
|
||||
# now delete the first one and verify that the denial is being handled
|
||||
# properly
|
||||
attempt.delete()
|
||||
status = SoftwareSecurePhotoVerification.user_status(user)
|
||||
self.assertEquals(status, ('must_reverify', ['id_image_missing']))
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
def test_parse_error_msg_success(self):
|
||||
user = UserFactory.create()
|
||||
@@ -384,87 +271,6 @@ class TestPhotoVerification(MockS3Mixin, ModuleStoreTestCase):
|
||||
attempt.save()
|
||||
self.assertFalse(attempt.active_at_datetime(datetime.now(pytz.UTC) + timedelta(days=1)))
|
||||
|
||||
def test_verification_for_datetime(self):
|
||||
user = UserFactory.create()
|
||||
now = datetime.now(pytz.UTC)
|
||||
|
||||
# No attempts in the query set, so should return None
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = SoftwareSecurePhotoVerification.verification_for_datetime(now, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
# Should also return None if no deadline specified
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = SoftwareSecurePhotoVerification.verification_for_datetime(None, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
# Make an attempt
|
||||
attempt = SoftwareSecurePhotoVerification.objects.create(user=user)
|
||||
|
||||
# Before the created date, should get no results
|
||||
before = attempt.created_at - timedelta(seconds=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = SoftwareSecurePhotoVerification.verification_for_datetime(before, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
# Immediately after the created date, should get the attempt
|
||||
after_created = attempt.created_at + timedelta(seconds=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = SoftwareSecurePhotoVerification.verification_for_datetime(after_created, query)
|
||||
self.assertEqual(result, attempt)
|
||||
|
||||
# If no deadline specified, should return first available
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = SoftwareSecurePhotoVerification.verification_for_datetime(None, query)
|
||||
self.assertEqual(result, attempt)
|
||||
|
||||
# Immediately before the expiration date, should get the attempt
|
||||
expiration = attempt.created_at + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
before_expiration = expiration - timedelta(seconds=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = SoftwareSecurePhotoVerification.verification_for_datetime(before_expiration, query)
|
||||
self.assertEqual(result, attempt)
|
||||
|
||||
# Immediately after the expiration date, should not get the attempt
|
||||
attempt.created_at = attempt.created_at - timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
attempt.save()
|
||||
after = datetime.now(pytz.UTC) + timedelta(days=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = SoftwareSecurePhotoVerification.verification_for_datetime(after, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
# Create a second attempt in the same window
|
||||
second_attempt = SoftwareSecurePhotoVerification.objects.create(user=user)
|
||||
|
||||
# Now we should get the newer attempt
|
||||
deadline = second_attempt.created_at + timedelta(days=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = SoftwareSecurePhotoVerification.verification_for_datetime(deadline, query)
|
||||
self.assertEqual(result, second_attempt)
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
{'enrollment_mode': 'honor', 'status': None, 'output': 'N/A'},
|
||||
{'enrollment_mode': 'audit', 'status': None, 'output': 'N/A'},
|
||||
{'enrollment_mode': 'verified', 'status': False, 'output': 'Not ID Verified'},
|
||||
{'enrollment_mode': 'verified', 'status': True, 'output': 'ID Verified'},
|
||||
)
|
||||
def test_verification_status_for_user(self, enrollment_mode, status, output):
|
||||
"""
|
||||
Verify verification_status_for_user returns correct status.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
course = CourseFactory.create()
|
||||
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
|
||||
) as mock_verification:
|
||||
|
||||
mock_verification.return_value = status
|
||||
|
||||
status = SoftwareSecurePhotoVerification.verification_status_for_user(user, course.id, enrollment_mode)
|
||||
self.assertEqual(status, output)
|
||||
|
||||
def test_initial_verification_for_user(self):
|
||||
"""Test that method 'get_initial_verification' of model
|
||||
'SoftwareSecurePhotoVerification' always returns the initial
|
||||
|
||||
168
lms/djangoapps/verify_student/tests/test_services.py
Normal file
168
lms/djangoapps/verify_student/tests/test_services.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for the service classes in verify_student.
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
from mock import patch
|
||||
from nose.tools import (
|
||||
assert_equals,
|
||||
assert_false,
|
||||
assert_is_none,
|
||||
assert_true
|
||||
)
|
||||
|
||||
from common.test.utils import MockS3Mixin
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
FAKE_SETTINGS = {
|
||||
"DAYS_GOOD_FOR": 10,
|
||||
}
|
||||
|
||||
|
||||
@patch.dict(settings.VERIFY_STUDENT, FAKE_SETTINGS)
|
||||
@ddt.ddt
|
||||
class TestIDVerificationService(MockS3Mixin, ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for IDVerificationService.
|
||||
"""
|
||||
|
||||
def test_active_for_user(self):
|
||||
"""
|
||||
Make sure we can retrive a user's active (in progress) verification
|
||||
attempt.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
|
||||
# This user has no active at the moment...
|
||||
assert_is_none(IDVerificationService.active_for_user(user))
|
||||
|
||||
# Create an attempt and mark it ready...
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
attempt.mark_ready()
|
||||
assert_equals(attempt, IDVerificationService.active_for_user(user))
|
||||
|
||||
# A new user won't see this...
|
||||
user2 = UserFactory.create()
|
||||
user2.save()
|
||||
assert_is_none(IDVerificationService.active_for_user(user2))
|
||||
|
||||
# If it's got a different status, it doesn't count
|
||||
for status in ["submitted", "must_retry", "approved", "denied"]:
|
||||
attempt.status = status
|
||||
attempt.save()
|
||||
assert_is_none(IDVerificationService.active_for_user(user))
|
||||
|
||||
# But if we create yet another one and mark it ready, it passes again.
|
||||
attempt_2 = SoftwareSecurePhotoVerification(user=user)
|
||||
attempt_2.mark_ready()
|
||||
assert_equals(attempt_2, IDVerificationService.active_for_user(user))
|
||||
|
||||
# And if we add yet another one with a later created time, we get that
|
||||
# one instead. We always want the most recent attempt marked ready()
|
||||
attempt_3 = SoftwareSecurePhotoVerification(
|
||||
user=user,
|
||||
created_at=attempt_2.created_at + timedelta(days=1)
|
||||
)
|
||||
attempt_3.save()
|
||||
|
||||
# We haven't marked attempt_3 ready yet, so attempt_2 still wins
|
||||
assert_equals(attempt_2, IDVerificationService.active_for_user(user))
|
||||
|
||||
# Now we mark attempt_3 ready and expect it to come back
|
||||
attempt_3.mark_ready()
|
||||
assert_equals(attempt_3, IDVerificationService.active_for_user(user))
|
||||
|
||||
def test_user_is_verified(self):
|
||||
"""
|
||||
Test to make sure we correctly answer whether a user has been verified.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
attempt.save()
|
||||
|
||||
# If it's any of these, they're not verified...
|
||||
for status in ["created", "ready", "denied", "submitted", "must_retry"]:
|
||||
attempt.status = status
|
||||
attempt.save()
|
||||
assert_false(IDVerificationService.user_is_verified(user), status)
|
||||
|
||||
attempt.status = "approved"
|
||||
attempt.save()
|
||||
assert_true(IDVerificationService.user_is_verified(user), attempt.status)
|
||||
|
||||
def test_user_has_valid_or_pending(self):
|
||||
"""
|
||||
Determine whether we have to prompt this user to verify, or if they've
|
||||
already at least initiated a verification submission.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
|
||||
# If it's any of these statuses, they don't have anything outstanding
|
||||
for status in ["created", "ready", "denied"]:
|
||||
attempt.status = status
|
||||
attempt.save()
|
||||
assert_false(IDVerificationService.user_has_valid_or_pending(user), status)
|
||||
|
||||
# Any of these, and we are. Note the benefit of the doubt we're giving
|
||||
# -- must_retry, and submitted both count until we hear otherwise
|
||||
for status in ["submitted", "must_retry", "approved"]:
|
||||
attempt.status = status
|
||||
attempt.save()
|
||||
assert_true(IDVerificationService.user_has_valid_or_pending(user), status)
|
||||
|
||||
def test_user_status(self):
|
||||
# test for correct status when no error returned
|
||||
user = UserFactory.create()
|
||||
status = IDVerificationService.user_status(user)
|
||||
self.assertEquals(status, ('none', ''))
|
||||
|
||||
# test for when one has been created
|
||||
attempt = SoftwareSecurePhotoVerification.objects.create(user=user, status='approved')
|
||||
status = IDVerificationService.user_status(user)
|
||||
self.assertEquals(status, ('approved', ''))
|
||||
|
||||
# create another one for the same user, make sure the right one is
|
||||
# returned
|
||||
SoftwareSecurePhotoVerification.objects.create(
|
||||
user=user, status='denied', error_msg='[{"photoIdReasons": ["Not provided"]}]'
|
||||
)
|
||||
status = IDVerificationService.user_status(user)
|
||||
self.assertEquals(status, ('approved', ''))
|
||||
|
||||
# now delete the first one and verify that the denial is being handled
|
||||
# properly
|
||||
attempt.delete()
|
||||
status = IDVerificationService.user_status(user)
|
||||
self.assertEquals(status, ('must_reverify', ['id_image_missing']))
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
{'enrollment_mode': 'honor', 'status': None, 'output': 'N/A'},
|
||||
{'enrollment_mode': 'audit', 'status': None, 'output': 'N/A'},
|
||||
{'enrollment_mode': 'verified', 'status': False, 'output': 'Not ID Verified'},
|
||||
{'enrollment_mode': 'verified', 'status': True, 'output': 'ID Verified'},
|
||||
)
|
||||
def test_verification_status_for_user(self, enrollment_mode, status, output):
|
||||
"""
|
||||
Verify verification_status_for_user returns correct status.
|
||||
"""
|
||||
user = UserFactory.create()
|
||||
CourseFactory.create()
|
||||
|
||||
with patch(
|
||||
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
||||
) as mock_verification:
|
||||
|
||||
mock_verification.return_value = status
|
||||
|
||||
status = IDVerificationService.verification_status_for_user(user, enrollment_mode)
|
||||
self.assertEqual(status, output)
|
||||
85
lms/djangoapps/verify_student/tests/test_utils.py
Normal file
85
lms/djangoapps/verify_student/tests/test_utils.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for verify_student utility functions.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import unittest
|
||||
import pytz
|
||||
from mock import patch
|
||||
from pytest import mark
|
||||
from django.conf import settings
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.utils import verification_for_datetime
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
FAKE_SETTINGS = {
|
||||
"DAYS_GOOD_FOR": 10,
|
||||
}
|
||||
|
||||
|
||||
@patch.dict(settings.VERIFY_STUDENT, FAKE_SETTINGS)
|
||||
@mark.django_db
|
||||
class TestVerifyStudentUtils(unittest.TestCase):
|
||||
"""
|
||||
Tests for utility functions in verify_student.
|
||||
"""
|
||||
|
||||
def test_verification_for_datetime(self):
|
||||
user = UserFactory.create()
|
||||
now = datetime.now(pytz.UTC)
|
||||
|
||||
# No attempts in the query set, so should return None
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(now, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
# Should also return None if no deadline specified
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(None, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
# Make an attempt
|
||||
attempt = SoftwareSecurePhotoVerification.objects.create(user=user)
|
||||
|
||||
# Before the created date, should get no results
|
||||
before = attempt.created_at - timedelta(seconds=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(before, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
# Immediately after the created date, should get the attempt
|
||||
after_created = attempt.created_at + timedelta(seconds=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(after_created, query)
|
||||
self.assertEqual(result, attempt)
|
||||
|
||||
# If no deadline specified, should return first available
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(None, query)
|
||||
self.assertEqual(result, attempt)
|
||||
|
||||
# Immediately before the expiration date, should get the attempt
|
||||
expiration = attempt.created_at + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
before_expiration = expiration - timedelta(seconds=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(before_expiration, query)
|
||||
self.assertEqual(result, attempt)
|
||||
|
||||
# Immediately after the expiration date, should not get the attempt
|
||||
attempt.created_at = attempt.created_at - timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
attempt.save()
|
||||
after = datetime.now(pytz.UTC) + timedelta(days=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(after, query)
|
||||
self.assertIs(result, None)
|
||||
|
||||
# Create a second attempt in the same window
|
||||
second_attempt = SoftwareSecurePhotoVerification.objects.create(user=user)
|
||||
|
||||
# Now we should get the newer attempt
|
||||
deadline = second_attempt.created_at + timedelta(days=1)
|
||||
query = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
||||
result = verification_for_datetime(deadline, query)
|
||||
self.assertEqual(result, second_attempt)
|
||||
@@ -1,9 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Common Utilities for the verify_student application.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import pytz
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail
|
||||
@@ -43,3 +45,61 @@ def send_verification_status_email(context):
|
||||
subject=context['subject'],
|
||||
user=context['user'].id
|
||||
))
|
||||
|
||||
|
||||
def is_verification_expiring_soon(expiration_datetime):
|
||||
"""
|
||||
Returns True if verification is expiring within EXPIRING_SOON_WINDOW.
|
||||
"""
|
||||
if expiration_datetime:
|
||||
if (expiration_datetime - datetime.datetime.now(pytz.UTC)).days <= settings.VERIFY_STUDENT.get(
|
||||
"EXPIRING_SOON_WINDOW"):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def earliest_allowed_verification_date():
|
||||
"""
|
||||
Returns the earliest allowed date given the settings
|
||||
"""
|
||||
days_good_for = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
return datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=days_good_for)
|
||||
|
||||
|
||||
def verification_for_datetime(deadline, candidates):
|
||||
"""Find a verification in a set that applied during a particular datetime.
|
||||
|
||||
A verification is considered "active" during a datetime if:
|
||||
1) The verification was created before the datetime, and
|
||||
2) The verification is set to expire after the datetime.
|
||||
|
||||
Note that verification status is *not* considered here,
|
||||
just the start/expire dates.
|
||||
|
||||
If multiple verifications were active at the deadline,
|
||||
returns the most recently created one.
|
||||
|
||||
Arguments:
|
||||
deadline (datetime): The datetime at which the verification applied.
|
||||
If `None`, then return the most recently created candidate.
|
||||
candidates (list of `PhotoVerification`s): Potential verifications to search through.
|
||||
|
||||
Returns:
|
||||
PhotoVerification: A photo verification that was active at the deadline.
|
||||
If no verification was active, return None.
|
||||
|
||||
"""
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
# If there's no deadline, then return the most recently created verification
|
||||
if deadline is None:
|
||||
return candidates[0]
|
||||
|
||||
# Otherwise, look for a verification that was in effect at the deadline,
|
||||
# preferring recent verifications.
|
||||
# If no such verification is found, implicitly return `None`
|
||||
for verification in candidates:
|
||||
if verification.active_at_datetime(deadline):
|
||||
return verification
|
||||
|
||||
@@ -35,8 +35,9 @@ from edxmako.shortcuts import render_to_response, render_to_string
|
||||
from lms.djangoapps.commerce.utils import EcommerceService, is_account_activation_requirement_disabled
|
||||
from lms.djangoapps.verify_student.image import InvalidImageData, decode_image_data
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
from lms.djangoapps.verify_student.utils import send_verification_status_email
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from lms.djangoapps.verify_student.ssencrypt import has_valid_signature
|
||||
from lms.djangoapps.verify_student.utils import is_verification_expiring_soon, send_verification_status_email
|
||||
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
|
||||
from openedx.core.djangoapps.embargo import api as embargo_api
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
@@ -645,7 +646,7 @@ class PayAndVerifyView(View):
|
||||
Returns:
|
||||
datetime object in string format
|
||||
"""
|
||||
photo_verifications = SoftwareSecurePhotoVerification.verification_valid_or_pending(user)
|
||||
photo_verifications = IDVerificationService.verification_valid_or_pending(user)
|
||||
# return 'expiration_datetime' of latest photo verification if found,
|
||||
# otherwise implicitly return ''
|
||||
if photo_verifications:
|
||||
@@ -664,7 +665,7 @@ class PayAndVerifyView(View):
|
||||
submitted photos within the expiration period.
|
||||
|
||||
"""
|
||||
return SoftwareSecurePhotoVerification.user_has_valid_or_pending(user)
|
||||
return IDVerificationService.user_has_valid_or_pending(user)
|
||||
|
||||
def _check_enrollment(self, user, course_key):
|
||||
"""Check whether the user has an active enrollment and has paid.
|
||||
@@ -1202,12 +1203,12 @@ class ReverifyView(View):
|
||||
Most of the work is done client-side by composing the same
|
||||
Backbone views used in the initial verification flow.
|
||||
"""
|
||||
status, __ = SoftwareSecurePhotoVerification.user_status(request.user)
|
||||
status, __ = IDVerificationService.user_status(request.user)
|
||||
|
||||
expiration_datetime = SoftwareSecurePhotoVerification.get_expiration_datetime(request.user)
|
||||
expiration_datetime = IDVerificationService.get_expiration_datetime(request.user)
|
||||
can_reverify = False
|
||||
if expiration_datetime:
|
||||
if SoftwareSecurePhotoVerification.is_verification_expiring_soon(expiration_datetime):
|
||||
if is_verification_expiring_soon(expiration_datetime):
|
||||
# The user has an active verification, but the verification
|
||||
# is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
|
||||
# In this case user can resubmit photos for reverification.
|
||||
|
||||
Reference in New Issue
Block a user