Commit a975b838 by aamir-khan

ECOM-1476: sending email on software secure response initial work

parent 411df0ae
......@@ -1107,6 +1107,22 @@ class VerificationStatus(models.Model):
status="submitted"
).count()
@classmethod
def get_location_id(cls, photo_verification):
""" Return the location id of xblock
Args:
photo_verification(SoftwareSecurePhotoVerification): SoftwareSecurePhotoVerification object
Return:
Location Id of xblock if any else empty string
"""
try:
ver_status = cls.objects.filter(checkpoint__photo_verification=photo_verification).latest()
return ver_status.location_id
except cls.DoesNotExist:
return ""
class InCourseReverificationConfiguration(ConfigurationModel):
"""Configure in-course re-verification.
......
......@@ -772,6 +772,40 @@ class VerificationStatusTest(ModuleStoreTestCase):
list(self.check_point2.checkpoint_status.all().values_list('location_id', flat=True))
)
def test_get_location_id(self):
""" Getting location id for a specific checkpoint """
# creating software secure attempt against checkpoint
self.check_point1.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
# add initial verification status for checkpoint
VerificationStatus.add_verification_status(
checkpoint=self.check_point1,
user=self.user,
status='submitted',
location_id=self.dummy_reverification_item_id_1
)
attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)
self.assertIsNotNone(VerificationStatus.get_location_id(attempt))
self.assertEqual(VerificationStatus.get_location_id(None), '')
def test_get_user_attempts(self):
# adding verification status
VerificationStatus.add_verification_status(
checkpoint=self.check_point1,
user=self.user,
status='submitted',
location_id=self.dummy_reverification_item_id_1
)
self.assertEqual(VerificationStatus.get_user_attempts(
course_key=self.course.id,
user_id=self.user.id,
related_assessment='midterm', location_id=self.dummy_reverification_item_id_1), 1)
class SkippedReverificationTest(ModuleStoreTestCase):
"""Tests for the SkippedReverification model. """
......
......@@ -10,6 +10,7 @@ from collections import namedtuple
from pytz import UTC
from django.utils import timezone
from ipware.ip import get_ip
from django.conf import settings
from django.core.urlresolvers import reverse
......@@ -58,6 +59,7 @@ from util.date_utils import get_default_time_display
from eventtracking import tracker
import analytics
from courseware.url_helpers import get_redirect_url
from django.contrib.auth.models import User
log = logging.getLogger(__name__)
......@@ -859,6 +861,96 @@ def submit_photos_for_verification(request):
return HttpResponse(200)
def _compose_message_reverification_email(
course_key, user_id, relates_assessment, photo_verification, status, is_secure
): # pylint: disable=invalid-name
""" Composes subject and message for email
Args:
course_key(CourseKey): CourseKey object
user_id(str): User Id
relates_assessment(str): related assessment name
photo_verification(QuerySet/SoftwareSecure): A query set of SoftwareSecure objects or SoftwareSecure objec
status(str): approval status
is_secure(Bool): Is running on secure protocol or not
Returns:
None if any error occurred else Tuple of subject and message strings
"""
try:
location_id = VerificationStatus.get_location_id(photo_verification)
usage_key = UsageKey.from_string(location_id)
course = modulestore().get_course(course_key)
redirect_url = get_redirect_url(course_key, usage_key.replace(course_key=course_key))
subject = "Re-verification Status"
context = {
"status": status,
"course_name": course.display_name_with_default,
"assessment": relates_assessment,
"courseware_url": redirect_url
}
reverification_block = modulestore().get_item(usage_key)
# Allowed attempts is 1 if not set on verification block
allowed_attempts = 1 if reverification_block.attempts == 0 else reverification_block.attempts
user_attempts = VerificationStatus.get_user_attempts(user_id, course_key, relates_assessment, location_id)
left_attempts = allowed_attempts - user_attempts
is_attempt_allowed = left_attempts > 0
verification_open = True
if reverification_block.due:
verification_open = timezone.now() <= reverification_block.due
context["left_attempts"] = left_attempts
context["is_attempt_allowed"] = is_attempt_allowed
context["verification_open"] = verification_open
context["due_date"] = get_default_time_display(reverification_block.due)
context["is_secure"] = is_secure
context["site"] = microsite.get_value('SITE_NAME', 'localhost')
context['platform_name'] = microsite.get_value('platform_name', settings.PLATFORM_NAME),
re_verification_link = reverse(
'verify_student_incourse_reverify',
args=(
unicode(course_key),
unicode(relates_assessment),
unicode(location_id)
)
)
context["reverify_link"] = re_verification_link
message = render_to_string('emails/reverification_processed.txt', context)
log.info(
"Sending email to User_Id=%s. Attempts left for this user are %s. "
"Allowed attempts %s. "
"Due Date %s",
str(user_id), left_attempts, allowed_attempts, str(reverification_block.due)
)
return subject, message
# Catch all exception to avoid raising back to view
except: # pylint: disable=bare-except
log.exception("The email for re-verification sending failed for user_id %s", user_id)
def _send_email(user_id, subject, message):
""" Send email to given user
Args:
user_id(str): User Id
subject(str): Subject lines of emails
message(str): Email message body
Returns:
None
"""
from_address = microsite.get_value(
'email_from_address',
settings.DEFAULT_FROM_EMAIL
)
user = User.objects.get(id=user_id)
user.email_user(subject, message, from_address)
@require_POST
@csrf_exempt # SS does its own message signing, and their API won't have a cookie value
def results_callback(request):
......@@ -910,26 +1002,24 @@ def results_callback(request):
try:
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id)
except SoftwareSecurePhotoVerification.DoesNotExist:
log.error("Software Secure posted back for receipt_id {}, but not found".format(receipt_id))
log.error("Software Secure posted back for receipt_id %s, but not found", receipt_id)
return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))
checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()
if result == "PASS":
log.debug("Approving verification for {}".format(receipt_id))
log.debug("Approving verification for %s", receipt_id)
attempt.approve()
status = "approved"
elif result == "FAIL":
log.debug("Denying verification for {}".format(receipt_id))
log.debug("Denying verification for %s", receipt_id)
attempt.deny(json.dumps(reason), error_code=error_code)
status = "denied"
elif result == "SYSTEM FAIL":
log.debug("System failure for {} -- resetting to must_retry".format(receipt_id))
log.debug("System failure for %s -- resetting to must_retry", receipt_id)
attempt.system_error(json.dumps(reason), error_code=error_code)
status = "error"
log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason)
else:
log.error("Software Secure returned unknown result {}".format(result))
log.error("Software Secure returned unknown result %s", result)
return HttpResponseBadRequest(
"Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result)
)
......@@ -939,7 +1029,22 @@ def results_callback(request):
course_id = attempt.window.course_id
course_enrollment = CourseEnrollment.get_or_create_enrollment(attempt.user, course_id)
course_enrollment.emit_event(EVENT_NAME_USER_REVERIFICATION_REVIEWED_BY_SOFTWARESECURE)
VerificationStatus.add_status_from_checkpoints(checkpoints=checkpoints, user=attempt.user, status=status)
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
if incourse_reverify_enabled:
checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()
VerificationStatus.add_status_from_checkpoints(checkpoints=checkpoints, user=attempt.user, status=status)
# If this is re-verification then send the update email
if checkpoints:
user_id = attempt.user.id
course_key = checkpoints[0].course_id
relates_assessment = checkpoints[0].checkpoint_name
subject, message = _compose_message_reverification_email(
course_key, user_id, relates_assessment, attempt, status, request.is_secure()
)
_send_email(user_id, subject, message)
return HttpResponse("OK!")
......
<%namespace file="../main.html" import="stanford_theme_enabled" />
<%! from django.utils.translation import ugettext as _ %>
% if status == "approved":
${_("Your verification for course {course_name} and assessment {assessment} "
"has been passed."
).format(course_name=course_name, assessment=assessment)}
%else:
${_("Your verification for course {course_name} and assessment {assessment} "
"has failed."
).format(course_name=course_name, assessment=assessment)}
% if not is_attempt_allowed:
${_("You have reached your allowed attempts limit. No more retakes allowed.")}
% elif not verification_open:
${_("Assessment date has passed and retake not allowed.")}
% else:
% if due_date:
${_("Assessment closes on {due_date}.".format(due_date=due_date))}
% else:
${_("Assessment is open and you have {left_attempts} attempt(s) remaining.".format(left_attempts=left_attempts))}
% endif
${_("Click on link below to re-verify:")}
% if is_secure:
https://${ site }${ reverify_link }
% else:
http://${ site }${ reverify_link }
% endif
% endif
% endif
${_("Click on link below to go to the courseware:")}
% if is_secure:
https://${ site }${ courseware_url }
% else:
http://${ site }${ courseware_url }
% endif
${_("The {platform_name} Team.").format(platform_name=platform_name)}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment