diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index b09b4c6be2..290aef9249 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -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: diff --git a/common/djangoapps/student/tests/test_change_name.py b/common/djangoapps/student/tests/test_change_name.py deleted file mode 100644 index ee6d708074..0000000000 --- a/common/djangoapps/student/tests/test_change_name.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -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) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 17a6bebf5d..81334ed54b 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -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 diff --git a/common/templates/course_modes/choose.html b/common/templates/course_modes/choose.html index f252c6cf2a..c6810697aa 100644 --- a/common/templates/course_modes/choose.html +++ b/common/templates/course_modes/choose.html @@ -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 name="bodyclass">register verification-process step-select-track <%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 name="js_extra"> @@ -65,7 +61,11 @@ from django.core.urlresolvers import reverse
- <%include file="/verify_student/_verification_header.html" args="course_name=course_name" /> +
% if "verified" in modes: @@ -129,36 +129,32 @@ from django.core.urlresolvers import reverse
% endif - % if not upgrade: - % if "honor" in modes: - - ${_("or")} - + % if "honor" in modes: + + ${_("or")} + -
-
- -

${_("Audit This Course")}

-
-

${_("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.")}

-
+
+
+ +

${_("Audit This Course")}

+
+

${_("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.")}

- -
    -
  • - -
  • -
- % endif + +
    +
  • + +
  • +
+
% endif
- - <%include file="/verify_student/_verification_support.html" />
diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index 833b8e4b95..015f3effdd 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -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']) + 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('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']) + days_good_for = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"] + attempt.created_at = datetime.now(pytz.UTC) - timedelta(days=(days_good_for + 1)) + attempt.save() - @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']) + # 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), diff --git a/lms/djangoapps/verify_student/urls.py b/lms/djangoapps/verify_student/urls.py index 59b907f7c2..05e4926ea1 100644 --- a/lms/djangoapps/verify_student/urls.py +++ b/lms/djangoapps/verify_student/urls.py @@ -89,29 +89,24 @@ urlpatterns = patterns( name="verify_student_results_callback", ), + url( + 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'^reverify$', views.ReverifyView.as_view(), name="verify_student_reverify" ), - url( - r'^reverification_confirmation$', - views.reverification_submission_confirmation, - name="verify_student_reverification_confirmation" - ), - - url( - r'^reverification_window_expired$', - views.reverification_window_expired, - name="verify_student_reverification_window_expired" - ), - - 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. diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index 967759346d..38b5d5160f 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -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 + 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. - Does not need to be attached to a particular course. - Does not need to worry about pricing """ @method_decorator(login_required) def get(self, request): """ - display this view + Render the reverification flow. + + Most of the work is done client-side by composing the same + Backbone views used in the initial verification flow. """ - context = { - "user_full_name": request.user.profile.name, - "error": False, - } - - return render_to_response("verify_student/photo_reverification.html", context) - - @method_decorator(login_required) - def post(self, request): - """ - submits the reverification to SoftwareSecure - """ - - 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) diff --git a/lms/envs/common.py b/lms/envs/common.py index 15379eb64e..35432c0363 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1307,6 +1307,20 @@ verify_student_js = [ ] 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', @@ -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' diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index 0a3e224202..169377d7b7 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.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', diff --git a/lms/static/js/spec/photocapture_spec.js b/lms/static/js/spec/photocapture_spec.js deleted file mode 100644 index 85ec82ce75..0000000000 --- a/lms/static/js/spec/photocapture_spec.js +++ /dev/null @@ -1,55 +0,0 @@ -define(['backbone', 'jquery', 'js/verify_student/photocapture'], - function (Backbone, $) { - - describe("Photo Verification", function () { - - beforeEach(function () { - setFixtures(''); - }); - - 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"); - }); - - }); - }); diff --git a/lms/static/js/spec/verify_student/make_payment_step_view_spec.js b/lms/static/js/spec/verify_student/make_payment_step_view_spec.js index 2c6e51caf3..c63915c780 100644 --- a/lms/static/js/spec/verify_student/make_payment_step_view_spec.js +++ b/lms/static/js/spec/verify_student/make_payment_step_view_spec.js @@ -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(); diff --git a/lms/static/js/spec/verify_student/pay_and_verify_view_spec.js b/lms/static/js/spec/verify_student/pay_and_verify_view_spec.js index 0c94a54ef9..29fbf52b32 100644 --- a/lms/static/js/spec/verify_student/pay_and_verify_view_spec.js +++ b/lms/static/js/spec/verify_student/pay_and_verify_view_spec.js @@ -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'); }); }); diff --git a/lms/static/js/spec/verify_student/reverify_view_spec.js b/lms/static/js/spec/verify_student/reverify_view_spec.js new file mode 100644 index 0000000000..68b41ab285 --- /dev/null +++ b/lms/static/js/spec/verify_student/reverify_view_spec.js @@ -0,0 +1,74 @@ +/** +* 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('
'); + $.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'); + }); + }); + } +); diff --git a/lms/static/js/spec/verify_student/review_photos_step_view_spec.js b/lms/static/js/spec/verify_student/review_photos_step_view_spec.js index 58d3757ab3..59f07447f1 100644 --- a/lms/static/js/spec/verify_student/review_photos_step_view_spec.js +++ b/lms/static/js/spec/verify_student/review_photos_step_view_spec.js @@ -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, diff --git a/lms/static/js/verify_student/pay_and_verify.js b/lms/static/js/verify_student/pay_and_verify.js index 0264d6d4c7..65d4781b2e 100644 --- a/lms/static/js/verify_student/pay_and_verify.js +++ b/lms/static/js/verify_student/pay_and_verify.js @@ -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'), diff --git a/lms/static/js/verify_student/photocapture.js b/lms/static/js/verify_student/photocapture.js deleted file mode 100644 index 7d3716238c..0000000000 --- a/lms/static/js/verify_student/photocapture.js +++ /dev/null @@ -1,356 +0,0 @@ -var onVideoFail = function(e) { - if(e == 'NO_DEVICES_FOUND') { - $('#no-webcam').show(); - $('#face_capture_button').hide(); - $('#photo_id_capture_button').hide(); - } - else { - console.log('Failed to get camera access!', e); - } -}; - -// Returns true if we are capable of video capture (regardless of whether the -// user has given permission). -function initVideoCapture() { - window.URL = window.URL || window.webkitURL; - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || - navigator.mozGetUserMedia || navigator.msGetUserMedia; - return !(navigator.getUserMedia == undefined); -} - -var submitReverificationPhotos = function() { - // add photos to the form - $('').attr({ - type: 'hidden', - name: 'face_image', - value: $("#face_image")[0].src, - }).appendTo("#reverify_form"); - $('').attr({ - type: 'hidden', - name: 'photo_id_image', - value: $("#photo_id_image")[0].src, - }).appendTo("#reverify_form"); - - $("#reverify_form").submit(); - -} - -var submitMidcourseReverificationPhotos = function() { - $('').attr({ - type: 'hidden', - name: 'face_image', - value: $("#face_image")[0].src, - }).appendTo("#reverify_form"); - $("#reverify_form").submit(); -} - -function showSubmissionError() { - if (xhr.status == 400) { - $('#order-error .copy p').html(xhr.responseText); - } - $('#order-error').show(); - $("html, body").animate({ scrollTop: 0 }); -} - -function submitForm(data) { - for (prop in data) { - $('').attr({ - type: 'hidden', - name: prop, - value: data[prop] - }).appendTo('#pay_form'); - } - $("#pay_form").submit(); -} - -function refereshPageMessage() { - $('#photo-error').show(); - $("html, body").animate({ scrollTop: 0 }); -} - -var submitToPaymentProcessing = function() { - $(".payment-button").addClass('is-disabled').attr('aria-disabled', true); - var contribution_input = $("input[name='contribution']:checked") - var contribution = 0; - if(contribution_input.attr('id') == 'contribution-other') { - contribution = $("input[name='contribution-other-amt']").val(); - } - else { - contribution = contribution_input.val(); - } - var course_id = $("input[name='course_id']").val(); - $.ajax({ - url: "/verify_student/create_order", - type: 'POST', - data: { - "course_id" : course_id, - "contribution": contribution, - "face_image" : $("#face_image")[0].src, - "photo_id_image" : $("#photo_id_image")[0].src - }, - success:function(data) { - if (data.success) { - submitForm(data); - } else { - refereshPageMessage(); - } - }, - error:function(xhr,status,error) { - $(".payment-button").removeClass('is-disabled').attr('aria-disabled', false); - showSubmissionError() - } - }); -} - -function doResetButton(resetButton, captureButton, approveButton, nextButtonNav, nextLink) { - approveButton.removeClass('approved'); - nextButtonNav.addClass('is-not-ready'); - nextLink.attr('href', "#"); - - captureButton.show(); - resetButton.hide(); - approveButton.hide(); -} - -function doApproveButton(approveButton, nextButtonNav, nextLink) { - nextButtonNav.removeClass('is-not-ready'); - approveButton.addClass('approved'); - nextLink.attr('href', "#next"); -} - -function doSnapshotButton(captureButton, resetButton, approveButton) { - captureButton.hide(); - resetButton.show(); - approveButton.show(); -} - -function submitNameChange(event) { - event.preventDefault(); - $("#lean_overlay").fadeOut(200); - $("#edit-name").css({ 'display' : 'none' }); - var full_name = $('input[name="name"]').val(); - var xhr = $.post( - "/change_name", - { - "new_name" : full_name, - "rationale": "Want to match ID for ID Verified Certificates." - }, - function(data) { - $('#full-name').html(full_name); - } - ) - .fail(function(jqXhr,text_status, error_thrown) { - $('.message-copy').html(jqXhr.responseText); - }); - -} - -function initSnapshotHandler(names, hasHtml5CameraSupport) { - var name = names.pop(); - if (name == undefined) { - return; - } - - var video = $('#' + name + '_video'); - var canvas = $('#' + name + '_canvas'); - var image = $('#' + name + "_image"); - var captureButton = $("#" + name + "_capture_button"); - var resetButton = $("#" + name + "_reset_button"); - var approveButton = $("#" + name + "_approve_button"); - var nextButtonNav = $("#" + name + "_next_button_nav"); - var nextLink = $("#" + name + "_next_link"); - var flashCapture = $("#" + name + "_flash"); - - var ctx = null; - if (hasHtml5CameraSupport) { - ctx = canvas[0].getContext('2d'); - } - - var localMediaStream = null; - - function snapshot(event) { - if (hasHtml5CameraSupport) { - if (localMediaStream) { - ctx.drawImage(video[0], 0, 0); - image[0].src = canvas[0].toDataURL('image/png'); - } - else { - return false; - } - video[0].pause(); - } - else { - if (flashCapture[0].cameraAuthorized()) { - image[0].src = flashCapture[0].snap(); - } - else { - return false; - } - } - - doSnapshotButton(captureButton, resetButton, approveButton); - return false; - } - - function reset() { - image[0].src = ""; - - if (hasHtml5CameraSupport) { - video[0].play(); - } - else { - flashCapture[0].reset(); - } - - doResetButton(resetButton, captureButton, approveButton, nextButtonNav, nextLink); - return false; - } - - function approve() { - doApproveButton(approveButton, nextButtonNav, nextLink) - return false; - } - - // Initialize state for this picture taker - captureButton.show(); - resetButton.hide(); - approveButton.hide(); - nextButtonNav.addClass('is-not-ready'); - nextLink.attr('href', "#"); - - // Connect event handlers... - video.click(snapshot); - captureButton.click(snapshot); - resetButton.click(reset); - approveButton.click(approve); - - // If it's flash-based, we can just immediate initialize the next one. - // If it's HTML5 based, we have to do it in the callback from getUserMedia - // so that Firefox doesn't eat the second request. - if (hasHtml5CameraSupport) { - navigator.getUserMedia({video: true}, function(stream) { - video[0].src = window.URL.createObjectURL(stream); - localMediaStream = stream; - - // We do this in a recursive call on success because Firefox seems to - // simply eat the request if you stack up two on top of each other before - // the user has a chance to approve the first one. - // - // This appears to be necessary for older versions of Firefox (before 28). - // For more info, see https://github.com/edx/edx-platform/pull/3053 - initSnapshotHandler(names, hasHtml5CameraSupport); - }, onVideoFail); - } - else { - initSnapshotHandler(names, hasHtml5CameraSupport); - } - -} - -function browserHasFlash() { - var hasFlash = false; - try { - var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); - if(fo) hasFlash = true; - } catch(e) { - if(navigator.mimeTypes["application/x-shockwave-flash"] != undefined) hasFlash = true; - } - return hasFlash; -} - -function objectTagForFlashCamera(name) { - // detect whether or not flash is available - if(browserHasFlash()) { - // I manually update this to have ?v={2,3,4, etc} to avoid caching of flash - // objects on local dev. - return ''; - } - else { - // display a message informing the user to install flash - $('#no-flash').show(); - } -} - -function waitForFlashLoad(func, flash_object) { - if(!flash_object.hasOwnProperty('percentLoaded') || flash_object.percentLoaded() < 100){ - setTimeout(function() { - waitForFlashLoad(func, flash_object); - }, - 50); - } - else { - func(flash_object); - } -} - -$(document).ready(function() { - $(".carousel-nav").addClass('sr'); - $(".payment-button").click(function(){ - analytics.pageview("Payment Form"); - submitToPaymentProcessing(); - }); - - $("#reverify_button").click(function() { - submitReverificationPhotos(); - }); - - $("#midcourse_reverify_button").click(function() { - submitMidcourseReverificationPhotos(); - }); - - // prevent browsers from keeping this button checked - $("#confirm_pics_good").prop("checked", false) - $("#confirm_pics_good").change(function() { - $(".payment-button").toggleClass('disabled'); - $("#reverify_button").toggleClass('disabled'); - $("#midcourse_reverify_button").toggleClass('disabled'); - }); - - - // add in handlers to add/remove the correct classes to the body - // when moving between steps - $('#face_next_link').click(function(){ - analytics.pageview("Capture ID Photo"); - $('#photo-error').hide(); - $('body').addClass('step-photos-id').removeClass('step-photos-cam') - }) - - $('#photo_id_next_link').click(function(){ - analytics.pageview("Review Photos"); - $('body').addClass('step-review').removeClass('step-photos-id') - }) - - // set up edit information dialog - $('#edit-name div[role="alert"]').hide(); - $('#edit-name .action-save').click(submitNameChange); - - var hasHtml5CameraSupport = initVideoCapture(); - - // If HTML5 WebRTC capture is not supported, we initialize jpegcam - if (!hasHtml5CameraSupport) { - $("#face_capture_div").html(objectTagForFlashCamera("face_flash")); - $("#photo_id_capture_div").html(objectTagForFlashCamera("photo_id_flash")); - // wait for the flash object to be loaded and then check for a camera - if(browserHasFlash()) { - waitForFlashLoad(function(flash_object) { - if(!flash_object.hasOwnProperty('hasCamera')){ - onVideoFail('NO_DEVICES_FOUND'); - } - }, $('#face_flash')[0]); - } - } - - analytics.pageview("Capture Face Photo"); - initSnapshotHandler(["photo_id", "face"], hasHtml5CameraSupport); - - $('a[rel="external"]').attr({ - title: gettext('This link will open in a new browser window/tab'), - target: '_blank' - }); - -}); diff --git a/lms/static/js/verify_student/reverify.js b/lms/static/js/verify_student/reverify.js new file mode 100644 index 0000000000..42d732ae2b --- /dev/null +++ b/lms/static/js/verify_student/reverify.js @@ -0,0 +1,44 @@ +/** + * 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, _ ); diff --git a/lms/static/js/verify_student/views/enrollment_confirmation_step_view.js b/lms/static/js/verify_student/views/enrollment_confirmation_step_view.js index 6529402f91..f9402f56f2 100644 --- a/lms/static/js/verify_student/views/enrollment_confirmation_step_view.js +++ b/lms/static/js/verify_student/views/enrollment_confirmation_step_view.js @@ -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 ); +})(); diff --git a/lms/static/js/verify_student/views/face_photo_step_view.js b/lms/static/js/verify_student/views/face_photo_step_view.js index c962eaab8e..b2654271b2 100644 --- a/lms/static/js/verify_student/views/face_photo_step_view.js +++ b/lms/static/js/verify_student/views/face_photo_step_view.js @@ -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. diff --git a/lms/static/js/verify_student/views/id_photo_step_view.js b/lms/static/js/verify_student/views/id_photo_step_view.js index 289e772017..f89bd3e7ca 100644 --- a/lms/static/js/verify_student/views/id_photo_step_view.js +++ b/lms/static/js/verify_student/views/id_photo_step_view.js @@ -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. diff --git a/lms/static/js/verify_student/views/intro_step_view.js b/lms/static/js/verify_student/views/intro_step_view.js index eaa4de4eb2..dd0e75cbc6 100644 --- a/lms/static/js/verify_student/views/intro_step_view.js +++ b/lms/static/js/verify_student/views/intro_step_view.js @@ -10,6 +10,8 @@ var edx = edx || {}; edx.verify_student.IntroStepView = edx.verify_student.StepView.extend({ + templateName: "intro_step", + defaultContext: function() { return { introTitle: '', diff --git a/lms/static/js/verify_student/views/make_payment_step_view.js b/lms/static/js/verify_student/views/make_payment_step_view.js index 68f92a11ce..20f56c03a9 100644 --- a/lms/static/js/verify_student/views/make_payment_step_view.js +++ b/lms/static/js/verify_student/views/make_payment_step_view.js @@ -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, diff --git a/lms/static/js/verify_student/views/pay_and_verify_view.js b/lms/static/js/verify_student/views/pay_and_verify_view.js index ee410ebacc..bad7d9e487 100644 --- a/lms/static/js/verify_student/views/pay_and_verify_view.js +++ b/lms/static/js/verify_student/views/pay_and_verify_view.js @@ -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; diff --git a/lms/static/js/verify_student/views/payment_confirmation_step_view.js b/lms/static/js/verify_student/views/payment_confirmation_step_view.js index ab662aa8f9..5e581dc7d7 100644 --- a/lms/static/js/verify_student/views/payment_confirmation_step_view.js +++ b/lms/static/js/verify_student/views/payment_confirmation_step_view.js @@ -10,6 +10,8 @@ var edx = edx || {}; edx.verify_student.PaymentConfirmationStepView = edx.verify_student.StepView.extend({ + templateName: "payment_confirmation_step", + defaultContext: function() { return { courseKey: '', diff --git a/lms/static/js/verify_student/views/reverify_success_step_view.js b/lms/static/js/verify_student/views/reverify_success_step_view.js new file mode 100644 index 0000000000..6d87d12eec --- /dev/null +++ b/lms/static/js/verify_student/views/reverify_success_step_view.js @@ -0,0 +1,17 @@ +/** + * 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' + }); + + })(); diff --git a/lms/static/js/verify_student/views/reverify_view.js b/lms/static/js/verify_student/views/reverify_view.js new file mode 100644 index 0000000000..10547cddd9 --- /dev/null +++ b/lms/static/js/verify_student/views/reverify_view.js @@ -0,0 +1,115 @@ +/** + * 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 = $('
').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); diff --git a/lms/static/js/verify_student/views/review_photos_step_view.js b/lms/static/js/verify_student/views/review_photos_step_view.js index b4a329abb7..a83ed906ee 100644 --- a/lms/static/js/verify_student/views/review_photos_step_view.js +++ b/lms/static/js/verify_student/views/review_photos_step_view.js @@ -10,6 +10,8 @@ var edx = edx || {}; edx.verify_student.ReviewPhotosStepView = edx.verify_student.StepView.extend({ + templateName: "review_photos_step", + defaultContext: function() { return { platformName: '', diff --git a/lms/static/js/verify_student/views/webcam_photo_view.js b/lms/static/js/verify_student/views/webcam_photo_view.js index 835f571920..ea1c84a658 100644 --- a/lms/static/js/verify_student/views/webcam_photo_view.js +++ b/lms/static/js/verify_student/views/webcam_photo_view.js @@ -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", diff --git a/lms/static/sass/views/_decoupled-verification.scss b/lms/static/sass/views/_decoupled-verification.scss index 6a38eb93dc..95d69833b9 100644 --- a/lms/static/sass/views/_decoupled-verification.scss +++ b/lms/static/sass/views/_decoupled-verification.scss @@ -1,6 +1,6 @@ // 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; diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index 14444798e3..d108f5cf20 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -53,8 +53,7 @@ // ==================== // VIEW: all verification steps -.verification-process, -.midcourse-reverification-process { +.verification-process { // reset: box-sizing (making things so right its scary) * { @@ -555,38 +554,6 @@ // ==================== - // UI: reverification message - .wrapper-reverification { - border-bottom: ($baseline/10) solid $m-pink; - margin-bottom: $baseline; - padding-bottom: $baseline; - position: relative; - - .deco-arrow { - @include triangle($baseline, $m-pink, down); - position: absolute; - bottom: -($baseline); - @include left(50%); - } - } - - .reverification { - - .message { - - .title { - @extend %hd-lv3; - color: $m-pink; - } - - .copy { - @extend %t-copy-sub1; - } - } - } - - // ==================== - // UI: slides .carousel { @@ -1965,441 +1932,6 @@ } } - } - - // VIEW: midcourse re-verification - &.midcourse-reverification-process { - - // step-dash - - &.step-dash { - - .content-main > .title { - @extend %t-title7; - display: block; - font-weight: 600; - color: $m-gray; - } - - .wrapper-reverify-open, - .wrapper-reverify-status { - display: inline-block; - vertical-align: top; - width: 48%; - } - - .copy .title { - @extend %t-title6; - font-weight: 600; - } - - .wrapper-reverify-status .title { - @extend %t-title6; - font-weight: normal; - color: $m-gray; - } - - .action-reverify { - padding: ($baseline/2) ($baseline*0.75); - } - - .reverification-list { - @include margin-right($baseline*1.5); - padding: 0; - list-style-type: none; - - .item { - box-shadow: 0 2px 5px 0 $shadow-l1 inset; - @include margin(($baseline*0.75), ($baseline*0.75), ($baseline*0.75), 0); - border: 1px solid $m-gray-t2; - - &.complete { - border: 1px solid $success-color; - - .course-info { - opacity: .5; - - .course-name { - font-weight: normal; - } - } - - .reverify-status { - @extend %t-weight4; - border-top: 1px solid $light-gray; - background-color: $m-gray-l4; - color: $success-color; - } - } - - &.pending { - border: 1px solid $warning-color; - - .course-info { - opacity: .5; - - .course-name { - font-weight: normal; - } - } - - .reverify-status { - @extend %t-weight4; - border-top: 1px solid $light-gray; - background-color: $m-gray-l4; - color: $warning-color; - } - } - - &.failed { - border: 1px solid $alert-color; - - .course-info { - opacity: .5; - - .course-name { - font-weight: normal; - } - } - - .reverify-status { - @extend %t-weight4; - border-top: 1px solid $light-gray; - background-color: $m-gray-l4; - color: $alert-color; - } - } - } - - .course-info { - margin-bottom: ($baseline/2); - padding: ($baseline/2) ($baseline*0.75); - } - - .course-name { - @extend %t-title5; - display: block; - font-weight: bold; - } - - .deadline { - @extend %copy-detail; - display: block; - margin-top: ($baseline/4); - } - - .reverify-status { - background-color: $light-gray; - padding: ($baseline/2) ($baseline*0.75); - } - } - - .support { - margin-top: $baseline; - @extend %t-copy-sub1; - } - - .wrapper-reverification-help { - margin-top: $baseline; - border-top: 1px solid $light-gray; - padding-top: ($baseline*1.5); - - .faq-item { - display: inline-block; - vertical-align: top; - width: flex-grid(4,12); - @include padding-right($baseline); - - &:last-child { - @include padding-right(0); - } - - .faq-answer { - @extend %t-copy-sub1; - } - } - } - } - - // step-photos - &.step-photos { - - .block-photo .title { - @extend %t-title4; - color: $m-blue-d1; - } - - .wrapper-task { - @include clearfix(); - width: flex-grid(12,12); - margin: $baseline 0; - - .wrapper-help { - @include float(right); - width: flex-grid(6,12); - padding: 0 $baseline; - - .help { - margin-bottom: ($baseline*1.5); - - &:last-child { - margin-bottom: 0; - } - - .title { - @extend %hd-lv3; - } - - .copy { - @extend %copy-detail; - } - - .example { - color: $m-gray-l2; - } - - // help - general list - .list-help { - margin-top: ($baseline/2); - color: $black; - - .help-item { - margin-bottom: ($baseline/4); - border-bottom: 1px solid $m-gray-l4; - padding-bottom: ($baseline/4); - - &:last-child { - margin-bottom: 0; - border-bottom: none; - padding-bottom: 0; - } - } - - .help-item-emphasis { - @extend %t-weight4; - } - } - - // help - faq - .list-faq { - margin-bottom: $baseline; - } - } - } - - .task { - @extend %ui-window; - @include float(left); - @include margin-right(flex-gutter()); - width: flex-grid(6,12); - } - - .controls { - padding: ($baseline*0.75) $baseline; - background: $m-gray-l4; - - .list-controls { - position: relative; - } - - .control { - position: absolute; - - .action { - @extend %btn-primary-blue; - padding: ($baseline/2) ($baseline*0.75); - - .icon { - @extend %t-icon4; - padding: ($baseline*.25) ($baseline*.5); - display: block; - } - } - - // STATE: hidden - &.is-hidden { - visibility: hidden; - } - - // STATE: shown - &.is-shown { - visibility: visible; - } - - // STATE: approved - &.approved { - - .action { - @extend %btn-verify-primary; - padding: ($baseline/2) ($baseline*0.75); - } - } - } - - // control - redo - .control-redo { - position: absolute; - @include left($baseline/2); - } - - // control - take/do, retake - .control-do, .control-retake { - @include left(45%); - } - - // control - approve - .control-approve { - position: absolute; - @include right($baseline/2); - } - } - - .msg { - @include clearfix(); - margin-top: ($baseline*2); - - .copy { - @include float(left); - width: flex-grid(8,12); - @include margin-right(flex-gutter()); - } - - .list-actions { - position: relative; - top: -($baseline/2); - @include float(left); - width: flex-grid(4,12); - @include text-align(right); - - .action-retakephotos a { - @extend %btn-primary-blue; - @include font-size(14); - padding: ($baseline/2) ($baseline*0.75); - } - } - } - - .msg-followup { - border-top: ($baseline/10) solid $m-gray-t0; - padding-top: $baseline; - } - } - - .review-task { - margin-bottom: ($baseline*1.5); - padding: ($baseline*0.75) $baseline; - border-radius: ($baseline/10); - background: $m-gray-l4; - - &:last-child { - margin-bottom: 0; - } - - > .title { - @extend %hd-lv3; - } - - .copy { - @extend %copy-base; - - strong { - @extend %t-weight5; - color: $m-gray-d4; - } - } - } - - - // individual task - name - .review-task-name { - @include clearfix(); - border: 1px solid $light-gray; - - .copy { - @include float(left); - width: flex-grid(8,12); - @include margin-right(flex-gutter()); - } - - .list-actions { - position: relative; - top: -($baseline); - @include float(left); - width: flex-grid(4,12); - @include text-align(right); - - .action-editname a { - @extend %btn-primary-blue; - @include font-size(14); - padding: ($baseline/2) ($baseline*0.75); - } - } - } - - .nav-wizard { - padding: ($baseline*0.75) $baseline; - - .prompt-verify { - @include float(left); - @include margin(0, flex-gutter(), 0, 0); - width: flex-grid(6,12); - - .title { - @extend %hd-lv4; - margin-bottom: ($baseline/4); - } - - .copy { - @extend %t-copy-sub1; - @extend %t-weight3; - } - - .list-actions { - margin-top: ($baseline/2); - } - - .action-verify label { - @extend %t-copy-sub1; - } - } - - .wizard-steps { - margin-top: ($baseline/2); - - .wizard-step { - @include margin-right(flex-gutter()); - display: inline-block; - vertical-align: middle; - - &:last-child { - @include margin-right(0); - } - } - } - } - - - .modal { - - fieldset { - margin-top: $baseline; - } - - .close-modal { - @include font-size(24); - color: $m-blue-d3; - - &:hover, &:focus { - color: $m-blue-d1; - border: none; - } - } - } - - } - } - - &.step-confirmation { .instruction { display: inline-block; @@ -2425,10 +1957,39 @@ margin: $baseline 0; } } + } + .reverify-success-step { + .title { + @extend %t-title4; + text-align: left; + text-transform: none; + } + + .wrapper-actions { + margin-top: 20px; + } } } +.reverify-blocked { + + @include padding(($baseline*1.5), ($baseline*1.5), ($baseline*2), ($baseline*1.5)); + + .title { + @extend %t-title4; + text-align: left; + text-transform: none; + } + + .wrapper-actions { + margin-top: 20px; + } + + .action-primary { + @extend %btn-primary-blue; + } +} //reverify notification special styles .msg-reverify { @@ -2437,13 +1998,6 @@ } } -// UI: photo reverification heading -h2.photo_verification { - @extend %t-title1; - text-align: left; - text-transform: none; -} - .facephoto.view { .wrapper-task { #facecam { diff --git a/lms/templates/verify_student/_modal_editname.html b/lms/templates/verify_student/_modal_editname.html deleted file mode 100644 index 49f26c5bd8..0000000000 --- a/lms/templates/verify_student/_modal_editname.html +++ /dev/null @@ -1,34 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> - - diff --git a/lms/templates/verify_student/_reverification_support.html b/lms/templates/verify_student/_reverification_support.html deleted file mode 100644 index bcd6039a2f..0000000000 --- a/lms/templates/verify_student/_reverification_support.html +++ /dev/null @@ -1,28 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> - -
- -
diff --git a/lms/templates/verify_student/_verification_header.html b/lms/templates/verify_student/_verification_header.html deleted file mode 100644 index db71f588ab..0000000000 --- a/lms/templates/verify_student/_verification_header.html +++ /dev/null @@ -1,31 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> - -<%namespace name='static' file='../static_content.html'/> - - - -<%block name="js_extra"> - <%static:js group='rwd_header'/> - diff --git a/lms/templates/verify_student/_verification_support.html b/lms/templates/verify_student/_verification_support.html deleted file mode 100644 index 0da3cd8aa9..0000000000 --- a/lms/templates/verify_student/_verification_support.html +++ /dev/null @@ -1,21 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> - -
- -
diff --git a/lms/templates/verify_student/face_photo_step.underscore b/lms/templates/verify_student/face_photo_step.underscore index 89297c3805..381cbce274 100644 --- a/lms/templates/verify_student/face_photo_step.underscore +++ b/lms/templates/verify_student/face_photo_step.underscore @@ -14,7 +14,7 @@
-

