Commit 17610edf by Diana Huang

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
parent 5389d6b7
...@@ -418,7 +418,6 @@ MKTG_URL_LINK_MAP = { ...@@ -418,7 +418,6 @@ MKTG_URL_LINK_MAP = {
COURSES_WITH_UNSAFE_CODE = [] COURSES_WITH_UNSAFE_CODE = []
############################## EVENT TRACKING ################################# ############################## EVENT TRACKING #################################
TRACK_MAX_EVENT = 10000 TRACK_MAX_EVENT = 10000
......
...@@ -25,14 +25,13 @@ from django.core.validators import validate_email, validate_slug, ValidationErro ...@@ -25,14 +25,13 @@ from django.core.validators import validate_email, validate_slug, ValidationErro
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.http import (HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, from django.http import (HttpResponse, HttpResponseBadRequest, HttpResponseForbidden,
HttpResponseNotAllowed, Http404) Http404)
from django.shortcuts import redirect from django.shortcuts import redirect
from django_future.csrf import ensure_csrf_cookie 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.utils.translation import ugettext as _
from django.views.decorators.http import require_POST, require_GET from django.views.decorators.http import require_POST, require_GET
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.utils.translation import ugettext as _u
from ratelimitbackend.exceptions import RateLimitException from ratelimitbackend.exceptions import RateLimitException
...@@ -337,7 +336,7 @@ def dashboard(request): ...@@ -337,7 +336,7 @@ def dashboard(request):
) )
) )
# Verification Attempts # Verification Attempts
verification_status = SoftwareSecurePhotoVerification.user_status(user) verification_status, verification_msg = SoftwareSecurePhotoVerification.user_status(user)
# get info w.r.t ExternalAuthMap # get info w.r.t ExternalAuthMap
external_auth_map = None external_auth_map = None
try: try:
...@@ -355,8 +354,8 @@ def dashboard(request): ...@@ -355,8 +354,8 @@ def dashboard(request):
'all_course_modes': course_modes, 'all_course_modes': course_modes,
'cert_statuses': cert_statuses, 'cert_statuses': cert_statuses,
'show_email_settings_for': show_email_settings_for, 'show_email_settings_for': show_email_settings_for,
'verification_status': verification_status[0], 'verification_status': verification_status,
'verification_msg': verification_status[1], 'verification_msg': verification_msg,
} }
return render_to_response('dashboard.html', context) return render_to_response('dashboard.html', context)
...@@ -663,11 +662,11 @@ def manage_user_standing(request): ...@@ -663,11 +662,11 @@ def manage_user_standing(request):
row = [user.username, user.standing.all()[0].changed_by] row = [user.username, user.standing.all()[0].changed_by]
rows.append(row) rows.append(row)
context = {'headers': headers, 'rows': rows} context = {'headers': headers, 'rows': rows}
return render_to_response("manage_user_standing.html", context) return render_to_response("manage_user_standing.html", context)
@require_POST @require_POST
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -681,34 +680,34 @@ def disable_account_ajax(request): ...@@ -681,34 +680,34 @@ def disable_account_ajax(request):
username = request.POST.get('username') username = request.POST.get('username')
context = {} context = {}
if username is None or username.strip() == '': 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) return JsonResponse(context, status=400)
account_action = request.POST.get('account_action') account_action = request.POST.get('account_action')
if account_action is None: if account_action is None:
context['message'] = _u('Please choose an option') context['message'] = _('Please choose an option')
return JsonResponse(context, status=400) return JsonResponse(context, status=400)
username = username.strip() username = username.strip()
try: try:
user = User.objects.get(username=username) user = User.objects.get(username=username)
except User.DoesNotExist: 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) return JsonResponse(context, status=400)
else: else:
user_account, _ = UserStanding.objects.get_or_create( user_account, _succss = UserStanding.objects.get_or_create(
user=user, defaults={'changed_by': request.user}, user=user, defaults={'changed_by': request.user},
) )
if account_action == 'disable': if account_action == 'disable':
user_account.account_status = UserStanding.ACCOUNT_DISABLED 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)) log.info("{} disabled {}'s account".format(request.user, username))
elif account_action == 'reenable': elif account_action == 'reenable':
user_account.account_status = UserStanding.ACCOUNT_ENABLED 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)) log.info("{} reenabled {}'s account".format(request.user, username))
else: else:
context['message'] = _u("Unexpected account status") context['message'] = _("Unexpected account status")
return JsonResponse(context, status=400) return JsonResponse(context, status=400)
user_account.changed_by = request.user user_account.changed_by = request.user
user_account.standing_last_changed_at = datetime.datetime.now(UTC) user_account.standing_last_changed_at = datetime.datetime.now(UTC)
......
...@@ -115,7 +115,6 @@ class PhotoVerification(StatusModel): ...@@ -115,7 +115,6 @@ class PhotoVerification(StatusModel):
attempt.status == "created" attempt.status == "created"
pending_requests = PhotoVerification.submitted.all() pending_requests = PhotoVerification.submitted.all()
""" """
######################## Fields Set During Creation ######################## ######################## Fields Set During Creation ########################
# See class docstring for description of status states # See class docstring for description of status states
STATUS = Choices('created', 'ready', 'submitted', 'must_retry', 'approved', 'denied') STATUS = Choices('created', 'ready', 'submitted', 'must_retry', 'approved', 'denied')
...@@ -233,27 +232,44 @@ class PhotoVerification(StatusModel): ...@@ -233,27 +232,44 @@ class PhotoVerification(StatusModel):
@classmethod @classmethod
def user_status(cls, user): 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 no such verification exists, returns 'none'
If verification has expired, returns 'expired' If verification has expired, returns 'expired'
""" If the verification has been approved, returns 'approved'
try: If the verification process is still ongoing, returns 'pending'
attempts = cls.objects.filter(user=user).order_by('-updated_at') If the verification has been denied and the user must resubmit photos, returns 'must_reverify'
attempt = attempts[0] """
except IndexError: status = 'none'
return ('none', '') error_msg = ''
if attempt.created_at < cls._earliest_allowed_date(): if cls.user_is_verified(user):
return ('expired', '') status = 'approved'
elif cls.user_has_valid_or_pending(user):
error_msg = attempt.error_msg # user_has_valid_or_pending does include 'approved', but if we are
if attempt.error_msg: # here, we know that the attempt is still pending
error_msg = attempt.parse_error_msg() status = 'pending'
else:
return (attempt.status, error_msg) # we need to check the most recent attempt to see if we need to ask them to do
# a retry
def parse_error_msg(self): 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)
# 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 (status, error_msg)
def parsed_error_msg(self):
""" """
Sometimes, the error message we've received needs to be parsed into Sometimes, the error message we've received needs to be parsed into
something more human readable something more human readable
...@@ -523,7 +539,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification): ...@@ -523,7 +539,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
self.status = "must_retry" self.status = "must_retry"
self.save() self.save()
def parse_error_msg(self): def parsed_error_msg(self):
""" """
Parse the error messages we receive from SoftwareSecure Parse the error messages we receive from SoftwareSecure
......
...@@ -321,7 +321,7 @@ class TestPhotoVerification(TestCase): ...@@ -321,7 +321,7 @@ class TestPhotoVerification(TestCase):
attempt.save() attempt.save()
status = SoftwareSecurePhotoVerification.user_status(user) 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 # create another one for the same user, make sure the right one is
# returned # returned
...@@ -331,14 +331,20 @@ class TestPhotoVerification(TestCase): ...@@ -331,14 +331,20 @@ class TestPhotoVerification(TestCase):
attempt2.save() attempt2.save()
status = SoftwareSecurePhotoVerification.user_status(user) 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): def test_parse_error_msg_success(self):
user = UserFactory.create() user = UserFactory.create()
attempt = SoftwareSecurePhotoVerification(user=user) attempt = SoftwareSecurePhotoVerification(user=user)
attempt.status = 'denied' attempt.status = 'denied'
attempt.error_msg = '[{"photoIdReasons": ["Not provided"]}]' 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) self.assertEquals("No photo ID was provided.", parsed_error_msg)
def test_parse_error_msg_failure(self): def test_parse_error_msg_failure(self):
...@@ -350,8 +356,9 @@ class TestPhotoVerification(TestCase): ...@@ -350,8 +356,9 @@ class TestPhotoVerification(TestCase):
'Not Provided', 'Not Provided',
'[{"IdReasons": ["Not provided"]}]', '[{"IdReasons": ["Not provided"]}]',
'{"IdReasons": ["Not provided"]}', '{"IdReasons": ["Not provided"]}',
u'[{"ïḋṚëäṡöṅṡ": ["Ⓝⓞⓣ ⓟⓡⓞⓥⓘⓓⓔⓓ "]}]',
} }
for msg in bad_messages: for msg in bad_messages:
attempt.error_msg = msg 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.") self.assertEquals(parsed_error_msg, "There was an error verifying your ID photos.")
...@@ -21,10 +21,7 @@ ...@@ -21,10 +21,7 @@
</li> </li>
%endif %endif
<%doc> %if verification_status == 'pending':
This is for 'pending' statuses, where we are waiting for a response
</%doc>
%if verification_status in ('must_retry', 'submitted'):
<li class="status status-verification is-pending"> <li class="status status-verification is-pending">
<span class="title status-title">${_("ID-Verification Status")}</span> <span class="title status-title">${_("ID-Verification Status")}</span>
...@@ -41,7 +38,7 @@ This is for 'pending' statuses, where we are waiting for a response ...@@ -41,7 +38,7 @@ This is for 'pending' statuses, where we are waiting for a response
%endif %endif
%if verification_status == 'denied': %if verification_status == 'must_reverify':
<li class="status status-verification is-denied"> <li class="status status-verification is-denied">
<span class="title status-title">${_("ID-Verification Status")}</span> <span class="title status-title">${_("ID-Verification Status")}</span>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment