Commit 775ae010 by David Ormsbee

Shorted names of models in verify_student

parent 8f572e53
...@@ -4,8 +4,8 @@ Models for Student Identity Verification ...@@ -4,8 +4,8 @@ Models for Student Identity Verification
This is where we put any models relating to establishing the real-life identity This is where we put any models relating to establishing the real-life identity
of a student over a period of time. Right now, the only models are the abstract of a student over a period of time. Right now, the only models are the abstract
`PhotoVerificationAttempt`, and its one concrete implementation `PhotoVerification`, and its one concrete implementation
`SoftwareSecurePhotoVerificationAttempt`. The hope is to keep as much of the `SoftwareSecurePhotoVerification`. The hope is to keep as much of the
photo verification process as generic as possible. photo verification process as generic as possible.
""" """
from datetime import datetime from datetime import datetime
...@@ -63,14 +63,14 @@ def status_before_must_be(*valid_start_statuses): ...@@ -63,14 +63,14 @@ def status_before_must_be(*valid_start_statuses):
return decorator_func return decorator_func
class PhotoVerificationAttempt(StatusModel): class PhotoVerification(StatusModel):
""" """
Each PhotoVerificationAttempt represents a Student's attempt to establish Each PhotoVerification represents a Student's attempt to establish
their identity by uploading a photo of themselves and a picture ID. An their identity by uploading a photo of themselves and a picture ID. An
attempt actually has a number of fields that need to be filled out at attempt actually has a number of fields that need to be filled out at
different steps of the approval process. While it's useful as a Django Model different steps of the approval process. While it's useful as a Django Model
for the querying facilities, **you should only create and edit a for the querying facilities, **you should only create and edit a
`PhotoVerificationAttempt` object through the methods provided**. Do not `PhotoVerification` object through the methods provided**. Do not
just construct one and start setting fields unless you really know what just construct one and start setting fields unless you really know what
you're doing. you're doing.
...@@ -96,9 +96,9 @@ class PhotoVerificationAttempt(StatusModel): ...@@ -96,9 +96,9 @@ class PhotoVerificationAttempt(StatusModel):
Because this Model inherits from StatusModel, we can also do things like:: Because this Model inherits from StatusModel, we can also do things like::
attempt.status == PhotoVerificationAttempt.STATUS.created attempt.status == PhotoVerification.STATUS.created
attempt.status == "created" attempt.status == "created"
pending_requests = PhotoVerificationAttempt.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
...@@ -154,24 +154,33 @@ class PhotoVerificationAttempt(StatusModel): ...@@ -154,24 +154,33 @@ class PhotoVerificationAttempt(StatusModel):
class Meta: class Meta:
abstract = True abstract = True
ordering = ['-created_at']
##### Methods listed in the order you'd typically call them ##### Methods listed in the order you'd typically call them
@classmethod @classmethod
def user_is_verified(cls, user_id): def user_is_verified(cls, user):
"""Returns whether or not a user has satisfactorily proved their """
Returns whether or not a user has satisfactorily proved their
identity. Depending on the policy, this can expire after some period of identity. Depending on the policy, this can expire after some period of
time, so a user might have to renew periodically.""" time, so a user might have to renew periodically."""
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def active_for_user(cls, user_id): def active_for_user(cls, user):
"""Return all PhotoVerificationAttempts that are still active (i.e. not """
Return all PhotoVerifications that are still active (i.e. not
approved or denied). approved or denied).
Should there only be one active at any given time for a user? Enforced Should there only be one active at any given time for a user? Enforced
at the DB level? at the DB level?
""" """
raise NotImplementedError # 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.
active_attempts = cls.objects.filter(user=user, status='created')
if active_attempts:
return active_attempts[0]
else:
return None
@status_before_must_be("created") @status_before_must_be("created")
def upload_face_image(self, img): def upload_face_image(self, img):
...@@ -315,10 +324,10 @@ class PhotoVerificationAttempt(StatusModel): ...@@ -315,10 +324,10 @@ class PhotoVerificationAttempt(StatusModel):
self.save() self.save()
class SoftwareSecurePhotoVerificationAttempt(PhotoVerificationAttempt): class SoftwareSecurePhotoVerification(PhotoVerification):
""" """
Model to verify identity using a service provided by Software Secure. Much Model to verify identity using a service provided by Software Secure. Much
of the logic is inherited from `PhotoVerificationAttempt`, but this class of the logic is inherited from `PhotoVerification`, but this class
encrypts the photos. encrypts the photos.
Software Secure (http://www.softwaresecure.com/) is a remote proctoring Software Secure (http://www.softwaresecure.com/) is a remote proctoring
...@@ -368,4 +377,3 @@ class SoftwareSecurePhotoVerificationAttempt(PhotoVerificationAttempt): ...@@ -368,4 +377,3 @@ class SoftwareSecurePhotoVerificationAttempt(PhotoVerificationAttempt):
) )
rsa_cipher = PKCS1_OAEP.new(key) rsa_cipher = PKCS1_OAEP.new(key)
rsa_encrypted_aes_key = rsa_cipher.encrypt(aes_key) rsa_encrypted_aes_key = rsa_cipher.encrypt(aes_key)
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from nose.tools import assert_in, assert_is_none, assert_equals, \ from nose.tools import (
assert_raises, assert_not_equals assert_in, assert_is_none, assert_equals, assert_raises, assert_not_equals
)
from django.test import TestCase from django.test import TestCase
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from verify_student.models import PhotoVerificationAttempt, VerificationException from verify_student.models import SoftwareSecurePhotoVerification, VerificationException
class TestPhotoVerificationAttempt(object): class TestPhotoVerification(object):
def test_state_transitions(self): def test_state_transitions(self):
"""Make sure we can't make unexpected status transitions. """Make sure we can't make unexpected status transitions.
...@@ -14,12 +15,12 @@ class TestPhotoVerificationAttempt(object): ...@@ -14,12 +15,12 @@ class TestPhotoVerificationAttempt(object):
The status transitions we expect are:: The status transitions we expect are::
created → ready → submitted → approved created → ready → submitted → approved
↑ ↓ ↑ ↓
→ denied → denied
""" """
user = UserFactory.create() user = UserFactory.create()
attempt = PhotoVerificationAttempt(user=user) attempt = SoftwareSecurePhotoVerification(user=user)
assert_equals(attempt.status, PhotoVerificationAttempt.STATUS.created) assert_equals(attempt.status, SoftwareSecurePhotoVerification.STATUS.created)
assert_equals(attempt.status, "created") assert_equals(attempt.status, "created")
# This should fail because we don't have the necessary fields filled out # This should fail because we don't have the necessary fields filled out
...@@ -38,7 +39,7 @@ class TestPhotoVerificationAttempt(object): ...@@ -38,7 +39,7 @@ class TestPhotoVerificationAttempt(object):
assert_equals(attempt.status, "ready") assert_equals(attempt.status, "ready")
# Once again, state transitions should fail here. We can't approve or # Once again, state transitions should fail here. We can't approve or
# deny anything until it's been placed into the submitted state -- i.e. # deny anything until it's been placed into the submitted state -- i.e.
# the user has clicked on whatever agreements, or given payment, or done # the user has clicked on whatever agreements, or given payment, or done
# whatever the application requires before it agrees to process their # whatever the application requires before it agrees to process their
# attempt. # attempt.
......
...@@ -25,4 +25,19 @@ urlpatterns = patterns( ...@@ -25,4 +25,19 @@ urlpatterns = patterns(
views.final_verification, views.final_verification,
name="verify_student/final_verification" name="verify_student/final_verification"
), ),
# The above are what we did for the design mockups, but what we're really
# looking at now is:
url(
r'^show_verification_page',
views.show_verification_page,
name="verify_student/show_verification_page"
),
url(
r'^start_or_resume_attempt',
views.start_or_resume_attempt,
name="verify_student/start_or_resume_attempt"
)
) )
...@@ -4,14 +4,26 @@ ...@@ -4,14 +4,26 @@
""" """
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
from verify_student.models import SoftwareSecurePhotoVerification
# @login_required # @login_required
def start_or_resume_attempt(request): def start_or_resume_attempt(request, course_id):
""" """
If they've already started a PhotoVerificationAttempt, we move to wherever If they've already started a PhotoVerificationAttempt, we move to wherever
they are in that process. If they've completed one, then we skip straight they are in that process. If they've completed one, then we skip straight
to payment. to payment.
""" """
pass # If the user has already been verified within the given time period,
# redirect straight to the payment -- no need to verify again.
#if SoftwareSecurePhotoVerification.user_is_verified(user):
# pass
attempt = SoftwareSecurePhotoVerification.active_for_user(request.user)
if not attempt:
# Redirect to show requirements
pass
# if attempt.
def show_requirements(request): def show_requirements(request):
"""This might just be a plain template without a view.""" """This might just be a plain template without a view."""
...@@ -29,3 +41,9 @@ def photo_id_upload(request): ...@@ -29,3 +41,9 @@ def photo_id_upload(request):
def final_verification(request): def final_verification(request):
context = { "course_id" : "edX/Certs101/2013_Test" } context = { "course_id" : "edX/Certs101/2013_Test" }
return render_to_response("verify_student/final_verification.html", context) return render_to_response("verify_student/final_verification.html", context)
#
def show_verification_page(request):
pass
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