<%- gettext( "Take Your Photo" ) %>

+

<%- gettext( "Take Your Photo" ) %>

<%= _.sprintf( gettext( "When your face is in position, use the camera button %(icon)s below to take your photo." ), { icon: '(icon)' } ) %>

diff --git a/lms/templates/verify_student/incourse_reverify.html b/lms/templates/verify_student/incourse_reverify.html index 5814d9cf19..a6a3144bc5 100644 --- a/lms/templates/verify_student/incourse_reverify.html +++ b/lms/templates/verify_student/incourse_reverify.html @@ -1,7 +1,5 @@ <%! -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 - <%static:js group='reverify'/> + <%static:js group='incourse_reverify'/> <%block name="content"> diff --git a/lms/templates/verify_student/pay_and_verify.html b/lms/templates/verify_student/pay_and_verify.html index ada5813309..802ca32ac0 100644 --- a/lms/templates/verify_student/pay_and_verify.html +++ b/lms/templates/verify_student/pay_and_verify.html @@ -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: diff --git a/lms/templates/verify_student/photo_reverification.html b/lms/templates/verify_student/photo_reverification.html deleted file mode 100644 index 66170e7452..0000000000 --- a/lms/templates/verify_student/photo_reverification.html +++ /dev/null @@ -1,402 +0,0 @@ -<%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-photos -<%block name="pagetitle">${_("Re-Verification")} - -<%block name="js_extra"> - - - - - - -<%block name="content"> - - - - - -%if error: -
-
- -
-

${_("Error submitting your images")}

-
-

${_("Oops! Something went wrong. Please confirm your details and try again.")}

-
-
-
-
-%endif - -
-
- -
-
-
-

${_("Verify Your Identity")}

-
- ## Translators: {start_bold} and {end_bold} will be replaced with HTML tags. - ## Please do not translate these variables. -

${_("To verify your identity and continue as a verified student in this course, complete the following steps {start_bold}before the course verification deadline{end_bold}. If you do not verify your identity, you can still receive an honor code certificate for the course.").format(start_bold="", end_bold="")}

-
-
- - -
-
- -
-
-

${_("Your Progress")}

- -
    -
  1. - 1 - ${_("Current Step: ")}${_("Re-Take Photo")} -
  2. - -
  3. - 2 - ${_("Re-Take ID Photo")} -
  4. - -
  5. - 3 - ${_("Review")} -
  6. - -
  7. - - - - ${_("Confirmation")} -
  8. -
- - - - -
-
- -
-
- - -
-
- - <%include file="_reverification_support.html" /> -
-
- -<%include file="_modal_editname.html" /> - diff --git a/lms/templates/verify_student/prompt_midcourse_reverify.html b/lms/templates/verify_student/prompt_midcourse_reverify.html deleted file mode 100644 index 4b5f9f75e7..0000000000 --- a/lms/templates/verify_student/prompt_midcourse_reverify.html +++ /dev/null @@ -1,5 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> -

${_("You need to re-verify to continue")}

-

- ${_("To continue in the ID Verified track in {course}, you need to re-verify your identity by {date}. Go to URL.").format(email)} -

diff --git a/lms/templates/verify_student/reverification_confirmation.html b/lms/templates/verify_student/reverification_confirmation.html deleted file mode 100644 index c1d09eb957..0000000000 --- a/lms/templates/verify_student/reverification_confirmation.html +++ /dev/null @@ -1,79 +0,0 @@ -<%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 name="pagetitle">${_("Re-Verification Submission Confirmation")} - -<%block name="js_extra"> - - - -<%block name="content"> - -
-
- -
-
-

${_("Your Progress")}

- -
    -
  1. - 1 - ${_("Re-Take Photo")} -
  2. - -
  3. - 2 - ${_("Re-Take ID Photo")} -
  4. - -
  5. - 3 - ${_("Review")} -
  6. - -
  7. - - - - ${_("Current Step: ")}${_("Confirmation")} -
  8. -
- - - - -
-
- -
-
-
-
-
-

${_("Your Credentials Have Been Updated")}

- -
-

${_("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.")}

-
- -
    - -
-
-
-
-
-
- - <%include file="_reverification_support.html" /> -
-
- diff --git a/lms/templates/verify_student/reverification_window_expired.html b/lms/templates/verify_student/reverification_window_expired.html deleted file mode 100644 index 05a109682a..0000000000 --- a/lms/templates/verify_student/reverification_window_expired.html +++ /dev/null @@ -1,46 +0,0 @@ -<%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 name="pagetitle">${_("Re-Verification Failed")} - -<%block name="js_extra"> - - - -<%block name="content"> - -
-
- -
-
-
-
-
-

