From bf210398e6970c42f5573627e14a5162def25e2c Mon Sep 17 00:00:00 2001 From: Julia Hansbrough <julia@edx.org> Date: Wed, 22 Jan 2014 22:16:01 +0000 Subject: [PATCH] Tests and cleanup Improved tests, fixed javascript for photocapture, removed extraneous photocapture code, added time & course numbers to templates for design --- common/djangoapps/student/views.py | 93 ++--- lms/djangoapps/verify_student/exceptions.py | 9 + ...onwindow__add_field_softwaresecurephoto.py | 4 +- lms/djangoapps/verify_student/models.py | 38 ++- .../verify_student/tests/test_models.py | 4 +- .../verify_student/tests/test_views.py | 33 +- lms/djangoapps/verify_student/urls.py | 6 + lms/djangoapps/verify_student/views.py | 28 +- lms/static/js/verify_student/photocapture.js | 20 +- .../js/verify_student/photocapturebasic2.js | 322 ------------------ .../_dashboard_prompt_midcourse_reverify.html | 2 +- .../midcourse_photo_reverification.html | 4 +- .../midcourse_reverify_dash.html | 2 +- .../reverification_window_expired.html | 45 +++ 14 files changed, 201 insertions(+), 409 deletions(-) create mode 100644 lms/djangoapps/verify_student/exceptions.py delete mode 100644 lms/static/js/verify_student/photocapturebasic2.js create mode 100644 lms/templates/verify_student/reverification_window_expired.html diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 585da3d7136..bbbad37f04c 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -182,6 +182,52 @@ def cert_info(user, course): return _cert_info(user, course, certificate_status_for_student(user, course.id)) +def reverification_info(user, course, enrollment): + """ + If "user" currently needs to be reverified in "course", this returns a four-tuple with re-verification + info for views to display. Else, returns None. + + Four-tuple data: (course_id, course_display_name, reverification_end_date, reverification_status) + """ + # IF the reverification window is open + if (MidcourseReverificationWindow.window_open_for_course(course.id)): + # AND the user is actually verified-enrolled AND they don't have a pending reverification already + window = MidcourseReverificationWindow.get_window(course.id, datetime.datetime.now(UTC)) + if (enrollment.mode == "verified" and not SoftwareSecurePhotoVerification.user_has_valid_or_pending(user, window=window)): + window = MidcourseReverificationWindow.get_window(course.id, datetime.datetime.now(UTC)) + return ( + course.id, + course.display_name, + course.number, + window.end_date.strftime('%B %d, %Y %X %p'), + "must_reverify" # TODO: reflect more states than just "must_reverify" has_valid_or_pending (must show failure) + ) + return None + + +def get_course_enrollment_pairs(user, course_org_filter, org_filter_out_set): + course_enrollment_pairs = [] + for enrollment in CourseEnrollment.enrollments_for_user(user): + try: + course = course_from_id(enrollment.course_id) + + # if we are in a Microsite, then filter out anything that is not + # attributed (by ORG) to that Microsite + if course_org_filter and course_org_filter != course.location.org: + continue + # Conversely, if we are not in a Microsite, then let's filter out any enrollments + # with courses attributed (by ORG) to Microsites + elif course.location.org in org_filter_out_set: + continue + + course_enrollment_pairs.append((course, enrollment)) + except ItemNotFoundError: + log.error("User {0} enrolled in non-existent course {1}" + .format(user.username, enrollment.course_id)) + return course_enrollment_pairs + + + def _cert_info(user, course, cert_status): """ Implements the logic for cert_info -- split out for testing. @@ -323,11 +369,6 @@ def complete_course_mode_info(course_id, enrollment): def dashboard(request): user = request.user - # Build our (course, enrollment) list for the user, but ignore any courses that no - # longer exist (because the course IDs have changed). Still, we don't delete those - # enrollments, because it could have been a data push snafu. - course_enrollment_pairs = [] - # for microsites, we want to filter and only show enrollments for courses within # the microsites 'ORG' course_org_filter = MicrositeConfiguration.get_microsite_configuration_value('course_org_filter') @@ -340,23 +381,10 @@ def dashboard(request): if course_org_filter: org_filter_out_set.remove(course_org_filter) - for enrollment in CourseEnrollment.enrollments_for_user(user): - try: - course = course_from_id(enrollment.course_id) - - # if we are in a Microsite, then filter out anything that is not - # attributed (by ORG) to that Microsite - if course_org_filter and course_org_filter != course.location.org: - continue - # Conversely, if we are not in a Microsite, then let's filter out any enrollments - # with courses attributed (by ORG) to Microsites - elif course.location.org in org_filter_out_set: - continue - - course_enrollment_pairs.append((course, enrollment)) - except ItemNotFoundError: - log.error(u"User {0} enrolled in non-existent course {1}" - .format(user.username, enrollment.course_id)) + # Build our (course, enrollment) list for the user, but ignore any courses that no + # longer exist (because the course IDs have changed). Still, we don't delete those + # enrollments, because it could have been a data push snafu. + course_enrollment_pairs = get_course_enrollment_pairs(user, course_org_filter, org_filter_out_set) course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True) @@ -392,25 +420,12 @@ def dashboard(request): # TODO: make this banner appear at the top of courseware as well verification_status, verification_msg = SoftwareSecurePhotoVerification.user_status(user) - # TODO: Factor this out into a function; I'm pretty sure there's code duplication floating around... + # Gets data for midcourse reverifications, if any are necessary or have failed reverify_course_data = [] for (course, enrollment) in course_enrollment_pairs: - - # IF the reverification window is open - if (MidcourseReverificationWindow.window_open_for_course(course.id)): - # AND the user is actually verified-enrolled AND they don't have a pending reverification already - window = MidcourseReverificationWindow.get_window(course.id, datetime.datetime.now(UTC)) - if (enrollment.mode == "verified" and not SoftwareSecurePhotoVerification.user_has_valid_or_pending(user, window=window)): - window = MidcourseReverificationWindow.get_window(course.id, datetime.datetime.now(UTC)) - status_for_window = SoftwareSecurePhotoVerification.user_status(user, window=window) - reverify_course_data.append( - ( - course.id, - course.display_name, - window.end_date, - "must_reverify" # TODO: reflect more states than just "must_reverify" has_valid_or_pending (must show failure) - ) - ) + info = reverification_info(user, course, enrollment) + if info: + reverify_course_data.append(info) show_refund_option_for = frozenset(course.id for course, _enrollment in course_enrollment_pairs if _enrollment.refundable()) diff --git a/lms/djangoapps/verify_student/exceptions.py b/lms/djangoapps/verify_student/exceptions.py new file mode 100644 index 00000000000..d31fdb6a6d0 --- /dev/null +++ b/lms/djangoapps/verify_student/exceptions.py @@ -0,0 +1,9 @@ +""" +Exceptions for the verify student app +""" +# (Exception Class Names are sort of self-explanatory, so skipping docstring requirement) +# pylint: disable=C0111 + + +class WindowExpiredException(Exception): + pass diff --git a/lms/djangoapps/verify_student/migrations/0002_auto__add_midcoursereverificationwindow__add_field_softwaresecurephoto.py b/lms/djangoapps/verify_student/migrations/0002_auto__add_midcoursereverificationwindow__add_field_softwaresecurephoto.py index 92ec4fdb4c2..537cd36c3d0 100644 --- a/lms/djangoapps/verify_student/migrations/0002_auto__add_midcoursereverificationwindow__add_field_softwaresecurephoto.py +++ b/lms/djangoapps/verify_student/migrations/0002_auto__add_midcoursereverificationwindow__add_field_softwaresecurephoto.py @@ -22,7 +22,6 @@ class Migration(SchemaMigration): self.gf('django.db.models.fields.related.ForeignKey')(to=orm['verify_student.MidcourseReverificationWindow'], null=True), keep_default=False) - def backwards(self, orm): # Deleting model 'MidcourseReverificationWindow' db.delete_table('verify_student_midcoursereverificationwindow') @@ -30,7 +29,6 @@ class Migration(SchemaMigration): # Deleting field 'SoftwareSecurePhotoVerification.window' db.delete_column('verify_student_softwaresecurephotoverification', 'window_id') - models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -97,4 +95,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['verify_student'] \ No newline at end of file + complete_apps = ['verify_student'] diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index b406cfa25b8..41d4cb650df 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -23,7 +23,7 @@ import pytz import requests from django.conf import settings -from django.core.exceptions import ObjectDoesNotExist, ValidationError +from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models from django.contrib.auth.models import User @@ -71,17 +71,9 @@ class MidcourseReverificationWindow(models.Model): Returns a boolean, True if the course is currently asking for reverification, else False. """ now = datetime.now(pytz.UTC) - - try: - cls.objects.get( - course_id=course_id, - start_date__lte=now, - end_date__gte=now, - ) - except(ObjectDoesNotExist): - return False - - return True + if cls.get_window(course_id, now): + return True + return False @classmethod def get_window(cls, course_id, date): @@ -244,9 +236,13 @@ class PhotoVerification(StatusModel): @classmethod def user_is_verified(cls, user, earliest_allowed_date=None, window=None): """ - Return whether or not a user has satisfactorily proved their - identity wrt to the INITIAL verification. Depending on the policy, - this can expire after some period of time, so a user might have to renew periodically. + Return whether or not a user has satisfactorily proved their identity. + Depending on the policy, this can expire after some period of time, so + a user might have to renew periodically. + + If window=None, then this will check for the user's *initial* verification. + If window is set to anything else, it will check for the reverification + associated with that window. """ return cls.objects.filter( user=user, @@ -264,6 +260,10 @@ class PhotoVerification(StatusModel): have been submitted but had an non-user error when it was being submitted. It's basically any situation in which the user has signed off on the contents of the attempt, and we have not yet received a denial. + + If window=None, this will check for the user's *initial* verification. If + window is anything else, this will check for the reverification associated + with that window. """ if window: valid_statuses = ['submitted', 'approved'] @@ -280,8 +280,11 @@ class PhotoVerification(StatusModel): @classmethod def active_for_user(cls, user, window=None): """ - Return the most recent INITIAL PhotoVerification that is marked ready (i.e. the + Return the most recent PhotoVerification that is marked ready (i.e. the user has said they're set, but we haven't submitted anything yet). + + If window=None, this checks for the original verification. If window is set to + anything else, this will check for the reverification associated with that window. """ # This should only be one at the most, but just in case we create more # by mistake, we'll grab the most recently created one. @@ -301,6 +304,9 @@ class PhotoVerification(StatusModel): If the verification has been approved, returns 'approved' If the verification process is still ongoing, returns 'pending' If the verification has been denied and the user must resubmit photos, returns 'must_reverify' + + If window=None, this checks initial verifications + If window is set, this checks for the reverification associated with that window """ status = 'none' error_msg = '' diff --git a/lms/djangoapps/verify_student/tests/test_models.py b/lms/djangoapps/verify_student/tests/test_models.py index 26c4149dc3b..e2fb298ed94 100644 --- a/lms/djangoapps/verify_student/tests/test_models.py +++ b/lms/djangoapps/verify_student/tests/test_models.py @@ -438,6 +438,7 @@ class TestMidcourseReverificationWindow(TestCase): @patch('verify_student.models.Key', new=MockKey) @patch('verify_student.models.requests.post', new=mock_software_secure_post) class TestMidcourseReverification(TestCase): + """ Tests for methods that are specific to midcourse SoftwareSecurePhotoVerification objects """ def setUp(self): self.course_id = "MITx/999/Robot_Super_Course" self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') @@ -527,6 +528,3 @@ class TestMidcourseReverification(TestCase): attempt.status = "approved" attempt.save() assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user=self.user, window=window)) - - def test_active_for_user(self): - pass diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index a6dd07b4892..5ab39dda9bb 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -11,6 +11,8 @@ verify_student/start?course_id=MITx/6.002x/2013_Spring # create """ import urllib from mock import patch, Mock, ANY +import pytz +from datetime import timedelta, datetime from django.test import TestCase from django.test.utils import override_settings @@ -114,24 +116,14 @@ class TestReverifyView(TestCase): self.assertIsNotNone(verification_attempt) except ObjectDoesNotExist: self.fail('No verification object generated') + ((template, context), _kwargs) = render_mock.call_args self.assertIn('photo_reverification', template) self.assertTrue(context['error']) - @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') - @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) class TestMidCourseReverifyView(TestCase): + """ Tests for the midcourse reverification views """ def setUp(self): self.user = UserFactory.create(username="rusty", password="test") self.client.login(username="rusty", password="test") @@ -149,16 +141,29 @@ class TestMidCourseReverifyView(TestCase): @patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True}) def test_midcourse_reverify_post_success(self): + window = MidcourseReverificationWindowFactory(course_id=self.course_id) url = reverse('verify_student_midcourse_reverify', kwargs={'course_id': self.course_id}) response = self.client.post(url, {'face_image': ','}) self.assertEquals(response.status_code, 302) try: - verification_attempt = SoftwareSecurePhotoVerification.objects.get(user=self.user) + verification_attempt = SoftwareSecurePhotoVerification.objects.get(user=self.user, window=window) self.assertIsNotNone(verification_attempt) except ObjectDoesNotExist: self.fail('No verification object generated') - # TODO make this test more detailed + @patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True}) + def test_midcourse_reverify_post_failure_expired_window(self): + window = MidcourseReverificationWindowFactory( + course_id=self.course_id, + start_date=datetime.now(pytz.UTC) - timedelta(days=100), + end_date=datetime.now(pytz.UTC) - timedelta(days=50), + ) + url = reverse('verify_student_midcourse_reverify', kwargs={'course_id': self.course_id}) + response = self.client.post(url, {'face_image': ','}) + self.assertEquals(response.status_code, 302) + with self.assertRaises(ObjectDoesNotExist): + SoftwareSecurePhotoVerification.objects.get(user=self.user, window=window) + @patch('verify_student.views.render_to_response', render_mock) def test_midcourse_reverify_dash(self): url = reverse('verify_student_midcourse_reverify_dash') diff --git a/lms/djangoapps/verify_student/urls.py b/lms/djangoapps/verify_student/urls.py index 40b434ecea1..fe9b233cb11 100644 --- a/lms/djangoapps/verify_student/urls.py +++ b/lms/djangoapps/verify_student/urls.py @@ -64,4 +64,10 @@ urlpatterns = patterns( views.midcourse_reverify_dash, name="verify_student_midcourse_reverify_dash" ), + + url( + r'^reverification_window_expired$', + views.reverification_window_expired, + name="verify_student_reverification_window_expired" + ), ) diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index 283d03fa64f..15c5e04d7ec 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -34,6 +34,7 @@ from verify_student.models import ( ) import ssencrypt from xmodule.modulestore.exceptions import ItemNotFoundError +from .exceptions import WindowExpiredException log = logging.getLogger(__name__) @@ -362,9 +363,11 @@ class MidCourseReverifyView(View): submits the reverification to SoftwareSecure """ try: - # TODO look at this more carefully! #1 testing candidate now = datetime.datetime.now(UTC) - attempt = SoftwareSecurePhotoVerification(user=request.user, window=MidcourseReverificationWindow.get_window(course_id, now)) + window = MidcourseReverificationWindow.get_window(course_id, now) + if window is None: + raise WindowExpiredException + attempt = SoftwareSecurePhotoVerification(user=request.user, window=window) b64_face_image = request.POST['face_image'].split(",")[1] attempt.upload_face_image(b64_face_image.decode('base64')) @@ -374,6 +377,13 @@ class MidCourseReverifyView(View): attempt.save() attempt.submit() return HttpResponseRedirect(reverse('verify_student_midcourse_reverification_confirmation')) + + except WindowExpiredException: + log.exception( + "User {} attempted to re-verify, but the window expired before the attempt".format(request.user.id) + ) + return HttpResponseRedirect(reverse('verify_student_reverification_window_expired')) + except Exception: log.exception( "Could not submit verification attempt for user {}".format(request.user.id) @@ -390,7 +400,6 @@ def midcourse_reverify_dash(_request): Shows the "course reverification dashboard", which displays the reverification status (must reverify, pending, approved, failed, etc) of all courses in which a student has a verified enrollment. """ - # TODO same comment as in student/views.py: need to factor out this functionality user = _request.user course_enrollment_pairs = [] for enrollment in CourseEnrollment.enrollments_for_user(user): @@ -406,7 +415,8 @@ def midcourse_reverify_dash(_request): ( course.id, course.display_name, - MidcourseReverificationWindow.get_window(course.id, datetime.datetime.now(UTC)).end_date, + course.number, + MidcourseReverificationWindow.get_window(course.id, datetime.datetime.now(UTC)).end_date.strftime('%B %d, %Y %X %p'), "must_reverify" ) ) @@ -431,3 +441,13 @@ def midcourse_reverification_confirmation(_request): Shows the user a confirmation page if the submission to SoftwareSecure was successful """ return render_to_response("verify_student/midcourse_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") diff --git a/lms/static/js/verify_student/photocapture.js b/lms/static/js/verify_student/photocapture.js index 4db15e9d51f..f8c89ae0db7 100644 --- a/lms/static/js/verify_student/photocapture.js +++ b/lms/static/js/verify_student/photocapture.js @@ -35,15 +35,22 @@ var submitReverificationPhotos = function() { } +var submitMidcourseReverificationPhotos = function() { + $('<input>').attr({ + type: 'hidden', + name: 'face_image', + value: $("#face_image")[0].src, + }).appendTo("#reverify_form"); + $("#reverify_form").submit(); +} + var submitToPaymentProcessing = function() { var contribution_input = $("input[name='contribution']:checked") var contribution = 0; - if(contribution_input.attr('id') == 'contribution-other') - { + if(contribution_input.attr('id') == 'contribution-other') { contribution = $("input[name='contribution-other-amt']").val(); } - else - { + else { contribution = contribution_input.val(); } var course_id = $("input[name='course_id']").val(); @@ -276,11 +283,16 @@ $(document).ready(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() { $("#pay_button").toggleClass('disabled'); $("#reverify_button").toggleClass('disabled'); + $("#midcourse_reverify_button").toggleClass('disabled'); }); diff --git a/lms/static/js/verify_student/photocapturebasic2.js b/lms/static/js/verify_student/photocapturebasic2.js deleted file mode 100644 index 7bf34582cd1..00000000000 --- a/lms/static/js/verify_student/photocapturebasic2.js +++ /dev/null @@ -1,322 +0,0 @@ -var onVideoFail = function(e) { - if(e == 'NO_DEVICES_FOUND') { - $('#no-webcam').show(); - $('#face_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 - $('<input>').attr({ - type: 'hidden', - name: 'face_image', - value: $("#face_image")[0].src, - }).appendTo("#reverify_form"); - // there is a change here - $("#reverify_form").submit(); - -} - -var submitToPaymentProcessing = function() { - 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(); - var xhr = $.post( - "/verify_student/create_order", - { - "course_id" : course_id, - "contribution": contribution, - "face_image" : $("#face_image")[0].src, - // there is a change here - }, - function(data) { - for (prop in data) { - $('<input>').attr({ - type: 'hidden', - name: prop, - value: data[prop] - }).appendTo('#pay_form'); - } - } - ) - .done(function(data) { - $("#pay_form").submit(); - }) - .fail(function(jqXhr,text_status, error_thrown) { - if(jqXhr.status == 400) { - $('#order-error .copy p').html(jqXhr.responseText); - } - $('#order-error').show(); - $("html, body").animate({ scrollTop: 0 }); - }); -} - -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); - // TODO put this back eventually - image[0] = image[0]; - image[0].src = image[0].src; - 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. - // this is the part that's complaining TODO - 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. - 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 '<object type="application/x-shockwave-flash" id="' + - name + '" name="' + name + '" data=' + - '"/static/js/verify_student/CameraCapture.swf?v=3"' + - 'width="500" height="375"><param name="quality" ' + - 'value="high"><param name="allowscriptaccess" ' + - 'value="sameDomain"></object>'; - } - else { - // display a message informing the user to install flash - $('#no-flash').show(); - } -} - -function linkNewWindow(e) { - window.open($(e.target).attr('href')); - e.preventDefault(); -} - -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'); - $("#pay_button").click(function(){ - analytics.pageview("Payment Form"); - submitToPaymentProcessing(); - }); - - $("#reverify_button").click(function() { - submitReverificationPhotos(); - }); - - // prevent browsers from keeping this button checked - $("#confirm_pics_good").prop("checked", false) - $("#confirm_pics_good").change(function() { - $("#pay_button").toggleClass('disabled'); - $("#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"); - $('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")); - // 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(["face"], hasHtml5CameraSupport); - - $('a[rel="external"]').attr('title', gettext('This link will open in a new browser window/tab')).bind('click', linkNewWindow); - -}); diff --git a/lms/templates/dashboard/_dashboard_prompt_midcourse_reverify.html b/lms/templates/dashboard/_dashboard_prompt_midcourse_reverify.html index 51e0da34277..6b988d4618f 100644 --- a/lms/templates/dashboard/_dashboard_prompt_midcourse_reverify.html +++ b/lms/templates/dashboard/_dashboard_prompt_midcourse_reverify.html @@ -6,7 +6,7 @@ <div class="msg msg-reverify has-actions"> <div class="msg-content"> <h2 class="title">${_("You need to re-verify to continue")}</h2> - % for course_id, course_name, date, status in reverify_course_data: + % for course_id, course_name, course_number, date, status in reverify_course_data: <div class="copy"> <p class='activation-message'> ${_('To continue in the verified track in <strong>{course_name}</strong>, you need to re-verify your identity by {date}.').format(course_name=course_name, date=date)} diff --git a/lms/templates/verify_student/midcourse_photo_reverification.html b/lms/templates/verify_student/midcourse_photo_reverification.html index ac06e65234f..ce9c9e24c94 100644 --- a/lms/templates/verify_student/midcourse_photo_reverification.html +++ b/lms/templates/verify_student/midcourse_photo_reverification.html @@ -9,7 +9,7 @@ <%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> -<script src="${static.url('js/verify_student/photocapturebasic2.js')}"></script> +<script src="${static.url('js/verify_student/photocapture.js')}"></script> </%block> @@ -176,7 +176,7 @@ <form id="reverify_form" method="post"> <input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }"> <input type="hidden" name="course_id" value="${course_id}"> - <input class="action-primary disabled" type="button" id="reverify_button" value="Submit photos & re-verify" name="payment"> + <input class="action-primary disabled" type="button" id="midcourse_reverify_button" value="Submit photos & re-verify" name="payment"> </form> </li> </ol> diff --git a/lms/templates/verify_student/midcourse_reverify_dash.html b/lms/templates/verify_student/midcourse_reverify_dash.html index a05f1e7211b..dd01ac67664 100644 --- a/lms/templates/verify_student/midcourse_reverify_dash.html +++ b/lms/templates/verify_student/midcourse_reverify_dash.html @@ -25,7 +25,7 @@ <th>Status</th> </tr> - % for course_id, course_name, date, status in reverify_course_data: + % for course_id, course_name, course_number, date, status in reverify_course_data: <tr> <td> <span class="course-name">${course_name} (HKS211.1x)</span> diff --git a/lms/templates/verify_student/reverification_window_expired.html b/lms/templates/verify_student/reverification_window_expired.html new file mode 100644 index 00000000000..b113f8d44f3 --- /dev/null +++ b/lms/templates/verify_student/reverification_window_expired.html @@ -0,0 +1,45 @@ + +<%! from django.utils.translation import ugettext as _ %> +<%! from django.core.urlresolvers import reverse %> +<%inherit file="../main.html" /> +<%namespace name='static' file='/static_content.html'/> + +<%block name="bodyclass">register verification-process is-not-verified step-confirmation</%block> +<%block name="title"><title>${_("Re-Verification Failed")}</title></%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> -- GitLab