Commit 8c52c92b by Will Daly

Reverification iOS support and refactor

* Delete reverification templates
* Delete photocapture.js
* Delete unused "name change" end-points
* Rebuild the reverification views using Backbone sub-views
* Stop passing template names to the JavaScript code
* Avoid hard-coding the parent view ID in the webcam view (for getting the capture click sound URL)
parent 86f17d02
......@@ -119,7 +119,6 @@ class ChooseModeView(View):
"course_num": course.display_number_with_default,
"chosen_price": chosen_price,
"error": error,
"can_audit": "audit" in modes,
"responsive": True
}
if "verified" in modes:
......
"""
Unit tests for change_name view of student.
"""
import json
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.client import Client
from django.test import TestCase
from student.tests.factories import UserFactory
from student.models import UserProfile
import unittest
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestChangeName(TestCase):
"""
Check the change_name view of student.
"""
def setUp(self):
super(TestChangeName, self).setUp()
self.student = UserFactory.create(password='test')
self.client = Client()
def test_change_name_get_request(self):
"""Get requests are not allowed in this view."""
change_name_url = reverse('change_name')
resp = self.client.get(change_name_url)
self.assertEquals(resp.status_code, 405)
def test_change_name_post_request(self):
"""Name will be changed when provided with proper data."""
self.client.login(username=self.student.username, password='test')
change_name_url = reverse('change_name')
resp = self.client.post(change_name_url, {
'new_name': 'waqas',
'rationale': 'change identity'
})
response_data = json.loads(resp.content)
user = UserProfile.objects.get(user=self.student.id)
meta = json.loads(user.meta)
self.assertEquals(user.name, 'waqas')
self.assertEqual(meta['old_names'][0][1], 'change identity')
self.assertTrue(response_data['success'])
def test_change_name_without_name(self):
"""Empty string for name is not allowed in this view."""
self.client.login(username=self.student.username, password='test')
change_name_url = reverse('change_name')
resp = self.client.post(change_name_url, {
'new_name': '',
'rationale': 'change identity'
})
response_data = json.loads(resp.content)
self.assertFalse(response_data['success'])
def test_unauthenticated(self):
"""Unauthenticated user is not allowed to call this view."""
change_name_url = reverse('change_name')
resp = self.client.post(change_name_url, {
'new_name': 'waqas',
'rationale': 'change identity'
})
self.assertEquals(resp.status_code, 404)
......@@ -2078,66 +2078,6 @@ def confirm_email_change(request, key): # pylint: disable=unused-argument
raise
# TODO: DELETE AFTER NEW ACCOUNT PAGE DONE
@ensure_csrf_cookie
@require_POST
def change_name_request(request):
""" Log a request for a new name. """
if not request.user.is_authenticated():
raise Http404
try:
pnc = PendingNameChange.objects.get(user=request.user.id)
except PendingNameChange.DoesNotExist:
pnc = PendingNameChange()
pnc.user = request.user
pnc.new_name = request.POST['new_name'].strip()
pnc.rationale = request.POST['rationale']
if len(pnc.new_name) < 2:
return JsonResponse({
"success": False,
"error": _('Name required'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
pnc.save()
# The following automatically accepts name change requests. Remove this to
# go back to the old system where it gets queued up for admin approval.
accept_name_change_by_id(pnc.id)
return JsonResponse({"success": True})
# TODO: DELETE AFTER NEW ACCOUNT PAGE DONE
def accept_name_change_by_id(uid):
"""
Accepts the pending name change request for the user represented
by user id `uid`.
"""
try:
pnc = PendingNameChange.objects.get(id=uid)
except PendingNameChange.DoesNotExist:
return JsonResponse({
"success": False,
"error": _('Invalid ID'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
user = pnc.user
u_prof = UserProfile.objects.get(user=user)
# Save old name
meta = u_prof.get_meta()
if 'old_names' not in meta:
meta['old_names'] = []
meta['old_names'].append([u_prof.name, pnc.rationale, datetime.datetime.now(UTC).isoformat()])
u_prof.set_meta(meta)
u_prof.name = pnc.new_name
u_prof.save()
pnc.delete()
return JsonResponse({"success": True})
@require_POST
@login_required
@ensure_csrf_cookie
......
......@@ -4,13 +4,9 @@ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%>
<%block name="bodyclass">register verification-process step-select-track ${'is-upgrading' if upgrade else ''}</%block>
<%block name="bodyclass">register verification-process step-select-track</%block>
<%block name="pagetitle">
% if upgrade:
${_("Upgrade Your Enrollment for {} | Choose Your Track").format(course_name)}
% else:
${_("Enroll In {} | Choose Your Track").format(course_name)}
%endif
${_("Enroll In {} | Choose Your Track").format(course_name)}
</%block>
<%block name="js_extra">
......@@ -65,7 +61,11 @@ from django.core.urlresolvers import reverse
<section class="wrapper">
<div class="wrapper-register-choose wrapper-content-main">
<article class="register-choose content-main">
<%include file="/verify_student/_verification_header.html" args="course_name=course_name" />
<header class="page-header content-main">
<h3 class="title">
${_("Congratulations! You are now enrolled in {course_name}").format(course_name=course_name)}
</h3>
</header>
<form class="form-register-choose" method="post" name="enrollment_mode_form" id="enrollment_mode_form">
% if "verified" in modes:
......@@ -129,36 +129,32 @@ from django.core.urlresolvers import reverse
</div>
% endif
% if not upgrade:
% if "honor" in modes:
<span class="deco-divider">
<span class="copy">${_("or")}</span>
</span>
% if "honor" in modes:
<span class="deco-divider">
<span class="copy">${_("or")}</span>
</span>
<div class="register-choice register-choice-audit">
<div class="wrapper-copy">
<span class="deco-ribbon"></span>
<h4 class="title">${_("Audit This Course")}</h4>
<div class="copy">
<p>${_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. If your work is satisfactory and you abide by the Honor Code, you'll receive a personalized Honor Code Certificate to showcase your achievement.")}</p>
</div>
<div class="register-choice register-choice-audit">
<div class="wrapper-copy">
<span class="deco-ribbon"></span>
<h4 class="title">${_("Audit This Course")}</h4>
<div class="copy">
<p>${_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. If your work is satisfactory and you abide by the Honor Code, you'll receive a personalized Honor Code Certificate to showcase your achievement.")}</p>
</div>
<ul class="list-actions">
<li class="action action-select">
<input type="submit" name="honor_mode" value="${_('Audit This Course')}" />
</li>
</ul>
</div>
% endif
<ul class="list-actions">
<li class="action action-select">
<input type="submit" name="honor_mode" value="${_('Audit This Course')}" />
</li>
</ul>
</div>
% endif
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
</form>
</article>
</div> <!-- /wrapper-content-main -->
<%include file="/verify_student/_verification_support.html" />
</section>
</div>
</%block>
......@@ -41,9 +41,8 @@ from student.models import CourseEnrollment
from util.date_utils import get_default_time_display
from util.testing import UrlResetMixin
from verify_student.views import (
checkout_with_ecommerce_service,
render_to_response, PayAndVerifyView, EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW,
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, _send_email, _compose_message_reverification_email
checkout_with_ecommerce_service, render_to_response, PayAndVerifyView,
_send_email, _compose_message_reverification_email
)
from verify_student.models import (
SoftwareSecurePhotoVerification, VerificationCheckpoint,
......@@ -1584,52 +1583,84 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
VerificationStatus.add_verification_status(checkpoint, self.user, "submitted")
class TestReverifyView(ModuleStoreTestCase):
class TestReverifyView(TestCase):
"""
Tests for the reverification views.
Tests for the reverification view.
Reverification occurs when a verification attempt is denied or expired,
and the student is given the option to resubmit.
"""
USERNAME = "shaftoe"
PASSWORD = "detachment-2702"
def setUp(self):
super(TestReverifyView, self).setUp()
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
success = self.client.login(username=self.USERNAME, password=self.PASSWORD)
self.assertTrue(success, msg="Could not log in")
self.user = UserFactory.create(username="rusty", password="test")
self.user.profile.name = u"Røøsty Bøøgins"
self.user.profile.save()
self.client.login(username="rusty", password="test")
self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
self.course_key = self.course.id
def test_reverify_view_can_reverify_denied(self):
# User has a denied attempt, so can reverify
attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
attempt.mark_ready()
attempt.submit()
attempt.deny("error")
self._assert_can_reverify()
@patch('verify_student.views.render_to_response', render_mock)
def test_reverify_get(self):
url = reverse('verify_student_reverify')
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
((_template, context), _kwargs) = render_mock.call_args # pylint: disable=unpacking-non-sequence
self.assertFalse(context['error'])
@patch('verify_student.views.render_to_response', render_mock)
def test_reverify_post_failure(self):
url = reverse('verify_student_reverify')
response = self.client.post(url, {'face_image': '',
'photo_id_image': ''})
self.assertEquals(response.status_code, 200)
((template, context), _kwargs) = render_mock.call_args # pylint: disable=unpacking-non-sequence
self.assertIn('photo_reverification', template)
self.assertTrue(context['error'])
def test_reverify_view_can_reverify_expired(self):
# User has a verification attempt, but it's expired
attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
attempt.mark_ready()
attempt.submit()
attempt.approve()
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
def test_reverify_post_success(self):
url = reverse('verify_student_reverify')
response = self.client.post(url, {'face_image': ',',
'photo_id_image': ','})
self.assertEquals(response.status_code, 302)
try:
verification_attempt = SoftwareSecurePhotoVerification.objects.get(user=self.user)
self.assertIsNotNone(verification_attempt)
except ObjectDoesNotExist:
self.fail('No verification object generated')
((template, context), _kwargs) = render_mock.call_args # pylint: disable=unpacking-non-sequence
self.assertIn('photo_reverification', template)
self.assertTrue(context['error'])
days_good_for = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
attempt.created_at = datetime.now(pytz.UTC) - timedelta(days=(days_good_for + 1))
attempt.save()
# Allow the student to reverify
self._assert_can_reverify()
def test_reverify_view_cannot_reverify_pending(self):
# User has submitted a verification attempt, but Software Secure has not yet responded
attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
attempt.mark_ready()
attempt.submit()
# Cannot reverify because an attempt has already been submitted.
self._assert_cannot_reverify()
def test_reverify_view_cannot_reverify_approved(self):
# Submitted attempt has been approved
attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
attempt.mark_ready()
attempt.submit()
attempt.approve()
# Cannot reverify because the user is already verified.
self._assert_cannot_reverify()
def _get_reverify_page(self):
"""
Retrieve the reverification page and return the response.
"""
url = reverse("verify_student_reverify")
return self.client.get(url)
def _assert_can_reverify(self):
"""
Check that the reverification flow is rendered.
"""
response = self._get_reverify_page()
self.assertContains(response, "reverify-container")
def _assert_cannot_reverify(self):
"""
Check that the user is blocked from reverifying.
"""
response = self._get_reverify_page()
self.assertContains(response, "reverify-blocked")
class TestInCourseReverifyView(ModuleStoreTestCase):
......@@ -1727,7 +1758,7 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
# submitting the photo verification
self.mock_tracker.track.assert_called_once_with( # pylint: disable=no-member
self.user.id, # pylint: disable=no-member
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW,
'edx.bi.reverify.started',
{
'category': "verification",
'label': unicode(self.course_key),
......@@ -1781,7 +1812,7 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
# photo verification
self.mock_tracker.track.assert_called_once_with( # pylint: disable=no-member
self.user.id,
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY,
'edx.bi.reverify.submitted',
{
'category': "verification",
'label': unicode(self.course_key),
......
......@@ -90,28 +90,23 @@ urlpatterns = patterns(
),
url(
r'^reverify$',
views.ReverifyView.as_view(),
name="verify_student_reverify"
),
url(
r'^reverification_confirmation$',
views.reverification_submission_confirmation,
name="verify_student_reverification_confirmation"
r'^submit-photos/$',
views.submit_photos_for_verification,
name="verify_student_submit_photos"
),
# End-point for reverification
# Reverification occurs when a user's initial verification attempt
# is denied or expires. The user is allowed to retry by submitting
# new photos. This is different than *in-course* reverification,
# in which a student submits only face photos, which are matched
# against the ID photo from the user's initial verification attempt.
url(
r'^reverification_window_expired$',
views.reverification_window_expired,
name="verify_student_reverification_window_expired"
r'^reverify$',
views.ReverifyView.as_view(),
name="verify_student_reverify"
),
url(
r'^submit-photos/$',
views.submit_photos_for_verification,
name="verify_student_submit_photos"
),
# Endpoint for in-course reverification
# Users are sent to this end-point from within courseware
# to re-verify their identities by re-submitting face photos.
......
......@@ -6,7 +6,6 @@ import datetime
import decimal
import json
import logging
from collections import namedtuple
from pytz import UTC
from ipware.ip import get_ip
......@@ -14,10 +13,7 @@ from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.http import (
HttpResponse, HttpResponseBadRequest,
HttpResponseRedirect, Http404
)
from django.http import HttpResponse, HttpResponseBadRequest, Http404
from django.contrib.auth.models import User
from django.shortcuts import redirect
from django.utils import timezone
......@@ -63,8 +59,6 @@ from staticfiles.storage import staticfiles_storage
log = logging.getLogger(__name__)
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW = 'edx.bi.reverify.started'
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY = 'edx.bi.reverify.submitted'
class PayAndVerifyView(View):
......@@ -156,43 +150,14 @@ class PayAndVerifyView(View):
INTRO_STEP,
]
Step = namedtuple(
'Step',
[
'title',
'template_name'
]
)
STEP_INFO = {
INTRO_STEP: Step(
title=ugettext_lazy("Intro"),
template_name="intro_step"
),
MAKE_PAYMENT_STEP: Step(
title=ugettext_lazy("Make payment"),
template_name="make_payment_step"
),
PAYMENT_CONFIRMATION_STEP: Step(
title=ugettext_lazy("Payment confirmation"),
template_name="payment_confirmation_step"
),
FACE_PHOTO_STEP: Step(
title=ugettext_lazy("Take photo"),
template_name="face_photo_step"
),
ID_PHOTO_STEP: Step(
title=ugettext_lazy("Take a photo of your ID"),
template_name="id_photo_step"
),
REVIEW_PHOTOS_STEP: Step(
title=ugettext_lazy("Review your info"),
template_name="review_photos_step"
),
ENROLLMENT_CONFIRMATION_STEP: Step(
title=ugettext_lazy("Enrollment confirmation"),
template_name="enrollment_confirmation_step"
),
STEP_TITLES = {
INTRO_STEP: ugettext_lazy("Intro"),
MAKE_PAYMENT_STEP: ugettext_lazy("Make payment"),
PAYMENT_CONFIRMATION_STEP: ugettext_lazy("Payment confirmation"),
FACE_PHOTO_STEP: ugettext_lazy("Take photo"),
ID_PHOTO_STEP: ugettext_lazy("Take a photo of your ID"),
REVIEW_PHOTOS_STEP: ugettext_lazy("Review your info"),
ENROLLMENT_CONFIRMATION_STEP: ugettext_lazy("Enrollment confirmation"),
}
# Messages
......@@ -554,8 +519,7 @@ class PayAndVerifyView(View):
return [
{
'name': step,
'title': unicode(self.STEP_INFO[step].title),
'templateName': self.STEP_INFO[step].template_name
'title': unicode(self.STEP_TITLES[step]),
}
for step in display_steps
if step not in remove_steps
......@@ -1044,85 +1008,52 @@ def results_callback(request):
class ReverifyView(View):
"""
The main reverification view. Under similar constraints as the main verification view.
Has to perform these functions:
- take new face photo
- take new id photo
- submit photos to photo verification service
Does not need to be attached to a particular course.
Does not need to worry about pricing
Reverification occurs when a user's initial verification is denied
or expires. When this happens, users can re-submit photos through
the re-verification flow.
Unlike in-course reverification, this flow requires users to submit
*both* face and ID photos. In contrast, during in-course reverification,
students submit only face photos, which are matched against the ID photo
the user submitted during initial verification.
"""
@method_decorator(login_required)
def get(self, request):
"""
display this view
"""
context = {
"user_full_name": request.user.profile.name,
"error": False,
}
return render_to_response("verify_student/photo_reverification.html", context)
Render the reverification flow.
@method_decorator(login_required)
def post(self, request):
"""
submits the reverification to SoftwareSecure
Most of the work is done client-side by composing the same
Backbone views used in the initial verification flow.
"""
try:
attempt = SoftwareSecurePhotoVerification(user=request.user)
b64_face_image = request.POST['face_image'].split(",")[1]
b64_photo_id_image = request.POST['photo_id_image'].split(",")[1]
attempt.upload_face_image(b64_face_image.decode('base64'))
attempt.upload_photo_id_image(b64_photo_id_image.decode('base64'))
attempt.mark_ready()
# save this attempt
attempt.save()
# then submit it across
attempt.submit()
return HttpResponseRedirect(reverse('verify_student_reverification_confirmation'))
except Exception:
log.exception(
"Could not submit verification attempt for user {}".format(request.user.id)
)
status, _ = SoftwareSecurePhotoVerification.user_status(request.user)
if status in ["must_reverify", "expired"]:
context = {
"user_full_name": request.user.profile.name,
"error": True,
"platform_name": settings.PLATFORM_NAME,
"capture_sound": staticfiles_storage.url("audio/camera_capture.wav"),
}
return render_to_response("verify_student/photo_reverification.html", context)
@login_required
def reverification_submission_confirmation(_request):
"""
Shows the user a confirmation page if the submission to SoftwareSecure was successful
"""
return render_to_response("verify_student/reverification_confirmation.html")
@login_required
def reverification_window_expired(_request):
"""
Displays an error page if a student tries to submit a reverification, but the window
for that reverification has already expired.
"""
# TODO need someone to review the copy for this template
return render_to_response("verify_student/reverification_window_expired.html")
return render_to_response("verify_student/reverify.html", context)
else:
context = {
"status": status
}
return render_to_response("verify_student/reverify_not_allowed.html", context)
class InCourseReverifyView(View):
"""
The in-course reverification view.
Needs to perform these functions:
- take new face photo
- retrieve the old id photo
- submit these photos to photo verification service
Does not need to worry about pricing
In-course reverification occurs while a student is taking a course.
At points in the course, students are prompted to submit face photos,
which are matched against the ID photos the user submitted during their
initial verification.
Students are prompted to enter this flow from an "In Course Reverification"
XBlock (courseware component) that course authors add to the course.
See https://github.com/edx/edx-reverification-block for more details.
"""
@method_decorator(login_required)
def get(self, request, course_id, usage_id):
......@@ -1168,9 +1099,7 @@ class InCourseReverifyView(View):
return self._redirect_no_initial_verification(user, course_key)
# emit the reverification event
self._track_reverification_events(
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, user.id, course_id, checkpoint.checkpoint_name
)
self._track_reverification_events('edx.bi.reverify.started', user.id, course_id, checkpoint.checkpoint_name)
context = {
'course_key': unicode(course_key),
......@@ -1235,7 +1164,8 @@ class InCourseReverifyView(View):
# emit the reverification event
self._track_reverification_events(
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, user.id, course_id, checkpoint.checkpoint_name
'edx.bi.reverify.submitted',
user.id, course_id, checkpoint.checkpoint_name
)
redirect_url = get_redirect_url(course_key, usage_key)
......
......@@ -1310,6 +1310,20 @@ reverify_js = [
'js/verify_student/views/error_view.js',
'js/verify_student/views/image_input_view.js',
'js/verify_student/views/webcam_photo_view.js',
'js/verify_student/views/step_view.js',
'js/verify_student/views/face_photo_step_view.js',
'js/verify_student/views/id_photo_step_view.js',
'js/verify_student/views/review_photos_step_view.js',
'js/verify_student/views/reverify_success_step_view.js',
'js/verify_student/models/verification_model.js',
'js/verify_student/views/reverify_view.js',
'js/verify_student/reverify.js',
]
incourse_reverify_js = [
'js/verify_student/views/error_view.js',
'js/verify_student/views/image_input_view.js',
'js/verify_student/views/webcam_photo_view.js',
'js/verify_student/models/reverification_model.js',
'js/verify_student/views/incourse_reverify_view.js',
'js/verify_student/incourse_reverify.js',
......@@ -1541,6 +1555,10 @@ PIPELINE_JS = {
'source_filenames': reverify_js,
'output_filename': 'js/reverify.js'
},
'incourse_reverify': {
'source_filenames': incourse_reverify_js,
'output_filename': 'js/incourse_reverify.js'
},
'ccx': {
'source_filenames': ccx_js,
'output_filename': 'js/ccx.js'
......
......@@ -61,7 +61,6 @@
// Manually specify LMS files that are not converted to RequireJS
'history': 'js/vendor/history',
'js/mustache': 'js/mustache',
'js/verify_student/photocapture': 'js/verify_student/photocapture',
'js/staff_debug_actions': 'js/staff_debug_actions',
'js/vendor/jquery.qubit': 'js/vendor/jquery.qubit',
......@@ -284,9 +283,6 @@
exports: 'js/student_profile/profile',
deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie']
},
'js/verify_student/photocapture': {
exports: 'js/verify_student/photocapture'
},
'js/staff_debug_actions': {
exports: 'js/staff_debug_actions',
deps: ['gettext']
......@@ -501,6 +497,7 @@
'gettext',
'jquery.cookie',
'jquery.url',
'string_utils',
'js/verify_student/views/step_view',
]
},
......@@ -550,6 +547,13 @@
'js/verify_student/views/step_view',
]
},
'js/verify_student/views/reverify_success_step_view': {
exports: 'edx.verify_student.ReverifySuccessStepView',
deps: [
'jquery',
'js/verify_student/views/step_view',
]
},
'js/verify_student/views/pay_and_verify_view': {
exports: 'edx.verify_student.PayAndVerifyView',
deps: [
......@@ -567,6 +571,20 @@
'js/verify_student/views/enrollment_confirmation_step_view'
]
},
'js/verify_student/views/reverify_view': {
exports: 'edx.verify_student.ReverifyView',
deps: [
'jquery',
'underscore',
'backbone',
'gettext',
'js/verify_student/models/verification_model',
'js/verify_student/views/face_photo_step_view',
'js/verify_student/views/id_photo_step_view',
'js/verify_student/views/enrollment_confirmation_step_view',
'js/verify_student/views/reverify_success_step_view'
]
},
// Student Notes
'annotator_1.2.9': {
exports: 'Annotator',
......@@ -583,7 +601,6 @@
'lms/include/js/spec/components/header/header_spec.js',
'lms/include/js/spec/components/tabbed/tabbed_view_spec.js',
'lms/include/js/spec/components/card/card_spec.js',
'lms/include/js/spec/photocapture_spec.js',
'lms/include/js/spec/staff_debug_actions_spec.js',
'lms/include/js/spec/views/notification_spec.js',
'lms/include/js/spec/views/file_uploader_spec.js',
......@@ -610,6 +627,7 @@
'lms/include/js/spec/student_profile/learner_profile_view_spec.js',
'lms/include/js/spec/student_profile/learner_profile_fields_spec.js',
'lms/include/js/spec/verify_student/pay_and_verify_view_spec.js',
'lms/include/js/spec/verify_student/reverify_view_spec.js',
'lms/include/js/spec/verify_student/webcam_photo_view_spec.js',
'lms/include/js/spec/verify_student/image_input_spec.js',
'lms/include/js/spec/verify_student/review_photos_step_view_spec.js',
......
define(['backbone', 'jquery', 'js/verify_student/photocapture'],
function (Backbone, $) {
describe("Photo Verification", function () {
beforeEach(function () {
setFixtures('<div id="order-error" style="display: none;"></div><input type="radio" name="contribution" value="35" id="contribution-35" checked="checked"><input type="radio" id="contribution-other" name="contribution" value=""><input type="text" size="9" name="contribution-other-amt" id="contribution-other-amt" value="30"><img id="face_image" src="src="data:image/png;base64,dummy"><img id="photo_id_image" src="src="data:image/png;base64,dummy"><button class="payment-button">pay button</button>');
});
it('retake photo', function () {
spyOn(window, "refereshPageMessage").andCallFake(function () {
return;
});
spyOn($, "ajax").andCallFake(function (e) {
e.success({"success": false});
});
submitToPaymentProcessing();
expect(window.refereshPageMessage).toHaveBeenCalled();
});
it('successful submission', function () {
spyOn(window, "submitForm").andCallFake(function () {
return;
});
spyOn($, "ajax").andCallFake(function (e) {
e.success({"success": true});
});
submitToPaymentProcessing();
expect(window.submitForm).toHaveBeenCalled();
expect($(".payment-button")).toHaveClass("is-disabled");
});
it('Error during process', function () {
spyOn(window, "showSubmissionError").andCallFake(function () {
return;
});
spyOn($, "ajax").andCallFake(function (e) {
e.error({});
});
spyOn($.fn, "addClass").andCallThrough();
spyOn($.fn, "removeClass").andCallThrough();
submitToPaymentProcessing();
expect(window.showSubmissionError).toHaveBeenCalled();
// make sure the button isn't disabled
expect($(".payment-button")).not.toHaveClass("is-disabled");
// but also make sure that it was disabled during the ajax call
expect($.fn.addClass).toHaveBeenCalledWith("is-disabled");
expect($.fn.removeClass).toHaveBeenCalledWith("is-disabled");
});
});
});
......@@ -29,7 +29,6 @@ define([
var createView = function( stepDataOverrides ) {
var view = new MakePaymentStepView({
el: $( '#current-step-container' ),
templateName: 'make_payment_step',
stepData: _.extend( _.clone( STEP_DATA ), stepDataOverrides ),
errorModel: new ( Backbone.Model.extend({}) )()
}).render();
......
......@@ -18,19 +18,16 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
];
var INTRO_STEP = {
templateName: "intro_step",
name: "intro-step",
title: "Intro"
};
var DISPLAY_STEPS_FOR_PAYMENT = [
{
templateName: "make_payment_step",
name: "make-payment-step",
title: "Make Payment"
},
{
templateName: "payment_confirmation_step",
name: "payment-confirmation-step",
title: "Payment Confirmation"
}
......@@ -38,22 +35,18 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
var DISPLAY_STEPS_FOR_VERIFICATION = [
{
templateName: "face_photo_step",
name: "face-photo-step",
title: "Take Face Photo"
},
{
templateName: "id_photo_step",
name: "id-photo-step",
title: "ID Photo"
},
{
templateName: "review_photos_step",
name: "review-photos-step",
title: "Review Photos"
},
{
templateName: "enrollment_confirmation_step",
name: "enrollment-confirmation-step",
title: "Enrollment Confirmation"
}
......@@ -67,7 +60,7 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
}).render();
};
var expectStepRendered = function( stepName, stepNum, numSteps ) {
var expectStepRendered = function( stepName ) {
// Expect that the step container div rendered
expect( $( '.' + stepName ).length > 0 ).toBe( true );
};
......@@ -89,27 +82,27 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
);
// Verify that the first step rendered
expectStepRendered('make-payment-step', 1, 6);
expectStepRendered('make-payment-step');
// Iterate through the steps, ensuring that each is rendered
view.nextStep();
expectStepRendered('payment-confirmation-step', 2, 6);
expectStepRendered('payment-confirmation-step');
view.nextStep();
expectStepRendered('face-photo-step', 3, 6);
expectStepRendered('face-photo-step');
view.nextStep();
expectStepRendered('id-photo-step', 4, 6);
expectStepRendered('id-photo-step');
view.nextStep();
expectStepRendered('review-photos-step', 5, 6);
expectStepRendered('review-photos-step');
view.nextStep();
expectStepRendered('enrollment-confirmation-step', 6, 6);
expectStepRendered('enrollment-confirmation-step');
// Going past the last step stays on the last step
view.nextStep();
expectStepRendered('enrollment-confirmation-step', 6, 6);
expectStepRendered('enrollment-confirmation-step');
});
it( 'renders intro and verification steps', function() {
......@@ -119,20 +112,20 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
);
// Verify that the first step rendered
expectStepRendered('intro-step', 1, 5);
expectStepRendered('intro-step');
// Iterate through the steps, ensuring that each is rendered
view.nextStep();
expectStepRendered('face-photo-step', 2, 5);
expectStepRendered('face-photo-step');
view.nextStep();
expectStepRendered('id-photo-step', 3, 5);
expectStepRendered('id-photo-step');
view.nextStep();
expectStepRendered('review-photos-step', 4, 5);
expectStepRendered('review-photos-step');
view.nextStep();
expectStepRendered('enrollment-confirmation-step', 5, 5);
expectStepRendered('enrollment-confirmation-step');
});
it( 'starts from a later step', function() {
......@@ -143,11 +136,11 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
);
// Verify that we start on the right step
expectStepRendered('payment-confirmation-step', 2, 6);
expectStepRendered('payment-confirmation-step');
// Try moving to the next step
view.nextStep();
expectStepRendered('face-photo-step', 3, 6);
expectStepRendered('face-photo-step');
});
......@@ -160,7 +153,7 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
// Jump back to the face photo step
view.goToStep('face-photo-step');
expectStepRendered('face-photo-step', 1, 4);
expectStepRendered('face-photo-step');
});
});
......
/**
* Tests for the reverification view.
**/
define(['jquery', 'js/common_helpers/template_helpers', 'js/verify_student/views/reverify_view'],
function( $, TemplateHelpers, ReverifyView ) {
'use strict';
describe( 'edx.verify_student.ReverifyView', function() {
var TEMPLATES = [
"reverify",
"webcam_photo",
"image_input",
"error",
"face_photo_step",
"id_photo_step",
"review_photos_step",
"reverify_success_step"
];
var STEP_INFO = {
'face-photo-step': {
platformName: 'edX',
},
'id-photo-step': {
platformName: 'edX',
},
'review-photos-step': {
fullName: 'John Doe',
platformName: 'edX'
},
'reverify-success-step': {
platformName: 'edX'
}
};
var createView = function() {
return new ReverifyView({stepInfo: STEP_INFO}).render();
};
var expectStepRendered = function( stepName ) {
// Expect that the step container div rendered
expect( $( '.' + stepName ).length > 0 ).toBe( true );
};
beforeEach(function() {
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'trackLink']);
setFixtures('<div id="reverify-container"></div>');
$.each( TEMPLATES, function( index, templateName ) {
TemplateHelpers.installTemplate('templates/verify_student/' + templateName );
});
});
it( 'renders verification steps', function() {
var view = createView();
// Go through the flow, verifying that each step renders
// We rely on other unit tests to check the behavior of these subviews.
expectStepRendered('face-photo-step');
view.nextStep();
expectStepRendered('id-photo-step');
view.nextStep();
expectStepRendered('review-photos-step');
view.nextStep();
expectStepRendered('reverify-success-step');
});
});
}
);
......@@ -21,7 +21,6 @@ define([
var createView = function() {
return new ReviewPhotosStepView({
el: $( '#current-step-container' ),
templateName: 'review_photos_step',
stepData: STEP_DATA,
model: new VerificationModel({
faceImage: FACE_IMAGE,
......
......@@ -74,10 +74,12 @@ var edx = edx || {};
requirements: el.data('requirements')
},
'face-photo-step': {
platformName: el.data('platform-name')
platformName: el.data('platform-name'),
captureSoundPath: el.data('capture-sound')
},
'id-photo-step': {
platformName: el.data('platform-name')
platformName: el.data('platform-name'),
captureSoundPath: el.data('capture-sound')
},
'review-photos-step': {
fullName: el.data('full-name'),
......
/**
* Reverification flow.
*
* This flow allows students who have a denied or expired verification
* to re-submit face and ID photos. It re-uses most of the same sub-views
* as the payment/verification flow.
*/
var edx = edx || {};
(function( $, _ ) {
'use strict';
var errorView,
el = $('#reverify-container');
edx.verify_student = edx.verify_student || {};
// Initialize an error view for displaying top-level error messages.
errorView = new edx.verify_student.ErrorView({
el: $('#error-container')
});
// Initialize the base view, passing in information
// from the data attributes on the parent div.
return new edx.verify_student.ReverifyView({
errorModel: errorView.model,
stepInfo: {
'face-photo-step': {
platformName: el.data('platform-name'),
captureSoundPath: el.data('capture-sound')
},
'id-photo-step': {
platformName: el.data('platform-name'),
captureSoundPath: el.data('capture-sound')
},
'review-photos-step': {
fullName: el.data('full-name'),
platformName: el.data('platform-name')
},
'reverify-success-step': {
platformName: el.data('platform-name')
}
}
}).render();
})( jQuery, _ );
......@@ -4,7 +4,7 @@
*/
var edx = edx || {};
(function( $ ) {
(function() {
'use strict';
edx.verify_student = edx.verify_student || {};
......@@ -12,6 +12,9 @@ var edx = edx || {};
// Currently, this step does not need to install any event handlers,
// since the displayed information is static.
edx.verify_student.EnrollmentConfirmationStepView = edx.verify_student.StepView.extend({
templateName: 'enrollment_confirmation_step',
postRender: function() {
// Track a virtual pageview, for easy funnel reconstruction.
window.analytics.page( 'verification', this.templateName );
......@@ -27,4 +30,4 @@ var edx = edx || {};
}
});
})( jQuery );
})();
......@@ -10,6 +10,8 @@ var edx = edx || {};
edx.verify_student.FacePhotoStepView = edx.verify_student.StepView.extend({
templateName: "face_photo_step",
defaultContext: function() {
return {
platformName: ''
......@@ -22,7 +24,8 @@ var edx = edx || {};
model: this.model,
modelAttribute: 'faceImage',
submitButton: '#next_step_button',
errorModel: this.errorModel
errorModel: this.errorModel,
captureSoundPath: this.stepData.captureSoundPath
}).render();
// Track a virtual pageview, for easy funnel reconstruction.
......
......@@ -10,6 +10,8 @@ var edx = edx || {};
edx.verify_student.IDPhotoStepView = edx.verify_student.StepView.extend({
templateName: "id_photo_step",
defaultContext: function() {
return {
platformName: ''
......@@ -22,7 +24,8 @@ var edx = edx || {};
model: this.model,
modelAttribute: 'identificationImage',
submitButton: '#next_step_button',
errorModel: this.errorModel
errorModel: this.errorModel,
captureSoundPath: this.stepData.captureSoundPath
}).render();
// Track a virtual pageview, for easy funnel reconstruction.
......
......@@ -10,6 +10,8 @@ var edx = edx || {};
edx.verify_student.IntroStepView = edx.verify_student.StepView.extend({
templateName: "intro_step",
defaultContext: function() {
return {
introTitle: '',
......
......@@ -10,6 +10,8 @@ var edx = edx || {};
edx.verify_student.MakePaymentStepView = edx.verify_student.StepView.extend({
templateName: "make_payment_step",
defaultContext: function() {
return {
isActive: true,
......
......@@ -83,7 +83,6 @@ var edx = edx || {};
subviewConfig = {
errorModel: this.errorModel,
templateName: this.displaySteps[i].templateName,
nextStepTitle: nextStepTitle,
stepData: stepData
};
......@@ -121,8 +120,6 @@ var edx = edx || {};
}
// Render the subview
// Note that this will trigger a GET request for the
// underscore template.
// When the view is rendered, it will overwrite the existing
// step in the DOM.
stepName = this.displaySteps[ this.currentStepIndex ].name;
......
......@@ -10,6 +10,8 @@ var edx = edx || {};
edx.verify_student.PaymentConfirmationStepView = edx.verify_student.StepView.extend({
templateName: "payment_confirmation_step",
defaultContext: function() {
return {
courseKey: '',
......
/**
* Show a message to the student that he/she has successfully
* submitted photos for reverification.
*/
var edx = edx || {};
(function() {
'use strict';
edx.verify_student = edx.verify_student || {};
edx.verify_student.ReverifySuccessStepView = edx.verify_student.StepView.extend({
templateName: 'reverify_success_step'
});
})();
/**
* Reverification flow.
*
* This flow allows students who have a denied or expired verification
* to re-submit face and ID photos. It re-uses most of the same sub-views
* as the payment/verification flow.
*
*/
var edx = edx || {};
(function($, _, Backbone, gettext) {
'use strict';
edx.verify_student = edx.verify_student || {};
edx.verify_student.ReverifyView = Backbone.View.extend({
el: '#reverify-container',
stepOrder: [
"face-photo-step",
"id-photo-step",
"review-photos-step",
"reverify-success-step"
],
stepViews: {},
initialize: function( obj ) {
this.errorModel = obj.errorModel || null;
this.initializeStepViews( obj.stepInfo || {} );
this.currentStepIndex = 0;
},
initializeStepViews: function( stepInfo ) {
var verificationModel, stepViewConstructors, nextStepTitles;
// We need to initialize this here, because
// outside of this method the subview classes
// might not yet have been loaded.
stepViewConstructors = {
'face-photo-step': edx.verify_student.FacePhotoStepView,
'id-photo-step': edx.verify_student.IDPhotoStepView,
'review-photos-step': edx.verify_student.ReviewPhotosStepView,
'reverify-success-step': edx.verify_student.ReverifySuccessStepView
};
nextStepTitles = [
gettext( "Take a photo of your ID" ),
gettext( "Review your info" ),
gettext( "Confirm" ),
""
];
// Create the verification model, which is shared
// among the different steps. This allows
// one step to save photos and another step
// to submit them.
verificationModel = new edx.verify_student.VerificationModel();
_.each(this.stepOrder, function(name, index) {
var stepView = new stepViewConstructors[name]({
errorModel: this.errorModel,
nextStepTitle: nextStepTitles[index],
stepData: stepInfo[name],
model: verificationModel
});
this.listenTo(stepView, 'next-step', this.nextStep);
this.listenTo(stepView, 'go-to-step', this.goToStep);
this.stepViews[name] = stepView;
}, this);
},
render: function() {
this.renderCurrentStep();
return this;
},
renderCurrentStep: function() {
var stepView, stepEl;
// Get or create the step container
stepEl = $("#current-step-container");
if (!stepEl.length) {
stepEl = $('<div id="current-step-container"></div>').appendTo(this.el);
}
// Render the step subview
// When the view is rendered, it will overwrite the existing step in the DOM.
stepView = this.stepViews[ this.stepOrder[ this.currentStepIndex ] ];
stepView.el = stepEl;
stepView.render();
},
nextStep: function() {
this.currentStepIndex = Math.min(
this.currentStepIndex + 1,
this.stepOrder.length - 1
);
this.render();
},
goToStep: function( stepName ) {
var stepIndex = _.indexOf(this.stepOrder, stepName);
if ( stepIndex >= 0 ) {
this.currentStepIndex = stepIndex;
}
this.render();
}
});
})(jQuery, _, Backbone, gettext);
......@@ -10,6 +10,8 @@ var edx = edx || {};
edx.verify_student.ReviewPhotosStepView = edx.verify_student.StepView.extend({
templateName: "review_photos_step",
defaultContext: function() {
return {
platformName: '',
......
......@@ -212,15 +212,11 @@
},
initialize: function( obj ) {
this.mainContainer = $('#pay-and-verify-container');
if (!this.mainContainer){
this.mainContainer = $('#incourse-reverify-container');
}
this.submitButton = obj.submitButton || "";
this.modelAttribute = obj.modelAttribute || "";
this.errorModel = obj.errorModel || null;
this.backend = this.backends[obj.backendName] || obj.backend;
this.captureSoundPath = this.mainContainer.data('capture-sound');
this.captureSoundPath = obj.captureSoundPath || "";
this.backend.initialize({
wrapper: "#camera",
......
// Updates for decoupled verification A/B test
.verification-process {
.pay-and-verify, .incourse-reverify {
.pay-and-verify, .incourse-reverify, .reverify {
.review {
.title.center-col {
padding: 0 calc( ( 100% - 750px ) / 2 ) 10px;
......
<%! from django.utils.translation import ugettext as _ %>
<section id="edit-name" class="modal">
<div class="inner-wrapper">
<header>
<h2>${_("Edit Your Name")}</h2>
<hr />
</header>
<div id="change_name_body">
<form id="course-checklists" class="course-checklists" method="post" action="">
<div role="alert" class="status message submission-error" tabindex="-1">
<p class="message-title">${_("The following error occurred while editing your name:")}
<span class="message-copy"> </span>
</p>
</div>
<p>${_("To uphold the credibility of {platform} certificates, all name changes will be logged and recorded.").format(platform=settings.PLATFORM_NAME)}</p>
<fieldset>
<div class="input-group">
<label for="name">${_('Full Name')}</label>
<input id="name" type="text" name="name" value="" placeholder="${user_full_name}" required aria-required="true" />
<label>${_("Reason for name change:")}</label>
<textarea id="name_rationale_field" value=""></textarea>
</div>
<div class="actions">
<button id="submit" class="action action-primary action-save">${_("Change my name")}</button>
</div>
</form>
</div>
<a href="javascript:void(0)" data-dismiss="leanModal" rel="view" class="action action-close action-editname-close close-modal">
<i class="icon fa fa-times-circle"></i>
<span class="sr">${_("close")}</span>
</a>
</section>
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper-content-supplementary">
<aside class="content-supplementary">
<ul class="list-help">
<li class="help-item help-item-whyreverify">
<h3 class="title">${_("Why Do I Need to Re-Verify My Identity?")}</h3>
<div class="copy">
<p>${_("You may need to re-verify your identity if an error occurs with your verification or if your verification has expired. All verifications expire after one year. The re-verification process is the same as the original verification process. You need a webcam and a government-issued photo ID.")}</p>
</div>
</li>
<li class="help-item help-item-technical">
<h3 class="title">${_("Having Technical Trouble?")}</h3>
<div class="copy">
<p>${_("Please make sure your browser is updated to the <strong>{a_start}most recent version possible{a_end}</strong>. Also, please make sure your <strong>webcam is plugged in, turned on, and allowed to function in your web browser (commonly adjustable in your browser settings)</strong>").format(a_start='<a rel="external" href="http://browsehappy.com/">', a_end="</a>")}</p>
</div>
</li>
<li class="help-item help-item-questions">
<h3 class="title">${_("Have questions?")}</h3>
<div class="copy">
<p>${_("Please read {a_start}our FAQs to view common questions about our certificates{a_end}.").format(a_start='<a rel="external" href="'+ marketing_link('WHAT_IS_VERIFIED_CERT') + '">', a_end="</a>")}</p>
</div>
</li>
</ul>
</aside>
</div> <!-- /wrapper-content-supplementary -->
<%! from django.utils.translation import ugettext as _ %>
<%namespace name='static' file='../static_content.html'/>
<header class="page-header content-main">
<h3 class="title">
<% course_name_html = '<span class="sts-course-name">' + course_num + '</span>' %>
<% course_display_html = course_org + "'s " + course_num + " " + course_name %>
% if upgrade:
${_("You are upgrading your enrollment for: {course_name}").format(
course_name=course_name_html
)}
% elif reverify:
${_("You are re-verifying for: {course_name}").format(
course_name=course_name_html
)}
% elif modes_dict and "professional" in modes_dict:
${_("You are enrolling in: {course_name}").format(
course_name=course_name_html
)}
% else:
${_("Congratulations! You are now enrolled in {course_display}").format(
course_display=course_display_html
)}
% endif
</h3>
</header>
<%block name="js_extra">
<%static:js group='rwd_header'/>
</%block>
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper-content-supplementary">
<aside class="content-supplementary">
<ul class="list-help">
<li class="help-item help-item-questions">
<h3 class="title">${_("Have questions?")}</h3>
<div class="copy">
<p>${_("Please read {a_start}our FAQs to view common questions about our certificates{a_end}.").format(a_start='<a rel="external" href="'+ marketing_link('WHAT_IS_VERIFIED_CERT') + '">', a_end="</a>")}</p>
</div>
</li>
<li class="help-item help-item-technical">
<h3 class="title">${_("Technical Requirements")}</h3>
<div class="copy">
<p>${_("Please make sure your browser is updated to the {a_start}most recent version possible{a_end}. Also, please make sure your <strong>webcam is plugged in, turned on, and allowed to function in your web browser (commonly adjustable in your browser settings).</strong>").format(a_start='<strong><a rel="external" href="http://browsehappy.com/">', a_end="</a></strong>")}</p>
</div>
</li>
</ul>
</aside>
</div> <!-- /wrapper-content-supplementary -->
......@@ -14,7 +14,7 @@
<div class="facephoto view">
<h2 class="title photo_verification"><%- gettext( "Take Your Photo" ) %></h2>
<h3 class="title"><%- gettext( "Take Your Photo" ) %></h2>
<div class="instruction">
<p><%= _.sprintf( gettext( "When your face is in position, use the camera button %(icon)s below to take your photo." ), { icon: '<span class="example">(<i class="icon fa fa-camera" aria-hidden="true"></i><span class="sr">icon</span>)</span>' } ) %></p>
</div>
......
<%!
import json
from django.utils.translation import ugettext as _
from verify_student.views import PayAndVerifyView
%>
<%namespace name='static' file='../static_content.html'/>
......@@ -25,7 +23,7 @@ from verify_student.views import PayAndVerifyView
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/vendor/backbone-min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script>
<%static:js group='reverify'/>
<%static:js group='incourse_reverify'/>
</%block>
<%block name="content">
......
......@@ -24,7 +24,8 @@ from verify_student.views import PayAndVerifyView
<%
template_names = (
["webcam_photo", "image_input", "error"] +
[step['templateName'] for step in display_steps]
["intro_step", "make_payment_step", "payment_confirmation_step"] +
["face_photo_step", "id_photo_step", "review_photos_step", "enrollment_confirmation_step"]
)
%>
% for template_name in template_names:
......
<%! from django.utils.translation import ugettext as _ %>
<h2 class="title">${_("You need to re-verify to continue")}</h2>
<p class='activation-message'>
${_("To continue in the ID Verified track in {course}, you need to re-verify your identity by {date}. Go to URL.").format(email)}
</p>
<%inherit file="../main.html" />
<%namespace name='static' file='/static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%>
<%block name="bodyclass">register verification-process is-not-verified step-confirmation</%block>
<%block name="pagetitle">${_("Re-Verification Submission Confirmation")}</%block>
<%block name="js_extra">
<script src="${static.url('js/vendor/responsive-carousel/responsive-carousel.js')}"></script>
<script src="${static.url('js/vendor/responsive-carousel/responsive-carousel.keybd.js')}"></script>
</%block>
<%block name="content">
<div class="container">
<section class="wrapper">
<div class="wrapper-progress">
<section class="progress">
<h3 class="sr title">${_("Your Progress")}</h3>
<ol class="progress-steps">
<li class="progress-step is-completed" id="progress-step1">
<span class="wrapper-step-number"><span class="step-number">1</span></span>
<span class="step-name">${_("Re-Take Photo")}</span>
</li>
<li class="progress-step is-completed" id="progress-step2">
<span class="wrapper-step-number"><span class="step-number">2</span></span>
<span class="step-name">${_("Re-Take ID Photo")}</span>
</li>
<li class="progress-step is-completed" id="progress-step3">
<span class="wrapper-step-number"><span class="step-number">3</span></span>
<span class="step-name">${_("Review")}</span>
</li>
<li class="progress-step is-current progress-step-icon" id="progress-step4">
<span class="wrapper-step-number"><span class="step-number">
<i class="icon fa fa-check-square-o"></i>
</span></span>
<span class="step-name"><span class="sr">${_("Current Step: ")}</span>${_("Confirmation")}</span>
</li>
</ol>
<span class="progress-sts">
<span class="progress-sts-value"></span>
</span>
</section>
</div>
<div class="wrapper-content-main">
<article class="content-main">
<section class="content-confirmation">
<div class="wrapper-view">
<div class="view">
<h3 class="title">${_("Your Credentials Have Been Updated")}</h3>
<div class="instruction">
<p>${_("We've captured your re-submitted information and will review it to verify your identity shortly. You should receive an update to your veriication status within 1-2 days. In the meantime, you still have access to all of your course content.")}</p>
</div>
<ol class="list-nav">
<li class="nav-item">
<a class="action action-primary" href="${reverse('dashboard')}">${_("Return to Your Dashboard")}</a>
</li>
</ol>
</div> <!-- /view -->
</div> <!-- /wrapper-view -->
</section>
</article>
</div> <!-- /wrapper-content-main -->
<%include file="_reverification_support.html" />
</section>
</div>
</%block>
<%inherit file="../main.html" />
<%namespace name='static' file='/static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%>
<%block name="bodyclass">register verification-process is-not-verified step-confirmation</%block>
<%block name="pagetitle">${_("Re-Verification Failed")}</%block>
<%block name="js_extra">
<script src="${static.url('js/vendor/responsive-carousel/responsive-carousel.js')}"></script>
<script src="${static.url('js/vendor/responsive-carousel/responsive-carousel.keybd.js')}"></script>
</%block>
<%block name="content">
<div class="container">
<section class="wrapper">
<div class="wrapper-content-main">
<article class="content-main">
<section class="content-confirmation">
<div class="wrapper-view">
<div class="view">
<h3 class="title">${_("Re-Verification Failed")}</h3>
<div class="instruction">
<p>${_("Your re-verification was submitted after the re-verification deadline, and you can no longer be re-verified.")}</p>
<p>${_("Please contact support if you believe this message to be in error.")}</p>
</div>
<ol class="list-nav">
<li class="nav-item">
<a class="action action-primary" href="${reverse('dashboard')}">${_("Return to Your Dashboard")}</a>
</li>
</ol>
</div> <!-- /view -->
</div> <!-- /wrapper-view -->
</section>
</article>
</div> <!-- /wrapper-content-main -->
<%include file="_reverification_support.html" />
</section>
</div>
</%block>
<%!
from django.utils.translation import ugettext as _
%>
<%namespace name='static' file='../static_content.html'/>
<%inherit file="../main.html" />
<%block name="bodyclass">register verification-process step-requirements</%block>
<%block name="pagetitle">${_("Re-Verification")}</%block>
<%block name="header_extras">
% for template_name in ["webcam_photo", "image_input", "error", "face_photo_step", "id_photo_step", "review_photos_step", "reverify_success_step"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="verify_student/${template_name}.underscore" />
</script>
% endfor
</%block>
<%block name="js_extra">
<%static:js group='rwd_header'/>
<script src="${static.url('js/vendor/underscore-min.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/vendor/backbone-min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script>
<%static:js group='reverify'/>
</%block>
<%block name="content">
## Top-level wrapper for errors
## JavaScript views may append to this wrapper
<div id="error-container" style="display: none;"></div>
<div class="container">
<section class="wrapper carousel">
## Container for the reverification view.
## The Backbone view renders itself into this <div>.
## The server can pass information to the Backbone view
## by including the information as "data-*" attributes
## of this </div>.
<div id="reverify-container" class="reverify"
data-full-name='${user_full_name}'
data-platform-name='${platform_name}'
data-capture-sound='${capture_sound}'
></div>
</section>
</div>
</%block>
<%!
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%>
<%inherit file="../main.html" />
<%block name="pagetitle">${_("Identity Verification")}</%block>
<%block name="content">
<div class="reverify-blocked">
<h1 class="title">${_("Identity Verification")}</h1>
<div class="instructions">
<p>
% if status in ["pending", "approved"]:
${_("You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 1-2 days).")}
% else:
${_("You cannot verify your identity at this time.")}
% endif
</p>
</div>
<div class="wrapper-actions">
<a class="action action-primary" href="${ reverse('dashboard') }">${_("Return to Your Dashboard")}</a>
</div>
</div>
</%block>
<div id="wrapper-reverify-success" tab-index="0" class="wrapper-view reverify-success-step">
<h1 class="title"><%- gettext( "Identity Verification In Progress" ) %></h3>
<div class="instruction">
<p><%- gettext( "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 1-2 days). In the meantime, you can still access all available course content." ) %></p>
</div>
<div class="wrapper-actions">
<a class="action action-primary" href="/dashboard"><%- gettext( "Return to Your Dashboard" ) %></a>
</div>
</div>
......@@ -29,7 +29,6 @@ urlpatterns = (
url(r'^admin_dashboard$', 'dashboard.views.dashboard'),
url(r'^email_confirm/(?P<key>[^/]*)$', 'student.views.confirm_email_change'),
url(r'^change_name$', 'student.views.change_name_request', name="change_name"),
url(r'^event$', 'track.views.user_track'),
url(r'^performance$', 'performance.views.performance_log'),
url(r'^segmentio/event$', 'track.views.segmentio.segmentio_event'),
......
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