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): ...@@ -1107,6 +1107,22 @@ class VerificationStatus(models.Model):
status="submitted" status="submitted"
).count() ).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): class InCourseReverificationConfiguration(ConfigurationModel):
"""Configure in-course re-verification. """Configure in-course re-verification.
......
...@@ -772,6 +772,40 @@ class VerificationStatusTest(ModuleStoreTestCase): ...@@ -772,6 +772,40 @@ class VerificationStatusTest(ModuleStoreTestCase):
list(self.check_point2.checkpoint_status.all().values_list('location_id', flat=True)) 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): class SkippedReverificationTest(ModuleStoreTestCase):
"""Tests for the SkippedReverification model. """ """Tests for the SkippedReverification model. """
......
...@@ -9,7 +9,8 @@ from uuid import uuid4 ...@@ -9,7 +9,8 @@ from uuid import uuid4
from django.test.utils import override_settings from django.test.utils import override_settings
import mock import mock
from mock import patch, Mock from mock import patch, Mock, ANY
from django.utils import timezone
import pytz import pytz
import ddt import ddt
from django.test.client import Client from django.test.client import Client
...@@ -24,8 +25,10 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase ...@@ -24,8 +25,10 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.factories import check_mongo_calls
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.locator import CourseLocator from opaque_keys.edx.locator import CourseLocator
from microsite_configuration import microsite
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
from commerce.tests import TEST_PAYMENT_DATA, TEST_API_URL, TEST_API_SIGNING_KEY from commerce.tests import TEST_PAYMENT_DATA, TEST_API_URL, TEST_API_SIGNING_KEY
...@@ -38,16 +41,15 @@ from embargo.test_utils import restrict_course ...@@ -38,16 +41,15 @@ from embargo.test_utils import restrict_course
from util.testing import UrlResetMixin from util.testing import UrlResetMixin
from verify_student.views import ( from verify_student.views import (
checkout_with_ecommerce_service, checkout_with_ecommerce_service,
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, render_to_response, PayAndVerifyView, EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW,
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, _send_email, _compose_message_reverification_email
PayAndVerifyView,
render_to_response,
) )
from verify_student.models import ( from verify_student.models import (
SoftwareSecurePhotoVerification, VerificationCheckpoint, SoftwareSecurePhotoVerification, VerificationCheckpoint,
InCourseReverificationConfiguration InCourseReverificationConfiguration, VerificationStatus
) )
from reverification.tests.factories import MidcourseReverificationWindowFactory from reverification.tests.factories import MidcourseReverificationWindowFactory
from util.date_utils import get_default_time_display
def mock_render_to_response(*args, **kwargs): def mock_render_to_response(*args, **kwargs):
...@@ -1531,6 +1533,121 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase): ...@@ -1531,6 +1533,121 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
self.assertEquals(response.content, 'OK!') self.assertEquals(response.content, 'OK!')
self.assertIsNotNone(CourseEnrollment.objects.get(course_id=self.course_id)) self.assertIsNotNone(CourseEnrollment.objects.get(course_id=self.course_id))
@mock.patch('verify_student.ssencrypt.has_valid_signature', mock.Mock(side_effect=mocked_has_valid_signature))
def test_in_course_reverify_disabled(self):
"""
Test for verification passed.
"""
data = {
"EdX-ID": self.receipt_id,
"Result": "PASS",
"Reason": "",
"MessageType": "You have been verified."
}
json_data = json.dumps(data)
response = self.client.post(
reverse('verify_student_results_callback'), data=json_data,
content_type='application/json',
HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
HTTP_DATE='testdate'
)
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=self.receipt_id)
self.assertEqual(attempt.status, u'approved')
self.assertEquals(response.content, 'OK!')
# Verify that photo submission confirmation email was sent
self.assertEqual(len(mail.outbox), 0)
user_status = VerificationStatus.objects.filter(user=self.user).count()
self.assertEqual(user_status, 0)
@mock.patch('verify_student.ssencrypt.has_valid_signature', mock.Mock(side_effect=mocked_has_valid_signature))
def test_pass_in_course_reverify_result(self):
"""
Test for verification passed.
"""
self.create_reverification_xblock()
incourse_reverify_enabled = InCourseReverificationConfiguration.current()
incourse_reverify_enabled.enabled = True
incourse_reverify_enabled.save()
data = {
"EdX-ID": self.receipt_id,
"Result": "PASS",
"Reason": "",
"MessageType": "You have been verified."
}
json_data = json.dumps(data)
response = self.client.post(
reverse('verify_student_results_callback'), data=json_data,
content_type='application/json',
HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
HTTP_DATE='testdate'
)
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=self.receipt_id)
self.assertEqual(attempt.status, u'approved')
self.assertEquals(response.content, 'OK!')
# Verify that photo re-verification status email was sent
self.assertEqual(len(mail.outbox), 1)
self.assertEqual("Re-verification Status", mail.outbox[0].subject)
@mock.patch('verify_student.views._send_email')
@mock.patch('verify_student.ssencrypt.has_valid_signature', mock.Mock(side_effect=mocked_has_valid_signature))
def test_reverification_on_callback(self, mock_send_email):
"""
Test software secure callback flow for re-verification.
"""
# Create the 'edx-reverification-block' in course tree
self.create_reverification_xblock()
# create dummy data for software secure photo verification result callback
data = {
"EdX-ID": self.receipt_id,
"Result": "PASS",
"Reason": "",
"MessageType": "You have been verified."
}
json_data = json.dumps(data)
response = self.client.post(
reverse('verify_student_results_callback'),
data=json_data,
content_type='application/json',
HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
HTTP_DATE='testdate'
)
self.assertEqual(response.content, 'OK!')
# now check that '_send_email' method is called on result callback
# with required parameters
subject = "Re-verification Status"
mock_send_email.assert_called_once_with(self.user.id, subject, ANY)
def create_reverification_xblock(self):
""" Create the reverification xblock
"""
# Create checkpoint
checkpoint = VerificationCheckpoint(course_id=self.course_id, checkpoint_name="midterm")
checkpoint.save()
# Add a re-verification attempt
checkpoint.add_verification_attempt(self.attempt)
# Create the 'edx-reverification-block' in course tree
section = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
subsection = ItemFactory.create(parent=section, category='sequential', display_name='Test Subsection')
vertical = ItemFactory.create(parent=subsection, category='vertical', display_name='Test Unit')
reverification = ItemFactory.create(
parent=vertical,
category='edx-reverification-block',
display_name='Test Verification Block'
)
# Add a re-verification attempt status for the user
VerificationStatus.add_verification_status(checkpoint, self.user, "submitted", reverification.location)
class TestReverifyView(ModuleStoreTestCase): class TestReverifyView(ModuleStoreTestCase):
""" """
...@@ -1922,3 +2039,273 @@ class TestInCourseReverifyView(ModuleStoreTestCase): ...@@ -1922,3 +2039,273 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
"checkpoint_name": checkpoint, "checkpoint_name": checkpoint,
"usage_id": unicode(self.reverification_location) "usage_id": unicode(self.reverification_location)
}) })
class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
"""
Test email sending on re-verification
"""
def build_course(self):
"""
Build up a course tree with a Reverificaiton xBlock.
"""
# pylint: disable=attribute-defined-outside-init
self.course_key = SlashSeparatedCourseKey("Robot", "999", "Test_Course")
self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
self.due_date = datetime(2015, 6, 22, tzinfo=pytz.UTC)
# Create the course modes
for mode in ('audit', 'honor', 'verified'):
min_price = 0 if mode in ["honor", "audit"] else 1
CourseModeFactory(mode_slug=mode, course_id=self.course_key, min_price=min_price)
# Create the 'edx-reverification-block' in course tree
section = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
subsection = ItemFactory.create(parent=section, category='sequential', display_name='Test Subsection')
vertical = ItemFactory.create(parent=subsection, category='vertical', display_name='Test Unit')
self.reverification = ItemFactory.create(
parent=vertical,
category='edx-reverification-block',
display_name='Test Verification Block',
metadata={'attempts': 3, 'due': self.due_date}
)
self.section_location = section.location
self.subsection_location = subsection.location
self.vertical_location = vertical.location
self.reverification_location = self.reverification.location
self.assessment = "midterm"
self.re_verification_link = reverse(
'verify_student_incourse_reverify',
args=(
unicode(self.course_key),
unicode(self.assessment),
unicode(self.reverification_location)
)
)
def setUp(self):
super(TestEmailMessageWithCustomICRVBlock, self).setUp()
self.build_course()
self.check_point = VerificationCheckpoint.objects.create(
course_id=self.course.id, checkpoint_name=self.assessment
)
self.check_point.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
VerificationStatus.add_verification_status(
checkpoint=self.check_point,
user=self.user,
status='submitted',
location_id=self.reverification_location
)
self.attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)
def test_approved_email_message(self):
subject, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "approved", True
)
self.assertIn(
"Your verification for course {course_name} and assessment {assessment} has been passed.".format(
course_name=self.course.display_name_with_default,
assessment=self.assessment
),
body
)
self.assertIn("Re-verification Status", subject)
def test_denied_email_message_with_valid_due_date_and_attempts_allowed(self):
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
)
self.assertIn(
"Your verification for course {course_name} and assessment {assessment} has failed.".format(
course_name=self.course.display_name_with_default,
assessment=self.assessment
),
body
)
self.assertIn("Assessment closes on {due_date}".format(due_date=get_default_time_display(self.due_date)), body)
self.assertIn("Click on link below to re-verify", body)
self.assertIn(
"https://{}{}".format(
microsite.get_value('SITE_NAME', 'localhost'), self.re_verification_link
),
body
)
def test_denied_email_message_with_close_verification_dates(self):
return_value = datetime(2016, 1, 1, tzinfo=timezone.utc)
with patch.object(timezone, 'now', return_value=return_value):
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
)
self.assertIn(
"Your verification for course {course_name} and assessment {assessment} has failed.".format(
course_name=self.course.display_name_with_default,
assessment=self.assessment
),
body
)
self.assertIn("Assessment date has passed and retake not allowed", body)
def test_check_num_queries(self):
# Get the re-verification block to check the call made
with check_mongo_calls(2):
ver_block = modulestore().get_item(self.reverification_location)
# Expect that the verification block is fetched
self.assertIsNotNone(ver_block)
class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
"""
Test for In-course Re-verification
"""
def build_course(self):
"""
Build up a course tree with a Reverificaiton xBlock.
"""
# pylint: disable=attribute-defined-outside-init
self.course_key = SlashSeparatedCourseKey("Robot", "999", "Test_Course")
self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
# Create the course modes
for mode in ('audit', 'honor', 'verified'):
min_price = 0 if mode in ["honor", "audit"] else 1
CourseModeFactory(mode_slug=mode, course_id=self.course_key, min_price=min_price)
# Create the 'edx-reverification-block' in course tree
section = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
subsection = ItemFactory.create(parent=section, category='sequential', display_name='Test Subsection')
vertical = ItemFactory.create(parent=subsection, category='vertical', display_name='Test Unit')
self.reverification = ItemFactory.create(
parent=vertical,
category='edx-reverification-block',
display_name='Test Verification Block'
)
self.section_location = section.location
self.subsection_location = subsection.location
self.vertical_location = vertical.location
self.reverification_location = self.reverification.location
self.assessment = "midterm"
self.re_verification_link = reverse(
'verify_student_incourse_reverify',
args=(
unicode(self.course_key),
unicode(self.assessment),
unicode(self.reverification_location)
)
)
def setUp(self):
super(TestEmailMessageWithDefaultICRVBlock, self).setUp()
self.build_course()
self.check_point = VerificationCheckpoint.objects.create(
course_id=self.course.id, checkpoint_name=self.assessment
)
self.check_point.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
self.attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)
def test_denied_email_message_with_no_attempt_allowed(self):
VerificationStatus.add_verification_status(
checkpoint=self.check_point,
user=self.user,
status='submitted',
location_id=self.reverification_location
)
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
)
self.assertIn(
"Your verification for course {course_name} and assessment {assessment} has failed.".format(
course_name=self.course.display_name_with_default,
assessment=self.assessment
),
body
)
self.assertIn("You have reached your allowed attempts limit. No more retakes allowed.", body)
def test_due_date(self):
self.reverification.due = datetime.now()
self.reverification.save()
VerificationStatus.add_verification_status(
checkpoint=self.check_point,
user=self.user,
status='submitted',
location_id=self.reverification_location
)
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
)
self.assertIn(
"Your verification for course {course_name} and assessment {assessment} has failed.".format(
course_name=self.course.display_name_with_default,
assessment=self.assessment
),
body
)
self.assertIn("You have reached your allowed attempts limit. No more retakes allowed.", body)
def test_denied_email_message_with_no_due_date(self):
VerificationStatus.add_verification_status(
checkpoint=self.check_point,
user=self.user,
status='error',
location_id=self.reverification_location
)
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
)
self.assertIn(
"Your verification for course {course_name} and assessment {assessment} has failed.".format(
course_name=self.course.display_name_with_default,
assessment=self.assessment
),
body
)
self.assertIn("Assessment is open and you have 1 attempt(s) remaining.", body)
self.assertIn("Click on link below to re-verify", body)
self.assertIn(
"https://{}{}".format(
microsite.get_value('SITE_NAME', 'localhost'), self.re_verification_link
),
body
)
def test_error_on_compose_email(self):
resp = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
)
self.assertIsNone(resp)
...@@ -10,6 +10,7 @@ from collections import namedtuple ...@@ -10,6 +10,7 @@ from collections import namedtuple
from pytz import UTC from pytz import UTC
from django.utils import timezone
from ipware.ip import get_ip from ipware.ip import get_ip
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -58,6 +59,7 @@ from util.date_utils import get_default_time_display ...@@ -58,6 +59,7 @@ from util.date_utils import get_default_time_display
from eventtracking import tracker from eventtracking import tracker
import analytics import analytics
from courseware.url_helpers import get_redirect_url from courseware.url_helpers import get_redirect_url
from django.contrib.auth.models import User
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -859,6 +861,96 @@ def submit_photos_for_verification(request): ...@@ -859,6 +861,96 @@ def submit_photos_for_verification(request):
return HttpResponse(200) 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 @require_POST
@csrf_exempt # SS does its own message signing, and their API won't have a cookie value @csrf_exempt # SS does its own message signing, and their API won't have a cookie value
def results_callback(request): def results_callback(request):
...@@ -910,26 +1002,24 @@ def results_callback(request): ...@@ -910,26 +1002,24 @@ def results_callback(request):
try: try:
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id) attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id)
except SoftwareSecurePhotoVerification.DoesNotExist: 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)) return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))
checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()
if result == "PASS": if result == "PASS":
log.debug("Approving verification for {}".format(receipt_id)) log.debug("Approving verification for %s", receipt_id)
attempt.approve() attempt.approve()
status = "approved" status = "approved"
elif result == "FAIL": 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) attempt.deny(json.dumps(reason), error_code=error_code)
status = "denied" status = "denied"
elif result == "SYSTEM FAIL": 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) attempt.system_error(json.dumps(reason), error_code=error_code)
status = "error" status = "error"
log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason) log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason)
else: else:
log.error("Software Secure returned unknown result {}".format(result)) log.error("Software Secure returned unknown result %s", result)
return HttpResponseBadRequest( return HttpResponseBadRequest(
"Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result) "Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result)
) )
...@@ -939,7 +1029,22 @@ def results_callback(request): ...@@ -939,7 +1029,22 @@ def results_callback(request):
course_id = attempt.window.course_id course_id = attempt.window.course_id
course_enrollment = CourseEnrollment.get_or_create_enrollment(attempt.user, course_id) course_enrollment = CourseEnrollment.get_or_create_enrollment(attempt.user, course_id)
course_enrollment.emit_event(EVENT_NAME_USER_REVERIFICATION_REVIEWED_BY_SOFTWARESECURE) 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!") 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