Commit bf210398 by Julia Hansbrough

Tests and cleanup

Improved tests, fixed javascript for photocapture, removed extraneous photocapture code, added time & course numbers to templates for design
parent 6c7d715e
...@@ -182,6 +182,52 @@ def cert_info(user, course): ...@@ -182,6 +182,52 @@ def cert_info(user, course):
return _cert_info(user, course, certificate_status_for_student(user, course.id)) 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): def _cert_info(user, course, cert_status):
""" """
Implements the logic for cert_info -- split out for testing. Implements the logic for cert_info -- split out for testing.
...@@ -323,11 +369,6 @@ def complete_course_mode_info(course_id, enrollment): ...@@ -323,11 +369,6 @@ def complete_course_mode_info(course_id, enrollment):
def dashboard(request): def dashboard(request):
user = request.user 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 # for microsites, we want to filter and only show enrollments for courses within
# the microsites 'ORG' # the microsites 'ORG'
course_org_filter = MicrositeConfiguration.get_microsite_configuration_value('course_org_filter') course_org_filter = MicrositeConfiguration.get_microsite_configuration_value('course_org_filter')
...@@ -340,23 +381,10 @@ def dashboard(request): ...@@ -340,23 +381,10 @@ def dashboard(request):
if course_org_filter: if course_org_filter:
org_filter_out_set.remove(course_org_filter) org_filter_out_set.remove(course_org_filter)
for enrollment in CourseEnrollment.enrollments_for_user(user): # Build our (course, enrollment) list for the user, but ignore any courses that no
try: # longer exist (because the course IDs have changed). Still, we don't delete those
course = course_from_id(enrollment.course_id) # 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)
# 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))
course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True) course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True)
...@@ -392,25 +420,12 @@ def dashboard(request): ...@@ -392,25 +420,12 @@ def dashboard(request):
# TODO: make this banner appear at the top of courseware as well # TODO: make this banner appear at the top of courseware as well
verification_status, verification_msg = SoftwareSecurePhotoVerification.user_status(user) 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 = [] reverify_course_data = []
for (course, enrollment) in course_enrollment_pairs: for (course, enrollment) in course_enrollment_pairs:
info = reverification_info(user, course, enrollment)
# IF the reverification window is open if info:
if (MidcourseReverificationWindow.window_open_for_course(course.id)): reverify_course_data.append(info)
# 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)
)
)
show_refund_option_for = frozenset(course.id for course, _enrollment in course_enrollment_pairs show_refund_option_for = frozenset(course.id for course, _enrollment in course_enrollment_pairs
if _enrollment.refundable()) if _enrollment.refundable())
......
"""
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
...@@ -22,7 +22,6 @@ class Migration(SchemaMigration): ...@@ -22,7 +22,6 @@ class Migration(SchemaMigration):
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['verify_student.MidcourseReverificationWindow'], null=True), self.gf('django.db.models.fields.related.ForeignKey')(to=orm['verify_student.MidcourseReverificationWindow'], null=True),
keep_default=False) keep_default=False)
def backwards(self, orm): def backwards(self, orm):
# Deleting model 'MidcourseReverificationWindow' # Deleting model 'MidcourseReverificationWindow'
db.delete_table('verify_student_midcoursereverificationwindow') db.delete_table('verify_student_midcoursereverificationwindow')
...@@ -30,7 +29,6 @@ class Migration(SchemaMigration): ...@@ -30,7 +29,6 @@ class Migration(SchemaMigration):
# Deleting field 'SoftwareSecurePhotoVerification.window' # Deleting field 'SoftwareSecurePhotoVerification.window'
db.delete_column('verify_student_softwaresecurephotoverification', 'window_id') db.delete_column('verify_student_softwaresecurephotoverification', 'window_id')
models = { models = {
'auth.group': { 'auth.group': {
'Meta': {'object_name': 'Group'}, 'Meta': {'object_name': 'Group'},
...@@ -97,4 +95,4 @@ class Migration(SchemaMigration): ...@@ -97,4 +95,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['verify_student'] complete_apps = ['verify_student']
\ No newline at end of file
...@@ -23,7 +23,7 @@ import pytz ...@@ -23,7 +23,7 @@ import pytz
import requests import requests
from django.conf import settings 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.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -71,17 +71,9 @@ class MidcourseReverificationWindow(models.Model): ...@@ -71,17 +71,9 @@ class MidcourseReverificationWindow(models.Model):
Returns a boolean, True if the course is currently asking for reverification, else False. Returns a boolean, True if the course is currently asking for reverification, else False.
""" """
now = datetime.now(pytz.UTC) now = datetime.now(pytz.UTC)
if cls.get_window(course_id, now):
try: return True
cls.objects.get( return False
course_id=course_id,
start_date__lte=now,
end_date__gte=now,
)
except(ObjectDoesNotExist):
return False
return True
@classmethod @classmethod
def get_window(cls, course_id, date): def get_window(cls, course_id, date):
...@@ -244,9 +236,13 @@ class PhotoVerification(StatusModel): ...@@ -244,9 +236,13 @@ class PhotoVerification(StatusModel):
@classmethod @classmethod
def user_is_verified(cls, user, earliest_allowed_date=None, window=None): def user_is_verified(cls, user, earliest_allowed_date=None, window=None):
""" """
Return whether or not a user has satisfactorily proved their Return whether or not a user has satisfactorily proved their identity.
identity wrt to the INITIAL verification. Depending on the policy, Depending on the policy, this can expire after some period of time, so
this can expire after some period of time, so a user might have to renew periodically. 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( return cls.objects.filter(
user=user, user=user,
...@@ -264,6 +260,10 @@ class PhotoVerification(StatusModel): ...@@ -264,6 +260,10 @@ class PhotoVerification(StatusModel):
have been submitted but had an non-user error when it was being 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 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. 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: if window:
valid_statuses = ['submitted', 'approved'] valid_statuses = ['submitted', 'approved']
...@@ -280,8 +280,11 @@ class PhotoVerification(StatusModel): ...@@ -280,8 +280,11 @@ class PhotoVerification(StatusModel):
@classmethod @classmethod
def active_for_user(cls, user, window=None): 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). 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 # 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. # by mistake, we'll grab the most recently created one.
...@@ -301,6 +304,9 @@ class PhotoVerification(StatusModel): ...@@ -301,6 +304,9 @@ class PhotoVerification(StatusModel):
If the verification has been approved, returns 'approved' If the verification has been approved, returns 'approved'
If the verification process is still ongoing, returns 'pending' 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 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' status = 'none'
error_msg = '' error_msg = ''
......
...@@ -438,6 +438,7 @@ class TestMidcourseReverificationWindow(TestCase): ...@@ -438,6 +438,7 @@ class TestMidcourseReverificationWindow(TestCase):
@patch('verify_student.models.Key', new=MockKey) @patch('verify_student.models.Key', new=MockKey)
@patch('verify_student.models.requests.post', new=mock_software_secure_post) @patch('verify_student.models.requests.post', new=mock_software_secure_post)
class TestMidcourseReverification(TestCase): class TestMidcourseReverification(TestCase):
""" Tests for methods that are specific to midcourse SoftwareSecurePhotoVerification objects """
def setUp(self): def setUp(self):
self.course_id = "MITx/999/Robot_Super_Course" self.course_id = "MITx/999/Robot_Super_Course"
self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
...@@ -527,6 +528,3 @@ class TestMidcourseReverification(TestCase): ...@@ -527,6 +528,3 @@ class TestMidcourseReverification(TestCase):
attempt.status = "approved" attempt.status = "approved"
attempt.save() attempt.save()
assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user=self.user, window=window)) assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user=self.user, window=window))
def test_active_for_user(self):
pass
...@@ -11,6 +11,8 @@ verify_student/start?course_id=MITx/6.002x/2013_Spring # create ...@@ -11,6 +11,8 @@ verify_student/start?course_id=MITx/6.002x/2013_Spring # create
""" """
import urllib import urllib
from mock import patch, Mock, ANY from mock import patch, Mock, ANY
import pytz
from datetime import timedelta, datetime
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
...@@ -114,24 +116,14 @@ class TestReverifyView(TestCase): ...@@ -114,24 +116,14 @@ class TestReverifyView(TestCase):
self.assertIsNotNone(verification_attempt) self.assertIsNotNone(verification_attempt)
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.fail('No verification object generated') self.fail('No verification object generated')
((template, context), _kwargs) = render_mock.call_args
self.assertIn('photo_reverification', template) self.assertIn('photo_reverification', template)
self.assertTrue(context['error']) 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) @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class TestMidCourseReverifyView(TestCase): class TestMidCourseReverifyView(TestCase):
""" Tests for the midcourse reverification views """
def setUp(self): def setUp(self):
self.user = UserFactory.create(username="rusty", password="test") self.user = UserFactory.create(username="rusty", password="test")
self.client.login(username="rusty", password="test") self.client.login(username="rusty", password="test")
...@@ -149,16 +141,29 @@ class TestMidCourseReverifyView(TestCase): ...@@ -149,16 +141,29 @@ class TestMidCourseReverifyView(TestCase):
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True}) @patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
def test_midcourse_reverify_post_success(self): 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}) url = reverse('verify_student_midcourse_reverify', kwargs={'course_id': self.course_id})
response = self.client.post(url, {'face_image': ','}) response = self.client.post(url, {'face_image': ','})
self.assertEquals(response.status_code, 302) self.assertEquals(response.status_code, 302)
try: try:
verification_attempt = SoftwareSecurePhotoVerification.objects.get(user=self.user) verification_attempt = SoftwareSecurePhotoVerification.objects.get(user=self.user, window=window)
self.assertIsNotNone(verification_attempt) self.assertIsNotNone(verification_attempt)
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.fail('No verification object generated') 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) @patch('verify_student.views.render_to_response', render_mock)
def test_midcourse_reverify_dash(self): def test_midcourse_reverify_dash(self):
url = reverse('verify_student_midcourse_reverify_dash') url = reverse('verify_student_midcourse_reverify_dash')
......
...@@ -64,4 +64,10 @@ urlpatterns = patterns( ...@@ -64,4 +64,10 @@ urlpatterns = patterns(
views.midcourse_reverify_dash, views.midcourse_reverify_dash,
name="verify_student_midcourse_reverify_dash" name="verify_student_midcourse_reverify_dash"
), ),
url(
r'^reverification_window_expired$',
views.reverification_window_expired,
name="verify_student_reverification_window_expired"
),
) )
...@@ -34,6 +34,7 @@ from verify_student.models import ( ...@@ -34,6 +34,7 @@ from verify_student.models import (
) )
import ssencrypt import ssencrypt
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from .exceptions import WindowExpiredException
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -362,9 +363,11 @@ class MidCourseReverifyView(View): ...@@ -362,9 +363,11 @@ class MidCourseReverifyView(View):
submits the reverification to SoftwareSecure submits the reverification to SoftwareSecure
""" """
try: try:
# TODO look at this more carefully! #1 testing candidate
now = datetime.datetime.now(UTC) 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] b64_face_image = request.POST['face_image'].split(",")[1]
attempt.upload_face_image(b64_face_image.decode('base64')) attempt.upload_face_image(b64_face_image.decode('base64'))
...@@ -374,6 +377,13 @@ class MidCourseReverifyView(View): ...@@ -374,6 +377,13 @@ class MidCourseReverifyView(View):
attempt.save() attempt.save()
attempt.submit() attempt.submit()
return HttpResponseRedirect(reverse('verify_student_midcourse_reverification_confirmation')) 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: except Exception:
log.exception( log.exception(
"Could not submit verification attempt for user {}".format(request.user.id) "Could not submit verification attempt for user {}".format(request.user.id)
...@@ -390,7 +400,6 @@ def midcourse_reverify_dash(_request): ...@@ -390,7 +400,6 @@ def midcourse_reverify_dash(_request):
Shows the "course reverification dashboard", which displays the reverification status (must reverify, 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. 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 user = _request.user
course_enrollment_pairs = [] course_enrollment_pairs = []
for enrollment in CourseEnrollment.enrollments_for_user(user): for enrollment in CourseEnrollment.enrollments_for_user(user):
...@@ -406,7 +415,8 @@ def midcourse_reverify_dash(_request): ...@@ -406,7 +415,8 @@ def midcourse_reverify_dash(_request):
( (
course.id, course.id,
course.display_name, 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" "must_reverify"
) )
) )
...@@ -431,3 +441,13 @@ def midcourse_reverification_confirmation(_request): ...@@ -431,3 +441,13 @@ def midcourse_reverification_confirmation(_request):
Shows the user a confirmation page if the submission to SoftwareSecure was successful Shows the user a confirmation page if the submission to SoftwareSecure was successful
""" """
return render_to_response("verify_student/midcourse_reverification_confirmation.html") 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")
...@@ -35,15 +35,22 @@ var submitReverificationPhotos = function() { ...@@ -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 submitToPaymentProcessing = function() {
var contribution_input = $("input[name='contribution']:checked") var contribution_input = $("input[name='contribution']:checked")
var contribution = 0; var contribution = 0;
if(contribution_input.attr('id') == 'contribution-other') if(contribution_input.attr('id') == 'contribution-other') {
{
contribution = $("input[name='contribution-other-amt']").val(); contribution = $("input[name='contribution-other-amt']").val();
} }
else else {
{
contribution = contribution_input.val(); contribution = contribution_input.val();
} }
var course_id = $("input[name='course_id']").val(); var course_id = $("input[name='course_id']").val();
...@@ -276,11 +283,16 @@ $(document).ready(function() { ...@@ -276,11 +283,16 @@ $(document).ready(function() {
submitReverificationPhotos(); submitReverificationPhotos();
}); });
$("#midcourse_reverify_button").click(function() {
submitMidcourseReverificationPhotos();
});
// prevent browsers from keeping this button checked // prevent browsers from keeping this button checked
$("#confirm_pics_good").prop("checked", false) $("#confirm_pics_good").prop("checked", false)
$("#confirm_pics_good").change(function() { $("#confirm_pics_good").change(function() {
$("#pay_button").toggleClass('disabled'); $("#pay_button").toggleClass('disabled');
$("#reverify_button").toggleClass('disabled'); $("#reverify_button").toggleClass('disabled');
$("#midcourse_reverify_button").toggleClass('disabled');
}); });
......
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);
});
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<div class="msg msg-reverify has-actions"> <div class="msg msg-reverify has-actions">
<div class="msg-content"> <div class="msg-content">
<h2 class="title">${_("You need to re-verify to continue")}</h2> <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"> <div class="copy">
<p class='activation-message'> <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)} ${_('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)}
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<%block name="js_extra"> <%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.js')}"></script>
<script src="${static.url('js/vendor/responsive-carousel/responsive-carousel.keybd.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> </%block>
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
<form id="reverify_form" method="post"> <form id="reverify_form" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }"> <input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
<input type="hidden" name="course_id" value="${course_id}"> <input type="hidden" name="course_id" value="${course_id}">
<input class="action-primary disabled" type="button" id="reverify_button" value="Submit photos &amp; re-verify" name="payment"> <input class="action-primary disabled" type="button" id="midcourse_reverify_button" value="Submit photos &amp; re-verify" name="payment">
</form> </form>
</li> </li>
</ol> </ol>
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<th>Status</th> <th>Status</th>
</tr> </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> <tr>
<td> <td>
<span class="course-name">${course_name} (HKS211.1x)</span> <span class="course-name">${course_name} (HKS211.1x)</span>
......
<%! 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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment