From dd46eb47abf94ddbbf7ea7c3fff1a044c06cd1d1 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 30 Sep 2013 14:57:30 -0400 Subject: [PATCH 01/19] Add barebones reverification flow. --- lms/djangoapps/verify_student/urls.py | 11 + lms/djangoapps/verify_student/views.py | 63 +++ lms/static/js/verify_student/photocapture.js | 22 + .../verify_student/photo_reverification.html | 394 ++++++++++++++++++ .../reverification_confirmation.html | 18 + 5 files changed, 508 insertions(+) create mode 100644 lms/templates/verify_student/photo_reverification.html create mode 100644 lms/templates/verify_student/reverification_confirmation.html diff --git a/lms/djangoapps/verify_student/urls.py b/lms/djangoapps/verify_student/urls.py index 15c2cc5f6b..843ebf9602 100644 --- a/lms/djangoapps/verify_student/urls.py +++ b/lms/djangoapps/verify_student/urls.py @@ -35,4 +35,15 @@ urlpatterns = patterns( name="verify_student_results_callback", ), + url( + r'^reverify$', + views.ReverifyView.as_view(), + name="verify_student_reverify" + ), + + url( + r'^reverification_confirmation$', + views.reverification_submission_confirmation, + name="verify_student_reverification_confirmation" + ), ) diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index 8fafc26834..673564e72c 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -267,3 +267,66 @@ def show_requirements(request, course_id): "upgrade": upgrade, } return render_to_response("verify_student/show_requirements.html", context) + + +class ReverifyView(View): + """ + The main reverification view. Under similar constraints as the main verification view. + Has to perform these functions: + - take new face photo + - take new id photo + - submit photos to photo verification service + + Does not need to be attached to a particular course. + Does not need to worry about pricing + """ + @method_decorator(login_required) + def get(self, request): + """ + display this view + """ + context = { + "user_full_name": request.user.profile.name, + "error": False, + } + + return render_to_response("verify_student/photo_reverification.html", context) + + @method_decorator(login_required) + def post(self, request): + """ + submits the reverification to SoftwareSecure + """ + + try: + attempt = SoftwareSecurePhotoVerification(user=request.user) + b64_face_image = request.POST['face_image'].split(",")[1] + b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] + + attempt.upload_face_image(b64_face_image.decode('base64')) + attempt.upload_photo_id_image(b64_photo_id_image.decode('base64')) + attempt.mark_ready() + + # save this attempt + attempt.save() + # then submit it across + attempt.submit() + return HttpResponseRedirect(reverse('verify_student_reverification_confirmation')) + except Exception: + log.exception( + "Could not submit verification attempt for user {}".format(request.user.id) + ) + context = { + "user_full_name": request.user.profile.name, + "error": True, + } + return render_to_response("verify_student/photo_reverification.html", context) + + +@login_required +def reverification_submission_confirmation(_request): + """ + Shows the user a confirmation page if the submission to SoftwareSecure was successful + """ + + return render_to_response("verify_student/reverification_confirmation.html") diff --git a/lms/static/js/verify_student/photocapture.js b/lms/static/js/verify_student/photocapture.js index 0519767841..4db15e9d51 100644 --- a/lms/static/js/verify_student/photocapture.js +++ b/lms/static/js/verify_student/photocapture.js @@ -18,6 +18,23 @@ function initVideoCapture() { return !(navigator.getUserMedia == undefined); } +var submitReverificationPhotos = function() { + // add photos to the form + $('').attr({ + type: 'hidden', + name: 'face_image', + value: $("#face_image")[0].src, + }).appendTo("#reverify_form"); + $('').attr({ + type: 'hidden', + name: 'photo_id_image', + value: $("#photo_id_image")[0].src, + }).appendTo("#reverify_form"); + + $("#reverify_form").submit(); + +} + var submitToPaymentProcessing = function() { var contribution_input = $("input[name='contribution']:checked") var contribution = 0; @@ -255,10 +272,15 @@ $(document).ready(function() { 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'); }); diff --git a/lms/templates/verify_student/photo_reverification.html b/lms/templates/verify_student/photo_reverification.html new file mode 100644 index 0000000000..764458b791 --- /dev/null +++ b/lms/templates/verify_student/photo_reverification.html @@ -0,0 +1,394 @@ +<%! 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 step-photos +<%block name="title">${_("Re-Verification")} + +<%block name="js_extra"> + + + + + + +<%block name="content"> + + + + + +%if error: +
+
+ +
+

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

+
+

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

+
+
+
+
+%endif + +
+
+ +
+
+

${_("Your Progress")}

+ + +
    +
  1. + 0 + ${_("Intro")} +
  2. + +
  3. + 1 + ${_("Current Step: ")}${_("Take Photo")} +
  4. + +
  5. + 2 + ${_("Take ID Photo")} +
  6. + +
  7. + 3 + ${_("Review")} +
  8. + +
  9. + 4 + ${_("Make Payment")} +
  10. + +
  11. + + + + ${_("Confirmation")} +
  12. +
+ + + + +
+
+ +
+
+ + +
+
+ +
+
+ +<%include file="_modal_editname.html" /> + diff --git a/lms/templates/verify_student/reverification_confirmation.html b/lms/templates/verify_student/reverification_confirmation.html new file mode 100644 index 0000000000..2ab639effc --- /dev/null +++ b/lms/templates/verify_student/reverification_confirmation.html @@ -0,0 +1,18 @@ + +<%! 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 step-photos +<%block name="title">${_("Verification Submission Confirmation")} + +<%block name="js_extra"> + + + +<%block name="content"> +

Successfully reverified!

+ +

Return to Dashboard