${_("Re-Verification Failed")}

- -
-

${_("Your re-verification was submitted after the re-verification deadline, and you can no longer be re-verified.")}

-

${_("Please contact support if you believe this message to be in error.")}

-
- -
    - -
-
-
-
-
-
- - <%include file="_reverification_support.html" /> -
-
- diff --git a/lms/templates/verify_student/reverify.html b/lms/templates/verify_student/reverify.html new file mode 100644 index 0000000000..3f2d0db042 --- /dev/null +++ b/lms/templates/verify_student/reverify.html @@ -0,0 +1,46 @@ +<%! +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 name="pagetitle">${_("Re-Verification")} + +<%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"]: + + % endfor + +<%block name="js_extra"> + <%static:js group='rwd_header'/> + + + + + <%static:js group='reverify'/> + + +<%block name="content"> +## Top-level wrapper for errors +## JavaScript views may append to this wrapper + + +
+ +
+ diff --git a/lms/templates/verify_student/reverify_not_allowed.html b/lms/templates/verify_student/reverify_not_allowed.html new file mode 100644 index 0000000000..c8facb3998 --- /dev/null +++ b/lms/templates/verify_student/reverify_not_allowed.html @@ -0,0 +1,28 @@ +<%! + from django.utils.translation import ugettext as _ + from django.core.urlresolvers import reverse +%> + +<%inherit file="../main.html" /> +<%block name="pagetitle">${_("Identity Verification")} + +<%block name="content"> +
+

${_("Identity Verification")}

+ +
+

+ % 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 +

+
+ + +
+ + diff --git a/lms/templates/verify_student/reverify_success_step.underscore b/lms/templates/verify_student/reverify_success_step.underscore new file mode 100644 index 0000000000..8c6e2e9fdb --- /dev/null +++ b/lms/templates/verify_student/reverify_success_step.underscore @@ -0,0 +1,11 @@ +
+

<%- gettext( "Identity Verification In Progress" ) %>

+ +
+

<%- 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." ) %>

+
+ + +
diff --git a/lms/urls.py b/lms/urls.py index d1bf250754..52103e5991 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -29,7 +29,6 @@ urlpatterns = ( url(r'^admin_dashboard$', 'dashboard.views.dashboard'), url(r'^email_confirm/(?P[^/]*)$', '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'),