basic flow from enroll mode selection to verify screen
This commit is contained in:
@@ -363,6 +363,7 @@ INSTALLED_APPS = (
|
||||
'course_modes'
|
||||
)
|
||||
|
||||
|
||||
################# EDX MARKETING SITE ##################################
|
||||
|
||||
EDXMKTG_COOKIE_NAME = 'edxloggedin'
|
||||
|
||||
@@ -52,6 +52,10 @@ class CourseMode(models.Model):
|
||||
modes = [cls.DEFAULT_MODE]
|
||||
return modes
|
||||
|
||||
@classmethod
|
||||
def modes_for_course_dict(cls, course_id):
|
||||
return { mode.slug : mode for mode in cls.modes_for_course(course_id) }
|
||||
|
||||
def __unicode__(self):
|
||||
return u"{} : {}, min={}, prices={}".format(
|
||||
self.course_id, self.mode_slug, self.min_price, self.suggested_prices
|
||||
|
||||
9
common/djangoapps/course_modes/urls.py
Normal file
9
common/djangoapps/course_modes/urls.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.conf.urls import include, patterns, url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from course_modes import views
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^choose', views.ChooseModeView.as_view(), name="course_modes_choose"),
|
||||
)
|
||||
@@ -1,9 +1,18 @@
|
||||
from django.http import HttpResponse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import (
|
||||
HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, Http404
|
||||
)
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic.base import View
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.http import urlencode
|
||||
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from courseware.access import has_access
|
||||
from student.models import CourseEnrollment
|
||||
from student.views import course_from_id
|
||||
|
||||
class ChooseModeView(View):
|
||||
|
||||
@@ -11,17 +20,42 @@ class ChooseModeView(View):
|
||||
course_id = request.GET.get("course_id")
|
||||
context = {
|
||||
"course_id" : course_id,
|
||||
"available_modes" : CourseMode.modes_for_course(course_id)
|
||||
"modes" : CourseMode.modes_for_course_dict(course_id)
|
||||
}
|
||||
return render_to_response("course_modes/choose.html", context)
|
||||
|
||||
|
||||
def post(self, request):
|
||||
course_id = request.GET.get("course_id")
|
||||
mode_slug = request.POST.get("mode_slug")
|
||||
user = request.user
|
||||
|
||||
# This is a bit redundant with logic in student.views.change_enrollement,
|
||||
# but I don't really have the time to refactor it more nicely and test.
|
||||
course = course_from_id(course_id)
|
||||
if has_access(user, course, 'enroll'):
|
||||
pass
|
||||
if not has_access(user, course, 'enroll'):
|
||||
return HttpResponseBadRequest(_("Enrollment is closed"))
|
||||
|
||||
requested_mode = self.get_requested_mode(request.POST.get("mode"))
|
||||
|
||||
allowed_modes = CourseMode.modes_for_course_dict(course_id)
|
||||
if requested_mode not in allowed_modes:
|
||||
return HttpResponseBadRequest(_("Enrollment mode not supported"))
|
||||
|
||||
if requested_mode in ("audit", "honor"):
|
||||
CourseEnrollment.enroll(user, course_id)
|
||||
return redirect('dashboard')
|
||||
|
||||
if requested_mode == "verified":
|
||||
return redirect(
|
||||
"{}?{}".format(
|
||||
reverse('verify_student_verify'),
|
||||
urlencode(dict(course_id=course_id))
|
||||
)
|
||||
)
|
||||
|
||||
def get_requested_mode(self, user_choice):
|
||||
choices = {
|
||||
"Select Audit" : "audit",
|
||||
"Select Certificate" : "verified"
|
||||
}
|
||||
return choices.get(user_choice)
|
||||
@@ -24,8 +24,7 @@ from django.db import IntegrityError, transaction
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, Http404
|
||||
from django.shortcuts import redirect
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.utils.http import cookie_date
|
||||
from django.utils.http import base36_to_int
|
||||
from django.utils.http import cookie_date, base36_to_int, urlencode
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
@@ -372,7 +371,12 @@ def change_enrollment(request):
|
||||
# where they can choose which mode they want.
|
||||
available_modes = CourseMode.modes_for_course(course_id)
|
||||
if len(available_modes) > 1:
|
||||
return HttpResponse(reverse("course_modes.views.choose"))
|
||||
return HttpResponse(
|
||||
"{}?{}".format(
|
||||
reverse("course_modes_choose"),
|
||||
urlencode(dict(course_id=course_id))
|
||||
)
|
||||
)
|
||||
|
||||
org, course_num, run = course_id.split("/")
|
||||
statsd.increment("common.student.enrollment",
|
||||
|
||||
@@ -8,7 +8,7 @@ of a student over a period of time. Right now, the only models are the abstract
|
||||
`SoftwareSecurePhotoVerification`. The hope is to keep as much of the
|
||||
photo verification process as generic as possible.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from hashlib import md5
|
||||
import base64
|
||||
import functools
|
||||
@@ -17,6 +17,7 @@ import uuid
|
||||
|
||||
import pytz
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from model_utils.models import StatusModel
|
||||
@@ -69,10 +70,10 @@ class PhotoVerification(StatusModel):
|
||||
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
|
||||
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
|
||||
`PhotoVerification` object through the methods provided**. Do not
|
||||
just construct one and start setting fields unless you really know what
|
||||
you're doing.
|
||||
for the querying facilities, **you should only edit a `PhotoVerification`
|
||||
object through the methods provided**. Initialize them with a user:
|
||||
|
||||
attempt = PhotoVerification(user=user)
|
||||
|
||||
We track this attempt through various states:
|
||||
|
||||
@@ -100,6 +101,9 @@ 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
|
||||
STATUS = Choices('created', 'ready', 'submitted', 'approved', 'denied')
|
||||
@@ -158,12 +162,37 @@ class PhotoVerification(StatusModel):
|
||||
|
||||
##### Methods listed in the order you'd typically call them
|
||||
@classmethod
|
||||
def user_is_verified(cls, user):
|
||||
def user_is_verified(cls, user, earliest_allowed_date=None):
|
||||
"""
|
||||
Returns whether or not a user has satisfactorily proved their
|
||||
identity. Depending on the policy, this can expire after some period of
|
||||
time, so a user might have to renew periodically."""
|
||||
raise NotImplementedError
|
||||
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__lte=earliest_allowed_date
|
||||
).exists()
|
||||
|
||||
@classmethod
|
||||
def user_has_valid_or_pending(cls, user, earliest_allowed_date=None):
|
||||
"""
|
||||
TODO: eliminate duplication with user_is_verified
|
||||
"""
|
||||
valid_statuses = ['ready', '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__lte=earliest_allowed_date
|
||||
).exists()
|
||||
|
||||
@classmethod
|
||||
def active_for_user(cls, user):
|
||||
|
||||
@@ -31,8 +31,6 @@ class StartView(TestCase):
|
||||
user = UserFactory.create(username="rusty", password="test")
|
||||
self.client.login(username="rusty", password="test")
|
||||
|
||||
|
||||
|
||||
def must_be_logged_in(self):
|
||||
self.assertHttpForbidden(self.client.get(self.start_url()))
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ urlpatterns = patterns(
|
||||
),
|
||||
|
||||
url(
|
||||
r'^start_or_resume_attempt',
|
||||
views.start_or_resume_attempt,
|
||||
name="verify_student/start_or_resume_attempt"
|
||||
r'^verify',
|
||||
views.VerifyView.as_view(),
|
||||
name="verify_student_verify"
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
@@ -4,28 +4,36 @@
|
||||
"""
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
from verify_student.models import SoftwareSecurePhotoVerification
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic.base import View
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from verify_student.models import SoftwareSecurePhotoVerification
|
||||
|
||||
# @login_required
|
||||
def start_or_resume_attempt(request, course_id):
|
||||
"""
|
||||
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
|
||||
to payment.
|
||||
"""
|
||||
# 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
|
||||
class VerifyView(View):
|
||||
|
||||
attempt = SoftwareSecurePhotoVerification.active_for_user(request.user)
|
||||
if not attempt:
|
||||
# Redirect to show requirements
|
||||
pass
|
||||
def get(self, request):
|
||||
"""
|
||||
"""
|
||||
# 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_has_valid_or_pending(request.user):
|
||||
progress_state = "payment"
|
||||
else:
|
||||
# If they haven't completed a verification attempt, we have to
|
||||
# restart with a new one. We can't reuse an older one because we
|
||||
# won't be able to show them their encrypted photo_id -- it's easier
|
||||
# bookkeeping-wise just to start over.
|
||||
progress_state = "start"
|
||||
|
||||
return render_to_response('verify_student/face_upload.html')
|
||||
|
||||
|
||||
def post(request):
|
||||
attempt = SoftwareSecurePhotoVerification(user=request.user)
|
||||
|
||||
# if attempt.
|
||||
|
||||
def show_requirements(request):
|
||||
"""This might just be a plain template without a view."""
|
||||
|
||||
@@ -823,3 +823,8 @@ def enable_theme(theme_name):
|
||||
# avoid collisions with default edX static files
|
||||
STATICFILES_DIRS.append((u'themes/%s' % theme_name,
|
||||
theme_root / 'static'))
|
||||
|
||||
################# Student Verification #################
|
||||
VERIFY_STUDENT = {
|
||||
"DAYS_GOOD_FOR" : 365, # How many days is a verficiation good for?
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user