+ From f31496511371c14d922030db60426885575828c2 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 1 Oct 2013 16:51:21 -0400 Subject: [PATCH 02/19] Add tests for some of the new views. --- .../verify_student/tests/test_views.py | 52 ++++++++++++++++++- lms/envs/acceptance.py | 4 -- lms/envs/test.py | 4 ++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index 35efc583c6..d4d3120dc9 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -10,20 +10,30 @@ verify_student/start?course_id=MITx/6.002x/2013_Spring # create """ import urllib +from mock import patch, Mock, ANY from django.test import TestCase from django.test.utils import override_settings from django.core.urlresolvers import reverse +from django.core.exceptions import ObjectDoesNotExist from xmodule.modulestore.tests.factories import CourseFactory from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE from student.tests.factories import UserFactory from course_modes.models import CourseMode +from verify_student.views import render_to_response +from verify_student.models import SoftwareSecurePhotoVerification + + +def mock_render_to_response(*args, **kwargs): + return render_to_response(*args, **kwargs) + +render_mock = Mock(side_effect=mock_render_to_response) class StartView(TestCase): - def start_url(course_id=""): + def start_url(self, course_id=""): return "/verify_student/{0}".format(urllib.quote(course_id)) def test_start_new_verification(self): @@ -58,3 +68,43 @@ class TestVerifyView(TestCase): response = self.client.get(url) self.assertEquals(response.status_code, 302) + + +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +class TestReverifyView(TestCase): + """ + Tests for the reverification views + + """ + def setUp(self): + self.user = UserFactory.create(username="rusty", password="test") + self.client.login(username="rusty", password="test") + + @patch('verify_student.views.render_to_response', render_mock) + def test_reverify_get(self): + url = reverse('verify_student_reverify') + response = self.client.get(url) + self.assertEquals(response.status_code, 200) + ((_template, context), _kwargs) = render_mock.call_args + self.assertFalse(context['error']) + + @patch('verify_student.views.render_to_response', render_mock) + def test_reverify_post_failure(self): + url = reverse('verify_student_reverify') + response = self.client.post(url, {'face_image': '', + 'photo_id_image': ''}) + self.assertEquals(response.status_code, 200) + ((template, context), _kwargs) = render_mock.call_args + self.assertIn('photo_reverification', template) + self.assertTrue(context['error']) + + 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') diff --git a/lms/envs/acceptance.py b/lms/envs/acceptance.py index e1976f32e7..d6e81faf27 100644 --- a/lms/envs/acceptance.py +++ b/lms/envs/acceptance.py @@ -91,10 +91,6 @@ MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True # Use the auto_auth workflow for creating users and logging them in MITX_FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] = True -# Don't actually send any requests to Software Secure for student identity -# verification. -MITX_FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True - # Enable fake payment processing page MITX_FEATURES['ENABLE_PAYMENT_FAKE'] = True diff --git a/lms/envs/test.py b/lms/envs/test.py index 42a4d84436..8093d12f45 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -36,6 +36,10 @@ MITX_FEATURES['ENABLE_INSTRUCTOR_BETA_DASHBOARD'] = True MITX_FEATURES['ENABLE_SHOPPING_CART'] = True +# Don't actually send any requests to Software Secure for student identity +# verification. +MITX_FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True + # Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it. WIKI_ENABLED = True From d3a4747b060faf8b3187473f3eb9d79d717473a8 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 4 Oct 2013 15:54:50 -0400 Subject: [PATCH 03/19] Add new user_status functionality to PhotoVerification. --- common/djangoapps/student/views.py | 6 ++ lms/djangoapps/verify_student/models.py | 89 ++++++++++++++++--- .../verify_student/tests/test_models.py | 72 ++++++++++++--- .../verify_student/tests/test_views.py | 2 + lms/envs/acceptance.py | 3 + lms/envs/test.py | 4 - 6 files changed, 150 insertions(+), 26 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 4ebbcff592..e5582e2f27 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -47,6 +47,8 @@ from student.models import ( ) from student.forms import PasswordResetFormNoActive +from verify_student.models import SoftwareSecurePhotoVerification + from certificates.models import CertificateStatuses, certificate_status_for_student from xmodule.course_module import CourseDescriptor @@ -334,6 +336,8 @@ def dashboard(request): CourseAuthorization.instructor_email_enabled(course.id) ) ) + # Verification Attempts + verification_status = SoftwareSecurePhotoVerification.user_status(user) # get info w.r.t ExternalAuthMap external_auth_map = None try: @@ -351,6 +355,8 @@ def dashboard(request): 'all_course_modes': course_modes, 'cert_statuses': cert_statuses, 'show_email_settings_for': show_email_settings_for, + 'verification_status': verification_status[0], + 'verification_msg': verification_status[1], } return render_to_response('dashboard.html', context) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 39ea4106ff..17cffa4ead 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -26,6 +26,7 @@ from django.conf import settings from django.core.urlresolvers import reverse from django.db import models from django.contrib.auth.models import User +from django.utils.translation import ugettext as _ from model_utils.models import StatusModel from model_utils import Choices @@ -174,6 +175,17 @@ class PhotoVerification(StatusModel): ordering = ['-created_at'] ##### Methods listed in the order you'd typically call them + @classmethod + def _earliest_allowed_date(cls): + """ + Returns the earliest allowed date given the settings + + """ + allowed_date = ( + datetime.now(pytz.UTC) - timedelta(days=cls.DAYS_GOOD_FOR) + ) + return allowed_date + @classmethod def user_is_verified(cls, user, earliest_allowed_date=None): """ @@ -181,14 +193,11 @@ class PhotoVerification(StatusModel): identity. Depending on the policy, this can expire after some period of time, so a user might have to renew periodically. """ - earliest_allowed_date = ( - earliest_allowed_date or - datetime.now(pytz.UTC) - timedelta(days=cls.DAYS_GOOD_FOR) - ) return cls.objects.filter( user=user, status="approved", - created_at__gte=earliest_allowed_date + created_at__gte=(earliest_allowed_date + or cls._earliest_allowed_date()) ).exists() @classmethod @@ -201,14 +210,11 @@ class PhotoVerification(StatusModel): on the contents of the attempt, and we have not yet received a denial. """ valid_statuses = ['must_retry', 'submitted', 'approved'] - earliest_allowed_date = ( - earliest_allowed_date or - datetime.now(pytz.UTC) - timedelta(days=cls.DAYS_GOOD_FOR) - ) return cls.objects.filter( user=user, status__in=valid_statuses, - created_at__gte=earliest_allowed_date + created_at__gte=(earliest_allowed_date + or cls._earliest_allowed_date()) ).exists() @classmethod @@ -225,6 +231,38 @@ class PhotoVerification(StatusModel): else: return None + @classmethod + def user_status(cls, user): + """ + Returns the status of the user based on their latest verification attempt + + If no such verification exists, returns 'none' + If verification has expired, returns 'expired' + """ + try: + attempts = cls.objects.filter(user=user).order_by('-updated_at') + attempt = attempts[0] + except IndexError: + return ('none', '') + + if attempt.created_at < cls._earliest_allowed_date(): + return ('expired', '') + + error_msg = attempt.error_msg + if attempt.error_msg: + error_msg = attempt.parse_error_msg() + + return (attempt.status, error_msg) + + def parse_error_msg(self): + """ + Sometimes, the error message we've received needs to be parsed into + something more human readable + + The default behavior is to return the current error message as is. + """ + return self.error_msg + @status_before_must_be("created") def upload_face_image(self, img): raise NotImplementedError @@ -486,6 +524,37 @@ class SoftwareSecurePhotoVerification(PhotoVerification): self.status = "must_retry" self.save() + def parse_error_msg(self): + """ + Parse the error messages we receive from SoftwareSecure + + Error messages are written in the form: + + `[{"photoIdReasons": ["Not provided"]}]` + + Returns a list of error messages + """ + # Translates the category names into something more human readable + category_dict = { + "photoIdReasons": _("Photo ID Issues: "), + "generalReasons": u"" + } + + try: + msg_json = json.loads(self.error_msg) + msg_dict = msg_json[0] + + msg = [] + for category in msg_dict: + # translate the category into a human-readable name + category_name = category_dict[category] + msg.append(category_name + u", ".join(msg_dict[category])) + return u", ".join(msg) + except (ValueError, KeyError): + # if we can't parse the message as JSON or the category doesn't + # match one of our known categories, show a generic error + return _("There was an error verifying your ID photos.") + def image_url(self, name): """ We dynamically generate this, since we want it the expiration clock to diff --git a/lms/djangoapps/verify_student/tests/test_models.py b/lms/djangoapps/verify_student/tests/test_models.py index 40842cdcdb..dcaa9cade9 100644 --- a/lms/djangoapps/verify_student/tests/test_models.py +++ b/lms/djangoapps/verify_student/tests/test_models.py @@ -17,11 +17,11 @@ from util.testing import UrlResetMixin import verify_student.models FAKE_SETTINGS = { - "SOFTWARE_SECURE" : { + "SOFTWARE_SECURE": { "FACE_IMAGE_AES_KEY" : "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "API_ACCESS_KEY" : "BBBBBBBBBBBBBBBBBBBB", - "API_SECRET_KEY" : "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", - "RSA_PUBLIC_KEY" : """-----BEGIN PUBLIC KEY----- + "API_ACCESS_KEY": "BBBBBBBBBBBBBBBBBBBB", + "API_SECRET_KEY": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + "RSA_PUBLIC_KEY": """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu2fUn20ZQtDpa1TKeCA/ rDA2cEeFARjEr41AP6jqP/k3O7TeqFX6DgCBkxcjojRCs5IfE8TimBHtv/bcSx9o 7PANTq/62ZLM9xAMpfCcU6aAd4+CVqQkXSYjj5TUqamzDFBkp67US8IPmw7I2Gaa @@ -30,10 +30,10 @@ dyZCM9pBcvcH+60ma+nNg8GVGBAW/oLxILBtg+T3PuXSUvcu/r6lUFMHk55pU94d 9A/T8ySJm379qU24ligMEetPk1o9CUasdaI96xfXVDyFhrzrntAmdD+HYCSPOQHz iwIDAQAB -----END PUBLIC KEY-----""", - "API_URL" : "http://localhost/verify_student/fake_endpoint", - "AWS_ACCESS_KEY" : "FAKEACCESSKEY", - "AWS_SECRET_KEY" : "FAKESECRETKEY", - "S3_BUCKET" : "fake-bucket" + "API_URL": "http://localhost/verify_student/fake_endpoint", + "AWS_ACCESS_KEY": "FAKEACCESSKEY", + "AWS_SECRET_KEY": "FAKESECRETKEY", + "S3_BUCKET": "fake-bucket" } } @@ -57,11 +57,13 @@ class MockKey(object): def generate_url(self, duration): return "http://fake-edx-s3.edx.org/" + class MockBucket(object): """Mocking a boto S3 Bucket object.""" def __init__(self, name): self.name = name + class MockS3Connection(object): """Mocking a boto S3 Connection""" def __init__(self, access_key, secret_key): @@ -165,14 +167,14 @@ class TestPhotoVerification(TestCase): # approved assert_raises(VerificationException, attempt.submit) - attempt.approve() # no-op - attempt.system_error("System error") # no-op, something processed it without error + attempt.approve() # no-op + attempt.system_error("System error") # no-op, something processed it without error attempt.deny(DENY_ERROR_MSG) # denied assert_raises(VerificationException, attempt.submit) - attempt.deny(DENY_ERROR_MSG) # no-op - attempt.system_error("System error") # no-op, something processed it without error + attempt.deny(DENY_ERROR_MSG) # no-op + attempt.system_error("System error") # no-op, something processed it without error attempt.approve() def test_name_freezing(self): @@ -307,3 +309,49 @@ class TestPhotoVerification(TestCase): attempt.save() assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user), status) + def test_user_status(self): + # test for correct status when no error returned + user = UserFactory.create() + status = SoftwareSecurePhotoVerification.user_status(user) + self.assertEquals(status, ('none', '')) + + # test for when one has been created + attempt = SoftwareSecurePhotoVerification(user=user) + attempt.status = 'approved' + attempt.save() + + status = SoftwareSecurePhotoVerification.user_status(user) + self.assertEquals(status, (attempt.status, '')) + + # create another one for the same user, make sure the right one is + # returned + attempt2 = SoftwareSecurePhotoVerification(user=user) + attempt2.status = 'denied' + attempt2.error_msg = '[{"photoIdReasons": ["Not provided"]}]' + attempt2.save() + + status = SoftwareSecurePhotoVerification.user_status(user) + self.assertEquals(status, (attempt2.status, "Photo ID Issues: Not provided")) + + def test_parse_error_msg_success(self): + user = UserFactory.create() + attempt = SoftwareSecurePhotoVerification(user=user) + attempt.status = 'denied' + attempt.error_msg = '[{"photoIdReasons": ["Not provided"]}]' + parsed_error_msg = attempt.parse_error_msg() + self.assertEquals("Photo ID Issues: Not provided", parsed_error_msg) + + def test_parse_error_msg_failure(self): + user = UserFactory.create() + attempt = SoftwareSecurePhotoVerification(user=user) + attempt.status = 'denied' + # when we can't parse into json + bad_messages = { + 'Not Provided', + '[{"IdReasons": ["Not provided"]}]', + '{"IdReasons": ["Not provided"]}', + } + for msg in bad_messages: + attempt.error_msg = msg + parsed_error_msg = attempt.parse_error_msg() + self.assertEquals(parsed_error_msg, "There was an error verifying your ID photos.") diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index d4d3120dc9..43cbff15bd 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -14,6 +14,7 @@ from mock import patch, Mock, ANY from django.test import TestCase from django.test.utils import override_settings +from django.conf import settings from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist @@ -98,6 +99,7 @@ class TestReverifyView(TestCase): self.assertIn('photo_reverification', template) self.assertTrue(context['error']) + @patch.dict(settings.MITX_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': ',', diff --git a/lms/envs/acceptance.py b/lms/envs/acceptance.py index d6e81faf27..76a2b81926 100644 --- a/lms/envs/acceptance.py +++ b/lms/envs/acceptance.py @@ -98,6 +98,9 @@ MITX_FEATURES['ENABLE_PAYMENT_FAKE'] = True MITX_FEATURES['ENABLE_INSTRUCTOR_EMAIL'] = True MITX_FEATURES['REQUIRE_COURSE_EMAIL_AUTH'] = False +# Don't actually send any requests to Software Secure for student identity +# verification. +MITX_FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True # Configure the payment processor to use the fake processing page # Since both the fake payment page and the shoppingcart app are using diff --git a/lms/envs/test.py b/lms/envs/test.py index 8093d12f45..42a4d84436 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -36,10 +36,6 @@ MITX_FEATURES['ENABLE_INSTRUCTOR_BETA_DASHBOARD'] = True MITX_FEATURES['ENABLE_SHOPPING_CART'] = True -# Don't actually send any requests to Software Secure for student identity -# verification. -MITX_FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True - # Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it. WIKI_ENABLED = True From ee52e0232e2894cedd978b289d6ec00170c36926 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 8 Oct 2013 11:23:12 -0400 Subject: [PATCH 04/19] Put in a dummy verification status into the dashboard. --- lms/templates/dashboard.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 3310451c94..b2326cb01c 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -171,6 +171,21 @@ % endif + %if verification_status == 'denied': +
  • + ${_("Photo Verification Failed")} +

    + ${verification_msg} +

    +

    + Please submit new verification photos. +

    +

    + Note: if you fail to pass a verification attempt before the course ends, you will not receive a verified certificate. +

    +
  • + %endif + From a25e413fdb1bf714b55d67633ac80084b6541ec8 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 9 Oct 2013 15:42:45 -0400 Subject: [PATCH 05/19] Fix settings for verification statuses on the student dashboard --- cms/envs/common.py | 5 +++++ lms/envs/common.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 8d28ddcd8d..cd3c11cfaf 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -418,6 +418,11 @@ MKTG_URL_LINK_MAP = { COURSES_WITH_UNSAFE_CODE = [] +################# Student Verification ################# +VERIFY_STUDENT = { + "DAYS_GOOD_FOR": 365, # How many days is a verficiation good for? +} + ############################## EVENT TRACKING ################################# TRACK_MAX_EVENT = 10000 diff --git a/lms/envs/common.py b/lms/envs/common.py index 20e2f012b7..96ad98779e 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1023,7 +1023,7 @@ def enable_theme(theme_name): ################# Student Verification ################# VERIFY_STUDENT = { - "DAYS_GOOD_FOR" : 365, # How many days is a verficiation good for? + "DAYS_GOOD_FOR": 365, # How many days is a verficiation good for? } ######################## CAS authentication ########################### From 280c46320916749ab0db52ec193c881d7b421025 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 22 Oct 2013 09:57:35 -0400 Subject: [PATCH 06/19] LMS: adds in styling and base UI for reverification flow --- lms/static/sass/views/_verification.scss | 160 +++++++++++++++++- .../_reverification_support.html | 28 +++ .../verify_student/photo_reverification.html | 43 ++--- .../reverification_confirmation.html | 68 +++++++- 4 files changed, 269 insertions(+), 30 deletions(-) create mode 100644 lms/templates/verify_student/_reverification_support.html diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index 43950d6f41..bcfc8d09bf 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -226,7 +226,7 @@ } // reset: lists - .list-actions, .list-steps, .progress-steps, .list-controls, .list-fields, .list-help, .list-faq, .nav-wizard, .list-reqs, .list-faq, .review-tasks, .list-tips, .wrapper-photos, .field-group, .list-info { + .list-actions, .list-steps, .progress-steps, .list-controls, .list-fields, .list-nav, .list-help, .list-faq, .nav-wizard, .list-reqs, .list-faq, .review-tasks, .list-tips, .wrapper-photos, .field-group, .list-info { @extend %ui-no-list; } @@ -358,15 +358,22 @@ // UI : message .wrapper-msg { - width: flex-grid(12,12); - margin: 0 auto ($baseline*1.5) auto; + margin-bottom: ($baseline*1.5); border-bottom: ($baseline/4) solid $m-blue; padding: $baseline ($baseline*1.5); background: tint($m-blue, 95%); + .msg { + @include clearfix(); + max-width: grid-width(12); + min-width: 760px; + width: flex-grid(12); + margin: 0 auto; + } + .msg-content, .msg-icon { - display: inline-block; - vertical-align: middle; + display: block; + float: left; } .msg-content { @@ -385,6 +392,7 @@ .msg-icon { width: flex-grid(1,12); @extend %t-icon2; + margin-right: flex-gutter(); text-align: center; color: $m-blue; } @@ -424,6 +432,10 @@ } } + .wrapper-msg-reverify { + + } + // ==================== // UI: inline messages @@ -1851,6 +1863,7 @@ } } } +// ==================== // STATE: already verified .register.is-verified { @@ -1904,6 +1917,8 @@ } } +// ==================== + // STATE: upgrading registration type .register.is-upgrading { @@ -1911,3 +1926,138 @@ margin-top: ($baseline*2) !important; } } + +// STATE: re-verifying +.register.is-not-verified { + + // progress indicator + .progress-sts { + width: 72%; + left: 15%; + } + + // VIEW: photo + &.step-photos { + + // progress nav + .progress .progress-step { + + // STATE: is current + &#progress-step1 { + border-bottom: ($baseline/5) solid $m-blue-d1; + opacity: 1.0; + + .wrapper-step-number { + border-color: $m-blue-d1; + } + + .step-number, .step-name { + color: $m-gray-d3; + } + } + } + + .progress-sts-value { + width: 0% !important; + } + } + + // VIEW: ID + &.step-photos-id { + + // progress nav + .progress .progress-step { + + // STATE: is completed + &#progress-step1 { + border-bottom: ($baseline/5) solid $verified-color-lvl3; + + .wrapper-step-number { + border-color: $verified-color-lvl3; + } + + .step-number, .step-name { + color: $m-gray-l3; + } + } + + // STATE: is current + &#progress-step2 { + border-bottom: ($baseline/5) solid $m-blue-d1; + opacity: 1.0; + + .wrapper-step-number { + border-color: $m-blue-d1; + } + + .step-number, .step-name { + color: $m-gray-d3; + } + } + } + + .progress-sts-value { + width: 40% !important; + } + } + + // VIEW: REVIEW + &.step-review { + + // progress nav + .progress .progress-step { + + // STATE: is completed + &#progress-step1, &#progress-step2 { + border-bottom: ($baseline/5) solid $verified-color-lvl3; + + .wrapper-step-number { + border-color: $verified-color-lvl3; + } + + .step-number, .step-name { + color: $m-gray-l3; + } + } + + // STATE: is current + &#progress-step3 { + border-bottom: ($baseline/5) solid $m-blue-d1; + opacity: 1.0; + + .wrapper-step-number { + border-color: $m-blue-d1; + } + + .step-number, .step-name { + color: $m-gray-d3; + } + } + } + + .progress-sts-value { + width: 70% !important; + } + } + + &.step-confirmation { + + .content-confirmation { + margin-bottom: ($baseline*2); + } + + .view { + + .title { + @extend %hd-lv2; + color: $m-blue-d1; + } + + .instruction { + @extend %t-copy-lead1; + margin-bottom: $baseline; + } + } +>>>>>>> LMS: adds in styling and base UI for reverification flow + } +} diff --git a/lms/templates/verify_student/_reverification_support.html b/lms/templates/verify_student/_reverification_support.html new file mode 100644 index 0000000000..a2a3ed86c0 --- /dev/null +++ b/lms/templates/verify_student/_reverification_support.html @@ -0,0 +1,28 @@ +<%! from django.utils.translation import ugettext as _ %> + +
    + +
    diff --git a/lms/templates/verify_student/photo_reverification.html b/lms/templates/verify_student/photo_reverification.html index 764458b791..9e710b4048 100644 --- a/lms/templates/verify_student/photo_reverification.html +++ b/lms/templates/verify_student/photo_reverification.html @@ -3,7 +3,7 @@ <%inherit file="../main.html" /> <%namespace name='static' file='/static_content.html'/> -<%block name="bodyclass">register verification-process step-photos +<%block name="bodyclass">register verification-process is-not-verified step-photos <%block name="title">${_("Re-Verification")} <%block name="js_extra"> @@ -53,6 +53,18 @@ %endif +
    +
    + +
    +

    ${_("Please Resubmit Your Verification Information")}

    +
    +

    ${_("There was an error with your previous verification")}. ${_("In order proceed in the verified certificate of achievement track of your current courses, please complete the following steps.")}

    +
    +
    +
    +
    +
    @@ -62,19 +74,14 @@
      -
    1. - 0 - ${_("Intro")} -
    2. -
    3. 1 - ${_("Current Step: ")}${_("Take Photo")} + ${_("Current Step: ")}${_("Re-Take Photo")}
    4. 2 - ${_("Take ID Photo")} + ${_("Re-Take ID Photo")}
    5. @@ -82,12 +89,7 @@ ${_("Review")}
    6. -
    7. - 4 - ${_("Make Payment")} -
    8. - -
    9. +
    10. @@ -107,7 +109,7 @@
    + <%include file="_reverification_support.html" /> diff --git a/lms/templates/verify_student/reverification_confirmation.html b/lms/templates/verify_student/reverification_confirmation.html index 2ab639effc..5b2ee17333 100644 --- a/lms/templates/verify_student/reverification_confirmation.html +++ b/lms/templates/verify_student/reverification_confirmation.html @@ -4,15 +4,75 @@ <%inherit file="../main.html" /> <%namespace name='static' file='/static_content.html'/> -<%block name="bodyclass">register verification-process step-photos -<%block name="title">${_("Verification Submission Confirmation")} +<%block name="bodyclass">register verification-process is-not-verified step-confirmation +<%block name="title">${_("Re-Verification Submission Confirmation")} <%block name="js_extra"> <%block name="content"> -

    Successfully reverified!

    -

    Return to Dashboard

    +
    +
    + +
    +
    +

    ${_("Your Progress")}

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

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

    + +
    +

    ${_("We've captured your re-submitted information and will review it to verify your identity shortly. You should receive an update to your veriication status within 1-2 days. In the meantime, you still have access to all of your course content.")}

    +
    + +
      + +
    +
    +
    +
    +
    +
    + + <%include file="_reverification_support.html" /> +
    +
    From 153694b75fceb510dd3cb5104527fd08fe59936d Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 21 Oct 2013 22:09:11 -0400 Subject: [PATCH 07/19] LMS: removing non production-friendly messages --- lms/templates/verify_student/photo_reverification.html | 1 - lms/templates/verify_student/photo_verification.html | 1 - 2 files changed, 2 deletions(-) diff --git a/lms/templates/verify_student/photo_reverification.html b/lms/templates/verify_student/photo_reverification.html index 9e710b4048..40bcc92c11 100644 --- a/lms/templates/verify_student/photo_reverification.html +++ b/lms/templates/verify_student/photo_reverification.html @@ -72,7 +72,6 @@

    ${_("Your Progress")}

    -
    1. 1 diff --git a/lms/templates/verify_student/photo_verification.html b/lms/templates/verify_student/photo_verification.html index 92b976ce51..8e754452c0 100644 --- a/lms/templates/verify_student/photo_verification.html +++ b/lms/templates/verify_student/photo_verification.html @@ -67,7 +67,6 @@

      ${_("Your Progress")}

      -
      1. 0 From 0d01179ffc7de43d009c9d2718f2b141086776f4 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 21 Oct 2013 22:05:46 -0400 Subject: [PATCH 08/19] LMS: revises semantics and styling for dashboard messages --- lms/static/sass/elements/_typography.scss | 6 +- lms/static/sass/multicourse/_dashboard.scss | 66 +++++++++++++++++++ lms/templates/dashboard.html | 25 ++----- ...ng.html => _dashboard_course_listing.html} | 0 .../_dashboard_status_verification.html | 27 ++++++++ 5 files changed, 104 insertions(+), 20 deletions(-) rename lms/templates/dashboard/{dashboard_course_listing.html => _dashboard_course_listing.html} (100%) create mode 100644 lms/templates/dashboard/_dashboard_status_verification.html diff --git a/lms/static/sass/elements/_typography.scss b/lms/static/sass/elements/_typography.scss index 68a3cd50df..3ea9620259 100644 --- a/lms/static/sass/elements/_typography.scss +++ b/lms/static/sass/elements/_typography.scss @@ -271,14 +271,18 @@ %copy-link { border-bottom: 1px dotted transparent; +<<<<<<< HEAD &:hover, &:active, &:focus { +======= + &:hover, &:active { +>>>>>>> LMS: revises semantics and styling for dashboard messages border-color: $link-color-d1; } } %copy-badge { @extend %t-title8; - @extend %t-weight5; + @extend %t-weight3; border-radius: ($baseline/5); padding: ($baseline/2) $baseline; text-transform: uppercase; diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 47582dd538..9740298cbe 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -75,6 +75,12 @@ margin-bottom: 15px; padding-bottom: 17px; + &:last-child { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; + } + &:hover, &:focus { .title .icon { opacity: 1.0; @@ -760,4 +766,64 @@ margin-right: 10px; } } + + // account-related + .user-info { + + // status + .status { + + .list--nav { + margin: ($baseline/2) 0 0 0; + padding: 0; + } + + .nav__item { + @extend %t-weight4; + @include font-size(13); + margin-left: 26px; + } + } + } + + // status - verification + .status--verification { + + .data { + white-space: normal !important; + text-overflow: no !important; + overflow: visible !important; + } + + .list--nav { + margin-left: 26px; + } + + // STATE: is denied + &.is-denied { + + .data { + color: $error-color !important; + } + } + } + + // message + .msg { + margin: ($baseline/2) 0 ($baseline/2) 26px; + } + + .msg__title { + @extend %hd-lv5; + color: $lighter-base-font-color; + } + + .msg__copy { + @extend %copy-metadata; + color: $lighter-base-font-color; + + p { + @extend %t-copy; + } + } } diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index b2326cb01c..eb657d13e4 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -150,10 +150,10 @@ @@ -203,7 +190,7 @@ <% cert_status = cert_statuses.get(course.id) %> <% show_email_settings = (course.id in show_email_settings_for) %> <% course_mode_info = all_course_modes.get(course.id) %> - <%include file='dashboard/dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info" /> + <%include file='dashboard/_dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info" /> % endfor diff --git a/lms/templates/dashboard/dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html similarity index 100% rename from lms/templates/dashboard/dashboard_course_listing.html rename to lms/templates/dashboard/_dashboard_course_listing.html diff --git a/lms/templates/dashboard/_dashboard_status_verification.html b/lms/templates/dashboard/_dashboard_status_verification.html new file mode 100644 index 0000000000..da4915cef5 --- /dev/null +++ b/lms/templates/dashboard/_dashboard_status_verification.html @@ -0,0 +1,27 @@ +<%! from django.utils.translation import ugettext as _ %> +<%! + from django.core.urlresolvers import reverse + from courseware.courses import course_image_url, get_course_about_section + import waffle +%> + +<%namespace name='static' file='../static_content.html'/> + +%if verification_status == 'denied': +
      2. +
        ${_("Verification Status")}
        ${verification_msg} + + + +
        +

        ${_("Please Note:")}

        +
        +

        ${_("If you fail to pass a verification attempt before the course ends, you will not receive a verified certificate.")}

        +
        +
        +
      3. +%endif From c64542e10a7cec3c391f406bad56ee128650541e Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 28 Oct 2013 17:22:57 -0400 Subject: [PATCH 09/19] LMS: revises re-verification instruction UI and help text --- lms/static/sass/views/_verification.scss | 45 ++++++++++++++++--- .../verify_student/photo_reverification.html | 27 +++++------ 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index bcfc8d09bf..9bd1274928 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -432,10 +432,6 @@ } } - .wrapper-msg-reverify { - - } - // ==================== // UI: inline messages @@ -632,6 +628,38 @@ // ==================== + // UI: reverification message + .wrapper-reverification { + border-bottom: ($baseline/10) solid $m-pink; + margin-bottom: $baseline; + padding-bottom: $baseline; + position: relative; + + .deco-arrow { + @include triangle($baseline, $m-pink, down); + position: absolute; + bottom: -($baseline); + left: 50%; + } + } + + .reverification { + + .message { + + .title { + @extend %hd-lv3; + color: $m-pink; + } + + .copy { + @extend %t-copy-sub1; + } + } + } + + // ==================== + // UI: slides .carousel { @@ -709,6 +737,10 @@ padding-bottom: 0; } } + + .help-item-emphasis { + @extend %t-weight4; + } } // help - faq @@ -1930,6 +1962,10 @@ // STATE: re-verifying .register.is-not-verified { + .help-item-emphasis { + color: $m-pink; + } + // progress indicator .progress-sts { width: 72%; @@ -2058,6 +2094,5 @@ margin-bottom: $baseline; } } ->>>>>>> LMS: adds in styling and base UI for reverification flow } } diff --git a/lms/templates/verify_student/photo_reverification.html b/lms/templates/verify_student/photo_reverification.html index 40bcc92c11..2b1bdaf920 100644 --- a/lms/templates/verify_student/photo_reverification.html +++ b/lms/templates/verify_student/photo_reverification.html @@ -53,21 +53,22 @@ %endif -
        -
        - -
        -

        ${_("Please Resubmit Your Verification Information")}

        -
        -

        ${_("There was an error with your previous verification")}. ${_("In order proceed in the verified certificate of achievement track of your current courses, please complete the following steps.")}

        -
        -
        -
        -
        -
        +
        +
        +
        +

        ${_("Please Resubmit Your Verification Information")}

        +
        +

        ${_("There was an error with your previous verification. In order proceed in the verified certificate of achievement track of your current courses, please complete the following steps.")}

        +
        +
        + + +
        +
        +

        ${_("Your Progress")}

        @@ -241,10 +242,10 @@
        • ${_("Make sure your ID is well-lit")}
        • +
        • ${_("Acceptable IDs include drivers licenses, passports, or other goverment-issued IDs that include your name and photo")}
        • ${_("Check that there isn't any glare")}
        • ${_("Ensure that you can see your photo and read your name")}
        • ${_("Try to keep your fingers at the edge to avoid covering important information")}
        • -
        • ${_("Acceptable IDs include drivers licenses, passports, or other goverment-issued IDs that include your name and photo")}
        • ${_("Once in position, use the camera button")} () ${_("to capture your ID")}
        • ${_("Use the checkmark button")} () ${_("once you are happy with the photo")}
        From 74f0e8856843a09cd770e5e3156367a53429549a Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 28 Oct 2013 18:19:15 -0400 Subject: [PATCH 10/19] LMS: revises UI states for verification status in dashboard view --- lms/static/sass/elements/_controls.scss | 17 +++ lms/static/sass/elements/_typography.scss | 4 - lms/static/sass/multicourse/_dashboard.scss | 105 ++++++++++++++++++ .../_dashboard_status_verification.html | 51 +++++++-- 4 files changed, 163 insertions(+), 14 deletions(-) diff --git a/lms/static/sass/elements/_controls.scss b/lms/static/sass/elements/_controls.scss index 4846a14ae5..b1e323effc 100644 --- a/lms/static/sass/elements/_controls.scss +++ b/lms/static/sass/elements/_controls.scss @@ -84,6 +84,23 @@ } } +// blue primary error color +%btn-primary-error { + @extend %btn-primary; + box-shadow: 0 2px 1px 0 shade($error-color, 25%); + background: shade($error-color, 25%); + color: $white; + + &:hover, &:active { + background: $error-color; + color: $white; + } + + &.disabled, &[disabled] { + box-shadow: none; + } +} + // blue primary button %btn-primary-blue { @extend %btn-primary; diff --git a/lms/static/sass/elements/_typography.scss b/lms/static/sass/elements/_typography.scss index 3ea9620259..2d677700c6 100644 --- a/lms/static/sass/elements/_typography.scss +++ b/lms/static/sass/elements/_typography.scss @@ -271,11 +271,7 @@ %copy-link { border-bottom: 1px dotted transparent; -<<<<<<< HEAD &:hover, &:active, &:focus { -======= - &:hover, &:active { ->>>>>>> LMS: revises semantics and styling for dashboard messages border-color: $link-color-d1; } } diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 9740298cbe..0b4f33efa2 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -786,6 +786,111 @@ } } + // status - verification + .status-verification { + + .status-title { + margin: 0 0 ($baseline/4) 26px; + } + + .status-data { + margin: 0 0 ($baseline/2) 26px; + width: 80%; + } + + .status-data-message { + @extend %t-copy-sub1; + @extend %t-weight4; + margin-bottom: ($baseline/2); + } + + .list-actions { + @extend %ui-no-list; + + .action { + @extend %t-weight4; + display: block; + @include font-size(14); + } + } + + .status-note { + @extend %t-copy-sub2; + position: relative; + margin-top: $baseline; + border-top: 1px solid $black-t0; + padding-top: ($baseline/2); + + p { + @extend %t-copy-sub2; + } + + .deco-arrow { + @include triangle(($baseline/2), $m-gray-d3, up); + position: absolute; + left: 45%; + top: -($baseline/2); + } + } + + // CASE: is denied + &.is-denied { + + .status-data-message { + color: $error-color; + border-bottom-color: rgba($error-color, 0.25); + } + + .status-note { + color: desaturate($error-color, 65%); + border-top-color: rgba($error-color, 0.25); + } + + .action-reverify { + @extend %btn-primary-error; + @extend %t-weight4; + display: block; + @include font-size(14); + } + + .deco-arrow { + @include triangle(($baseline/2), $error-color, up); + } + } + + // CASE: is accepted + &.is-accepted { + + .status-data-message { + color: $verified-color-lvl1; + border-bottom-color: $verified-color-lvl4; + } + + .status-note { + color: $m-gray-l1; + border-top-color: $verified-color-lvl4; + } + + .deco-arrow { + @include triangle(($baseline/2), $verified-color-lvl4, up); + } + } + + // CASE: is pending + &.is-pending { + + .status-data-message { + color: $m-gray-d3; + border-bottom-color: $m-gray-l4; + } + + .status-note { + color: $m-gray-l1; + border-top-color: $m-gray-d3; + } + } + } + // status - verification .status--verification { diff --git a/lms/templates/dashboard/_dashboard_status_verification.html b/lms/templates/dashboard/_dashboard_status_verification.html index da4915cef5..f5c0dd7aae 100644 --- a/lms/templates/dashboard/_dashboard_status_verification.html +++ b/lms/templates/dashboard/_dashboard_status_verification.html @@ -8,20 +8,51 @@ <%namespace name='static' file='../static_content.html'/> %if verification_status == 'denied': -
      4. -
        ${_("Verification Status")}
        ${verification_msg} +
      5. + ${_("ID-Verification Status")} - -
        -

        ${_("Please Note:")}

        -
        -

        ${_("If you fail to pass a verification attempt before the course ends, you will not receive a verified certificate.")}

        +
        + + +

        ${_("If you fail to pass a verification attempt before your course ends, you will not receive a verified certificate.")}

      6. %endif + +
      7. + ${_("ID-Verification Status")} + +
        + ${_("Reviewed and Verified")} + +
        + + +

        ${_("Your verification status is good until October 31, 2013")}

        +
        +
        +
      8. + +
      9. + ${_("ID-Verification Status")} + +
        + ${_("Pending")} + +
        + + +

        ${_("Your verification, submitted on October 29, 2013, is pending.")}

        +
        +
        +
      10. From b2a755de24b77651faf9f3f23e79fa11c579b110 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 28 Oct 2013 13:17:27 -0400 Subject: [PATCH 11/19] Add hooks for other statuses onto the dashboard --- .../dashboard/_dashboard_status_verification.html | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lms/templates/dashboard/_dashboard_status_verification.html b/lms/templates/dashboard/_dashboard_status_verification.html index f5c0dd7aae..3cee60e621 100644 --- a/lms/templates/dashboard/_dashboard_status_verification.html +++ b/lms/templates/dashboard/_dashboard_status_verification.html @@ -1,12 +1,21 @@ <%! from django.utils.translation import ugettext as _ %> <%! from django.core.urlresolvers import reverse - from courseware.courses import course_image_url, get_course_about_section - import waffle %> <%namespace name='static' file='../static_content.html'/> +%if verification_status == 'approved': + +%endif + +<%doc> +This is for 'pending' statuses, where we are waiting for a response + +%if verification_status in ('must_retry', 'submitted'): + +%endif + %if verification_status == 'denied':
      11. ${_("ID-Verification Status")} From df20a1028d443103259f9be58198c113840406a7 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 29 Oct 2013 09:31:27 -0400 Subject: [PATCH 12/19] Wire up new statuses onto the dashboard. LMS-1133 --- .../_dashboard_status_verification.html | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/lms/templates/dashboard/_dashboard_status_verification.html b/lms/templates/dashboard/_dashboard_status_verification.html index 3cee60e621..0f542cce21 100644 --- a/lms/templates/dashboard/_dashboard_status_verification.html +++ b/lms/templates/dashboard/_dashboard_status_verification.html @@ -6,13 +6,38 @@ <%namespace name='static' file='../static_content.html'/> %if verification_status == 'approved': +
      12. + ${_("ID-Verification Status")} +
        + ${_("Reviewed and Verified")} + +
        + + +

        ${_("Your verification status is good until October 31, 2013")}

        +
        +
        +
      13. %endif <%doc> This is for 'pending' statuses, where we are waiting for a response %if verification_status in ('must_retry', 'submitted'): +
      14. + ${_("ID-Verification Status")} + +
        + ${_("Pending")} + +
        + + +

        ${_("Your verification, submitted on October 29, 2013, is pending.")}

        +
        +
        +
      15. %endif @@ -38,30 +63,4 @@ This is for 'pending' statuses, where we are waiting for a response %endif -
      16. - ${_("ID-Verification Status")} -
        - ${_("Reviewed and Verified")} - -
        - - -

        ${_("Your verification status is good until October 31, 2013")}

        -
        -
        -
      17. - -
      18. - ${_("ID-Verification Status")} - -
        - ${_("Pending")} - -
        - - -

        ${_("Your verification, submitted on October 29, 2013, is pending.")}

        -
        -
        -
      19. From 78bbaa02b88a3f5d4b2704452ef8933b6d52092c Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 29 Oct 2013 14:55:45 -0400 Subject: [PATCH 13/19] Add in human-readable text for error messages. LMS-1133 --- lms/djangoapps/verify_student/models.py | 20 ++++++++++++------- .../verify_student/tests/test_models.py | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 17cffa4ead..f4466dbc85 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -534,10 +534,14 @@ class SoftwareSecurePhotoVerification(PhotoVerification): Returns a list of error messages """ - # Translates the category names into something more human readable - category_dict = { - "photoIdReasons": _("Photo ID Issues: "), - "generalReasons": u"" + # Translates the category names and messages into something more human readable + message_dict = { + ("photoIdReasons", "Not provided"): _("No photo ID was provided."), + ("photoIdReasons", "Text not clear"): _("The text in your photo ID image was not clear."), + ("generalReasons", "Name mismatch"): _("The name associated with your account and the name on your ID do not match."), + ("generalReasons", "Expected name missing"): _("We were unable to send your name along with your photo."), + ("userPhotoReasons", "Image not clear"): _("The image of your face was not clear."), + ("userPhotoReasons", "Face out of view"): _("Your face was not in view for your face photo"), } try: @@ -546,13 +550,15 @@ class SoftwareSecurePhotoVerification(PhotoVerification): msg = [] for category in msg_dict: - # translate the category into a human-readable name - category_name = category_dict[category] - msg.append(category_name + u", ".join(msg_dict[category])) + # find the messages associated with this category + category_msgs = msg_dict[category] + for category_msg in category_msgs: + msg.append(message_dict[(category, category_msg)]) return u", ".join(msg) except (ValueError, KeyError): # if we can't parse the message as JSON or the category doesn't # match one of our known categories, show a generic error + log.error('PhotoVerification: Error parsing this error message: %s', self.error_msg) return _("There was an error verifying your ID photos.") def image_url(self, name): diff --git a/lms/djangoapps/verify_student/tests/test_models.py b/lms/djangoapps/verify_student/tests/test_models.py index dcaa9cade9..e497c2d46c 100644 --- a/lms/djangoapps/verify_student/tests/test_models.py +++ b/lms/djangoapps/verify_student/tests/test_models.py @@ -331,7 +331,7 @@ class TestPhotoVerification(TestCase): attempt2.save() status = SoftwareSecurePhotoVerification.user_status(user) - self.assertEquals(status, (attempt2.status, "Photo ID Issues: Not provided")) + self.assertEquals(status, (attempt2.status, "No photo ID was provided.")) def test_parse_error_msg_success(self): user = UserFactory.create() @@ -339,7 +339,7 @@ class TestPhotoVerification(TestCase): attempt.status = 'denied' attempt.error_msg = '[{"photoIdReasons": ["Not provided"]}]' parsed_error_msg = attempt.parse_error_msg() - self.assertEquals("Photo ID Issues: Not provided", parsed_error_msg) + self.assertEquals("No photo ID was provided.", parsed_error_msg) def test_parse_error_msg_failure(self): user = UserFactory.create() From 97b076cb84d4ebf0fb97bc6b5c072505397f6629 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 30 Oct 2013 09:24:00 -0400 Subject: [PATCH 14/19] Hide certificates section if enrolled as audit. LMS-1133 --- lms/templates/dashboard/_dashboard_course_listing.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index 0a7925e93b..c43e7e79de 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -57,7 +57,7 @@ - % if course.has_ended() and cert_status: + % if course.has_ended() and cert_status and not enrollment.mode == 'audit': <% if cert_status['status'] == 'generating': status_css_class = 'course-status-certrendering' From d939a61e938f848fb3e63f5982d34e40d4bf5a51 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 30 Oct 2013 16:39:22 -0400 Subject: [PATCH 15/19] Update error messages and copy for re-verification flow --- lms/djangoapps/verify_student/models.py | 5 ++--- lms/templates/verify_student/_reverification_support.html | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index f4466dbc85..156cfb3ece 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -537,11 +537,10 @@ class SoftwareSecurePhotoVerification(PhotoVerification): # Translates the category names and messages into something more human readable message_dict = { ("photoIdReasons", "Not provided"): _("No photo ID was provided."), - ("photoIdReasons", "Text not clear"): _("The text in your photo ID image was not clear."), + ("photoIdReasons", "Text not clear"): _("We couldn't read your name from your photo ID image."), ("generalReasons", "Name mismatch"): _("The name associated with your account and the name on your ID do not match."), - ("generalReasons", "Expected name missing"): _("We were unable to send your name along with your photo."), ("userPhotoReasons", "Image not clear"): _("The image of your face was not clear."), - ("userPhotoReasons", "Face out of view"): _("Your face was not in view for your face photo"), + ("userPhotoReasons", "Face out of view"): _("Your face was not visible in your self-photo"), } try: diff --git a/lms/templates/verify_student/_reverification_support.html b/lms/templates/verify_student/_reverification_support.html index a2a3ed86c0..14b249762e 100644 --- a/lms/templates/verify_student/_reverification_support.html +++ b/lms/templates/verify_student/_reverification_support.html @@ -6,7 +6,12 @@
      20. ${_("Why Do I Need to Re-Verify?")}

        -

        ${_("Nulla vitae elit libero, a pharetra augue. Donec id elit non mi porta gravida at eget metus. Maecenas sed diam eget risus varius blandit sit amet non magna.")}

        +

        ${_("There was a problem with your original verification. To make sure that your identity is correctly associated with your course progress, we need to retake your photo and a photo of your identification document. If you don't have a valid identification document, contact {link_start}{support_email}{link_end}.").format( + support_email=settings.DEFAULT_FEEDBACK_EMAIL, + link_start=u''.format( + address=settings.DEFAULT_FEEDBACK_EMAIL, + subject_line=_('Problem with ID re-verification')), + link_end=u'')}

      21. From 5389d6b7cf33cf9a23482e68face00043821df5f Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 31 Oct 2013 11:12:16 -0400 Subject: [PATCH 16/19] Remove DAYS_GOOD_FOR from being a class variable. LMS-1387 --- cms/envs/common.py | 4 ---- lms/djangoapps/verify_student/models.py | 5 ++--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index cd3c11cfaf..f225e22061 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -418,10 +418,6 @@ MKTG_URL_LINK_MAP = { COURSES_WITH_UNSAFE_CODE = [] -################# Student Verification ################# -VERIFY_STUDENT = { - "DAYS_GOOD_FOR": 365, # How many days is a verficiation good for? -} ############################## EVENT TRACKING ################################# diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 156cfb3ece..6acfe0e889 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -115,8 +115,6 @@ class PhotoVerification(StatusModel): attempt.status == "created" pending_requests = PhotoVerification.submitted.all() """ - # We can make this configurable later... - DAYS_GOOD_FOR = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"] ######################## Fields Set During Creation ######################## # See class docstring for description of status states @@ -181,8 +179,9 @@ class PhotoVerification(StatusModel): Returns the earliest allowed date given the settings """ + DAYS_GOOD_FOR = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"] allowed_date = ( - datetime.now(pytz.UTC) - timedelta(days=cls.DAYS_GOOD_FOR) + datetime.now(pytz.UTC) - timedelta(days=DAYS_GOOD_FOR) ) return allowed_date From 17610edf77b7903b613b25098e582ee53fdc7d18 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 31 Oct 2013 14:20:57 -0400 Subject: [PATCH 17/19] Code cleanup due to review comments * make `user_status` more intelligent * remove some logic from the templates * rename `parse_error_msg` to `parsed_error_msg` * fix up and add more tests LMS-1387 --- cms/envs/common.py | 1 - common/djangoapps/student/views.py | 27 ++++++----- lms/djangoapps/verify_student/models.py | 46 +++++++++++++------ .../verify_student/tests/test_models.py | 15 ++++-- .../_dashboard_status_verification.html | 7 +-- 5 files changed, 57 insertions(+), 39 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index f225e22061..8d28ddcd8d 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -418,7 +418,6 @@ MKTG_URL_LINK_MAP = { COURSES_WITH_UNSAFE_CODE = [] - ############################## EVENT TRACKING ################################# TRACK_MAX_EVENT = 10000 diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index e5582e2f27..ca8ccf91b0 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -25,14 +25,13 @@ from django.core.validators import validate_email, validate_slug, ValidationErro from django.core.exceptions import ObjectDoesNotExist from django.db import IntegrityError, transaction from django.http import (HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, - HttpResponseNotAllowed, Http404) + Http404) from django.shortcuts import redirect from django_future.csrf import ensure_csrf_cookie -from django.utils.http import cookie_date, base36_to_int, urlencode +from django.utils.http import cookie_date, base36_to_int from django.utils.translation import ugettext as _ from django.views.decorators.http import require_POST, require_GET from django.contrib.admin.views.decorators import staff_member_required -from django.utils.translation import ugettext as _u from ratelimitbackend.exceptions import RateLimitException @@ -337,7 +336,7 @@ def dashboard(request): ) ) # Verification Attempts - verification_status = SoftwareSecurePhotoVerification.user_status(user) + verification_status, verification_msg = SoftwareSecurePhotoVerification.user_status(user) # get info w.r.t ExternalAuthMap external_auth_map = None try: @@ -355,8 +354,8 @@ def dashboard(request): 'all_course_modes': course_modes, 'cert_statuses': cert_statuses, 'show_email_settings_for': show_email_settings_for, - 'verification_status': verification_status[0], - 'verification_msg': verification_status[1], + 'verification_status': verification_status, + 'verification_msg': verification_msg, } return render_to_response('dashboard.html', context) @@ -663,11 +662,11 @@ def manage_user_standing(request): row = [user.username, user.standing.all()[0].changed_by] rows.append(row) - context = {'headers': headers, 'rows': rows} return render_to_response("manage_user_standing.html", context) + @require_POST @login_required @ensure_csrf_cookie @@ -681,34 +680,34 @@ def disable_account_ajax(request): username = request.POST.get('username') context = {} if username is None or username.strip() == '': - context['message'] = _u('Please enter a username') + context['message'] = _('Please enter a username') return JsonResponse(context, status=400) account_action = request.POST.get('account_action') if account_action is None: - context['message'] = _u('Please choose an option') + context['message'] = _('Please choose an option') return JsonResponse(context, status=400) username = username.strip() try: user = User.objects.get(username=username) except User.DoesNotExist: - context['message'] = _u("User with username {} does not exist").format(username) + context['message'] = _("User with username {} does not exist").format(username) return JsonResponse(context, status=400) else: - user_account, _ = UserStanding.objects.get_or_create( + user_account, _succss = UserStanding.objects.get_or_create( user=user, defaults={'changed_by': request.user}, ) if account_action == 'disable': user_account.account_status = UserStanding.ACCOUNT_DISABLED - context['message'] = _u("Successfully disabled {}'s account").format(username) + context['message'] = _("Successfully disabled {}'s account").format(username) log.info("{} disabled {}'s account".format(request.user, username)) elif account_action == 'reenable': user_account.account_status = UserStanding.ACCOUNT_ENABLED - context['message'] = _u("Successfully reenabled {}'s account").format(username) + context['message'] = _("Successfully reenabled {}'s account").format(username) log.info("{} reenabled {}'s account".format(request.user, username)) else: - context['message'] = _u("Unexpected account status") + context['message'] = _("Unexpected account status") return JsonResponse(context, status=400) user_account.changed_by = request.user user_account.standing_last_changed_at = datetime.datetime.now(UTC) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 6acfe0e889..523e8393a4 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -115,7 +115,6 @@ class PhotoVerification(StatusModel): attempt.status == "created" pending_requests = PhotoVerification.submitted.all() """ - ######################## Fields Set During Creation ######################## # See class docstring for description of status states STATUS = Choices('created', 'ready', 'submitted', 'must_retry', 'approved', 'denied') @@ -233,27 +232,44 @@ class PhotoVerification(StatusModel): @classmethod def user_status(cls, user): """ - Returns the status of the user based on their latest verification attempt + Returns the status of the user based on their past verification attempts If no such verification exists, returns 'none' If verification has expired, returns 'expired' + 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' """ - try: - attempts = cls.objects.filter(user=user).order_by('-updated_at') - attempt = attempts[0] - except IndexError: - return ('none', '') + status = 'none' + error_msg = '' - if attempt.created_at < cls._earliest_allowed_date(): - return ('expired', '') + if cls.user_is_verified(user): + status = 'approved' + elif cls.user_has_valid_or_pending(user): + # user_has_valid_or_pending does include 'approved', but if we are + # here, we know that the attempt is still pending + status = 'pending' + else: + # we need to check the most recent attempt to see if we need to ask them to do + # a retry + try: + attempts = cls.objects.filter(user=user).order_by('-updated_at') + attempt = attempts[0] + except IndexError: + return ('none', error_msg) + if attempt.created_at < cls._earliest_allowed_date(): + return ('expired', error_msg) - error_msg = attempt.error_msg - if attempt.error_msg: - error_msg = attempt.parse_error_msg() + # right now, this is the only state at which they must reverify. It + # may change later + if attempt.status == 'denied': + status = 'must_reverify' + if attempt.error_msg: + error_msg = attempt.parsed_error_msg() - return (attempt.status, error_msg) + return (status, error_msg) - def parse_error_msg(self): + def parsed_error_msg(self): """ Sometimes, the error message we've received needs to be parsed into something more human readable @@ -523,7 +539,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification): self.status = "must_retry" self.save() - def parse_error_msg(self): + def parsed_error_msg(self): """ Parse the error messages we receive from SoftwareSecure diff --git a/lms/djangoapps/verify_student/tests/test_models.py b/lms/djangoapps/verify_student/tests/test_models.py index e497c2d46c..fd2b767859 100644 --- a/lms/djangoapps/verify_student/tests/test_models.py +++ b/lms/djangoapps/verify_student/tests/test_models.py @@ -321,7 +321,7 @@ class TestPhotoVerification(TestCase): attempt.save() status = SoftwareSecurePhotoVerification.user_status(user) - self.assertEquals(status, (attempt.status, '')) + self.assertEquals(status, ('approved', '')) # create another one for the same user, make sure the right one is # returned @@ -331,14 +331,20 @@ class TestPhotoVerification(TestCase): attempt2.save() status = SoftwareSecurePhotoVerification.user_status(user) - self.assertEquals(status, (attempt2.status, "No photo ID was provided.")) + self.assertEquals(status, ('approved', '')) + + # now delete the first one and verify that the denial is being handled + # properly + attempt.delete() + status = SoftwareSecurePhotoVerification.user_status(user) + self.assertEquals(status, ('must_reverify', "No photo ID was provided.")) def test_parse_error_msg_success(self): user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = 'denied' attempt.error_msg = '[{"photoIdReasons": ["Not provided"]}]' - parsed_error_msg = attempt.parse_error_msg() + parsed_error_msg = attempt.parsed_error_msg() self.assertEquals("No photo ID was provided.", parsed_error_msg) def test_parse_error_msg_failure(self): @@ -350,8 +356,9 @@ class TestPhotoVerification(TestCase): 'Not Provided', '[{"IdReasons": ["Not provided"]}]', '{"IdReasons": ["Not provided"]}', + u'[{"ïḋṚëäṡöṅṡ": ["Ⓝⓞⓣ ⓟⓡⓞⓥⓘⓓⓔⓓ "]}]', } for msg in bad_messages: attempt.error_msg = msg - parsed_error_msg = attempt.parse_error_msg() + parsed_error_msg = attempt.parsed_error_msg() self.assertEquals(parsed_error_msg, "There was an error verifying your ID photos.") diff --git a/lms/templates/dashboard/_dashboard_status_verification.html b/lms/templates/dashboard/_dashboard_status_verification.html index 0f542cce21..6bdd76970c 100644 --- a/lms/templates/dashboard/_dashboard_status_verification.html +++ b/lms/templates/dashboard/_dashboard_status_verification.html @@ -21,10 +21,7 @@ %endif -<%doc> -This is for 'pending' statuses, where we are waiting for a response - -%if verification_status in ('must_retry', 'submitted'): +%if verification_status == 'pending':
      22. ${_("ID-Verification Status")} @@ -41,7 +38,7 @@ This is for 'pending' statuses, where we are waiting for a response %endif -%if verification_status == 'denied': +%if verification_status == 'must_reverify':
      23. ${_("ID-Verification Status")} From 002972f24c8ef6c67b1b7bed19af6cf23eced40c Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 1 Nov 2013 15:47:04 -0400 Subject: [PATCH 18/19] Remove dates from status messages * clean up one last typo LMS-1387 --- common/djangoapps/student/views.py | 2 +- lms/templates/dashboard/_dashboard_status_verification.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index ca8ccf91b0..75c9b75821 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -695,7 +695,7 @@ def disable_account_ajax(request): context['message'] = _("User with username {} does not exist").format(username) return JsonResponse(context, status=400) else: - user_account, _succss = UserStanding.objects.get_or_create( + user_account, _success = UserStanding.objects.get_or_create( user=user, defaults={'changed_by': request.user}, ) if account_action == 'disable': diff --git a/lms/templates/dashboard/_dashboard_status_verification.html b/lms/templates/dashboard/_dashboard_status_verification.html index 6bdd76970c..382ed02d4f 100644 --- a/lms/templates/dashboard/_dashboard_status_verification.html +++ b/lms/templates/dashboard/_dashboard_status_verification.html @@ -15,7 +15,7 @@
        -

        ${_("Your verification status is good until October 31, 2013")}

        +

        ${_("Your verification status is good for one year after submission.")}

      24. @@ -31,7 +31,7 @@
        -

        ${_("Your verification, submitted on October 29, 2013, is pending.")}

        +

        ${_("Your verification photos have been submitted and are awaiting a response.")}

        From b5ec2c72f97eee64881446409ec8c3d23b198069 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 4 Nov 2013 10:59:50 -0500 Subject: [PATCH 19/19] LMS: revises instructional copy for reverification flow --- lms/templates/dashboard/_dashboard_status_verification.html | 2 +- lms/templates/verify_student/photo_reverification.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/templates/dashboard/_dashboard_status_verification.html b/lms/templates/dashboard/_dashboard_status_verification.html index 382ed02d4f..65ccedb323 100644 --- a/lms/templates/dashboard/_dashboard_status_verification.html +++ b/lms/templates/dashboard/_dashboard_status_verification.html @@ -31,7 +31,7 @@
        -

        ${_("Your verification photos have been submitted and are awaiting a response.")}

        +

        ${_("Your verification photos have been submitted and will be reviewed shortly.")}

        diff --git a/lms/templates/verify_student/photo_reverification.html b/lms/templates/verify_student/photo_reverification.html index 2b1bdaf920..4203afe773 100644 --- a/lms/templates/verify_student/photo_reverification.html +++ b/lms/templates/verify_student/photo_reverification.html @@ -46,7 +46,7 @@

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

        -

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

        +

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