Commit 0dd58196 by David Ormsbee

basic flow from enroll mode selection to verify screen

parent 1676fc31
......@@ -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
......
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"),
)
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)
\ No newline at end of file
......@@ -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
class VerifyView(View):
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')
# @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
attempt = SoftwareSecurePhotoVerification.active_for_user(request.user)
if not attempt:
# Redirect to show requirements
pass
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?
}
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