171 lines
5.9 KiB
Python
171 lines
5.9 KiB
Python
"""
|
|
Implementation of "reverification" service to communicate with Reverification XBlock
|
|
"""
|
|
|
|
import logging
|
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import IntegrityError
|
|
|
|
from opaque_keys.edx.keys import CourseKey
|
|
|
|
from student.models import User, CourseEnrollment
|
|
from lms.djangoapps.verify_student.models import VerificationCheckpoint, VerificationStatus, SkippedReverification
|
|
|
|
from .models import SoftwareSecurePhotoVerification
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class VerificationService(object):
|
|
"""
|
|
Learner verification XBlock service
|
|
"""
|
|
|
|
def get_status(self, user_id):
|
|
"""
|
|
Returns the user's current photo verification status.
|
|
|
|
Args:
|
|
user_id: the user's id
|
|
|
|
Returns: one of the following strings
|
|
'none' - no such verification exists
|
|
'expired' - verification has expired
|
|
'approved' - verification has been approved
|
|
'pending' - verification process is still ongoing
|
|
'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)
|
|
|
|
def reverify_url(self):
|
|
"""
|
|
Returns the URL for a user to verify themselves.
|
|
"""
|
|
return reverse('verify_student_reverify')
|
|
|
|
|
|
class ReverificationService(object):
|
|
"""
|
|
Reverification XBlock service
|
|
"""
|
|
|
|
SKIPPED_STATUS = "skipped"
|
|
NON_VERIFIED_TRACK = "not-verified"
|
|
|
|
def get_status(self, user_id, course_id, related_assessment_location):
|
|
"""Get verification attempt status against a user for a given
|
|
'checkpoint' and 'course_id'.
|
|
|
|
Args:
|
|
user_id (str): User Id string
|
|
course_id (str): A string of course id
|
|
related_assessment_location (str): Location of Reverification XBlock
|
|
|
|
Returns: str or None
|
|
"""
|
|
user = User.objects.get(id=user_id)
|
|
course_key = CourseKey.from_string(course_id)
|
|
|
|
if not CourseEnrollment.is_enrolled_as_verified(user, course_key):
|
|
return self.NON_VERIFIED_TRACK
|
|
elif SkippedReverification.check_user_skipped_reverification_exists(user_id, course_key):
|
|
return self.SKIPPED_STATUS
|
|
|
|
try:
|
|
checkpoint_status = VerificationStatus.objects.filter(
|
|
user_id=user_id,
|
|
checkpoint__course_id=course_key,
|
|
checkpoint__checkpoint_location=related_assessment_location
|
|
).latest()
|
|
return checkpoint_status.status
|
|
except ObjectDoesNotExist:
|
|
return None
|
|
|
|
def start_verification(self, course_id, related_assessment_location):
|
|
"""Create re-verification link against a verification checkpoint.
|
|
|
|
Args:
|
|
course_id(str): A string of course id
|
|
related_assessment_location(str): Location of Reverification XBlock
|
|
|
|
Returns:
|
|
Re-verification link
|
|
"""
|
|
course_key = CourseKey.from_string(course_id)
|
|
|
|
# Get-or-create the verification checkpoint
|
|
VerificationCheckpoint.get_or_create_verification_checkpoint(course_key, related_assessment_location)
|
|
|
|
re_verification_link = reverse(
|
|
'verify_student_incourse_reverify',
|
|
args=(
|
|
unicode(course_key),
|
|
unicode(related_assessment_location)
|
|
)
|
|
)
|
|
return re_verification_link
|
|
|
|
def skip_verification(self, user_id, course_id, related_assessment_location):
|
|
"""Add skipped verification attempt entry for a user against a given
|
|
'checkpoint'.
|
|
|
|
Args:
|
|
user_id(str): User Id string
|
|
course_id(str): A string of course_id
|
|
related_assessment_location(str): Location of Reverification XBlock
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
course_key = CourseKey.from_string(course_id)
|
|
checkpoint = VerificationCheckpoint.objects.get(
|
|
course_id=course_key,
|
|
checkpoint_location=related_assessment_location
|
|
)
|
|
user = User.objects.get(id=user_id)
|
|
|
|
# user can skip a reverification attempt only if that user has not already
|
|
# skipped an attempt
|
|
try:
|
|
SkippedReverification.add_skipped_reverification_attempt(checkpoint, user_id, course_key)
|
|
except IntegrityError:
|
|
log.exception("Skipped attempt already exists for user %s: with course %s:", user_id, unicode(course_id))
|
|
return
|
|
|
|
try:
|
|
# Avoid circular import
|
|
from openedx.core.djangoapps.credit.api import set_credit_requirement_status
|
|
|
|
# As a user skips the reverification it declines to fulfill the requirement so
|
|
# requirement sets to declined.
|
|
set_credit_requirement_status(
|
|
user,
|
|
course_key,
|
|
'reverification',
|
|
checkpoint.checkpoint_location,
|
|
status='declined'
|
|
)
|
|
|
|
except Exception as err: # pylint: disable=broad-except
|
|
log.error("Unable to add credit requirement status for user with id %d: %s", user_id, err)
|
|
|
|
def get_attempts(self, user_id, course_id, related_assessment_location):
|
|
"""Get re-verification attempts against a user for a given 'checkpoint'
|
|
and 'course_id'.
|
|
|
|
Args:
|
|
user_id(str): User Id string
|
|
course_id(str): A string of course id
|
|
related_assessment_location(str): Location of Reverification XBlock
|
|
|
|
Returns:
|
|
Number of re-verification attempts of a user
|
|
"""
|
|
course_key = CourseKey.from_string(course_id)
|
|
return VerificationStatus.get_user_attempts(user_id, course_key, related_assessment_location)
|