Tests and cleanup
Improved tests, fixed javascript for photocapture, removed extraneous photocapture code, added time & course numbers to templates for design
This commit is contained in:
@@ -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())
|
||||
|
||||
9
lms/djangoapps/verify_student/exceptions.py
Normal file
9
lms/djangoapps/verify_student/exceptions.py
Normal file
@@ -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
|
||||
@@ -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']
|
||||
complete_apps = ['verify_student']
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
});
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user