diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index 585da3d713..bbbad37f04 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 0000000000..d31fdb6a6d
--- /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 92ec4fdb4c..537cd36c3d 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 b406cfa25b..41d4cb650d 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 26c4149dc3..e2fb298ed9 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 a6dd07b489..5ab39dda9b 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 40b434ecea..fe9b233cb1 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 283d03fa64..15c5e04d7e 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 4db15e9d51..f8c89ae0db 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() {
+ $('').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 7bf34582cd..0000000000
--- 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
- $('').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) {
- $('').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 '';
- }
- 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 51e0da3427..6b988d4618 100644
--- a/lms/templates/dashboard/_dashboard_prompt_midcourse_reverify.html
+++ b/lms/templates/dashboard/_dashboard_prompt_midcourse_reverify.html
@@ -6,7 +6,7 @@
${_("You need to re-verify to continue")}
- % for course_id, course_name, date, status in reverify_course_data:
+ % for course_id, course_name, course_number, date, status in reverify_course_data:
${_('To continue in the verified track in {course_name}, 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 ac06e65234..ce9c9e24c9 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">
-
+
%block>
@@ -176,7 +176,7 @@
- % for course_id, course_name, date, status in reverify_course_data:
+ % for course_id, course_name, course_number, date, status in reverify_course_data: