Commit ee990806 by Brian Wilson

add additional fields to testcenter user and update test center registration.

parent f472ac60
......@@ -141,6 +141,9 @@ class TestCenterUser(models.Model):
The field names and lengths are modeled on the conventions and constraints
of Pearson's data import system, including oddities such as suffix having
a limit of 255 while last_name only gets 50.
Also storing here the confirmation information received from Pearson (if any)
as to the success or failure of the upload. (VCDC file)
"""
# Our own record keeping...
user = models.ForeignKey(User, unique=True, default=None)
......@@ -155,7 +158,7 @@ class TestCenterUser(models.Model):
# we first create the User entry, and is assigned by Pearson later.
candidate_id = models.IntegerField(null=True, db_index=True)
# Unique ID we assign our user for a the Test Center.
# Unique ID we assign our user for the Test Center.
client_candidate_id = models.CharField(max_length=50, db_index=True)
# Name
......@@ -189,6 +192,11 @@ class TestCenterUser(models.Model):
# Company
company_name = models.CharField(max_length=50, blank=True)
# Confirmation
upload_status = models.CharField(max_length=20, blank=True) # 'Error' or 'Accepted'
confirmed_at = models.DateTimeField(null=True, db_index=True)
upload_error_message = models.CharField(max_length=512, blank=True)
@staticmethod
def user_provided_fields():
return [ 'first_name', 'middle_name', 'last_name', 'suffix', 'salutation',
......@@ -212,17 +220,38 @@ class TestCenterRegistration(models.Model):
The field names and lengths are modeled on the conventions and constraints
of Pearson's data import system.
"""
# TODO: Check the spec to find out lengths specified by Pearson
# to find an exam registration, we key off of the user and course_id.
# If multiple exams per course are possible, we would also need to add the
# exam_series_code.
testcenter_user = models.ForeignKey(TestCenterUser, unique=True, default=None)
course_id = models.CharField(max_length=128, db_index=True)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True, db_index=True)
course_id = models.CharField(max_length=128, db_index=True)
# user_updated_at happens only when the user makes a change to their data,
# and is something Pearson needs to know to manage updates. Unlike
# updated_at, this will not get incremented when we do a batch data import.
# The appointment dates, the exam count, and the accommodation codes can be updated,
# but hopefully this won't happen often.
user_updated_at = models.DateTimeField(db_index=True)
# "client_authorization_id" is the client's unique identifier for the authorization.
# This must be present for an update or delete to be sent to Pearson.
client_authorization_id = models.CharField(max_length=20, unique=True, db_index=True)
# information about the test, from the course policy:
exam_series_code = models.CharField(max_length=15, db_index=True)
eligibility_appointment_date_first = models.DateField(db_index=True)
eligibility_appointment_date_last = models.DateField(db_index=True)
# TODO: this should be an enumeration:
accommodation_code = models.CharField(max_length=64, blank=True)
# store the original text of the accommodation request.
accommodation_request = models.CharField(max_length=1024, blank=True)
# TODO: this should be an enumeration:
accommodation_code = models.CharField(max_length=64, blank=True)
# Confirmation
upload_status = models.CharField(max_length=20, blank=True) # 'Error' or 'Accepted'
confirmed_at = models.DateTimeField(db_index=True)
upload_error_message = models.CharField(max_length=512, blank=True)
@property
def candidate_id(self):
......@@ -232,6 +261,15 @@ class TestCenterRegistration(models.Model):
def client_candidate_id(self):
return self.testcenter_user.client_candidate_id
def get_testcenter_registrations_for_user_and_course(user, course_id):
try:
tcu = TestCenterUser.objects.get(user=user)
except User.DoesNotExist:
return []
return TestCenterRegistration.objects.filter(testcenter_user=tcu, course_id=course_id)
def unique_id_for_user(user):
"""
Return a unique id for a user, suitable for inserting into
......
import datetime
import feedparser
import itertools
#import itertools
import json
import logging
import random
import string
import sys
import time
#import time
import urllib
import uuid
......@@ -19,7 +19,8 @@ from django.core.context_processors import csrf
from django.core.mail import send_mail
from django.core.validators import validate_email, validate_slug, ValidationError
from django.db import IntegrityError
from django.http import HttpResponse, HttpResponseForbidden, Http404
from django.http import HttpResponse, HttpResponseForbidden, Http404,\
HttpResponseRedirect
from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string
from bs4 import BeautifulSoup
......@@ -37,13 +38,14 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from datetime import date
#from datetime import date
from collections import namedtuple
from courseware.courses import get_courses_by_university
from courseware.access import has_access
from statsd import statsd
from django.contrib.localflavor.ie.ie_counties import IE_COUNTY_CHOICES
log = logging.getLogger("mitx.student")
Article = namedtuple('Article', 'title url author image deck publication publish_date')
......@@ -640,9 +642,11 @@ def _do_create_or_update_test_center_user(post_vars):
# first determine if we need to create a new TestCenterUser, or if we are making any update
# to an existing TestCenterUser.
username=post_vars['username']
username = post_vars['username']
user = User.objects.get(username=username)
course_id = post_vars['course_id']
course = (course_from_id(course_id)) # assume it will be found....
needs_saving = False
try:
testcenter_user = TestCenterUser.objects.get(user=user)
......@@ -651,9 +655,8 @@ def _do_create_or_update_test_center_user(post_vars):
for fieldname in TestCenterUser.user_provided_fields()])
if needs_updating:
# TODO: what do we set a timestamp to, in order to get now()?
# leave user and client_candidate_id as before
testcenter_user.user_updated_at = datetime.datetime.now()
# Now do the update:
for fieldname in TestCenterUser.user_provided_fields():
testcenter_user.__setattr__(fieldname, post_vars[fieldname])
needs_saving = True
......@@ -661,7 +664,6 @@ def _do_create_or_update_test_center_user(post_vars):
except TestCenterUser.DoesNotExist:
# did not find the TestCenterUser, so create a new one
testcenter_user = TestCenterUser(user=user)
# testcenter_user.user = user
for fieldname in TestCenterUser.user_provided_fields():
testcenter_user.__setattr__(fieldname, post_vars[fieldname])
# testcenter_user.candidate_id remains unset
......@@ -670,31 +672,43 @@ def _do_create_or_update_test_center_user(post_vars):
needs_saving = True
# additional validation occurs at save time, so handle exceptions
# TODO: Rearrange so that if part of the process fails, the whole process fails.
# Right now, we can have e.g. no registration e-mail sent out and a zombie account
if needs_saving:
try:
testcenter_user.save()
except IntegrityError:
js = {'success': False}
# TODO: Figure out the cause of the integrity error
if len(User.objects.filter(username=post_vars['username'])) > 0:
js['value'] = "An account with this username already exists."
js['field'] = 'username'
return HttpResponse(json.dumps(js))
if len(User.objects.filter(email=post_vars['email'])) > 0:
js['value'] = "An account with this e-mail already exists."
js['field'] = 'email'
return HttpResponse(json.dumps(js))
raise
except IntegrityError, ie:
message = ie
context = {'course': course,
'user': user,
'message': message,
'testcenteruser': testcenter_user,
}
return render_to_response('test_center_register.html', context)
# create and save the registration:
registration = TestCenterRegistration(testcenter_user = testcenter_user)
# registration.register(user)
registration.course_id = post_vars['course_id']
registration.accommodation_request = post_vars['accommodations']
exam_info = course.testcenter_info
registration.exam_series_code = exam_info.get('Exam_Series_Code')
registration.eligibility_appointment_date_first = exam_info.get('First_Eligible_Appointment_Date')
registration.eligibility_appointment_date_last = exam_info.get('Last_Eligible_Appointment_Date')
# accommodation_code remains blank for now, along with Pearson confirmation
registration.user_updated_at = datetime.datetime.now()
# "client_authorization_id" is the client's unique identifier for the authorization.
# This must be present for an update or delete to be sent to Pearson.
registration.client_authorization_id = "1"
try:
registration.save()
except IntegrityError, ie:
message = ie
context = {'course': course,
'user': user,
'message': message,
'testcenteruser': testcenter_user,
}
return render_to_response('test_center_register.html', context)
return (user, testcenter_user, registration)
......@@ -759,11 +773,12 @@ def create_test_registration(request, post_override=None):
js['value'] = 'Could not send accommodation e-mail.'
return HttpResponse(json.dumps(js))
# TODO: enable appropriate stat
# statsd.increment("common.student.account_created")
js = {'success': True}
return HttpResponse(json.dumps(js), mimetype="application/json")
# js = {'success': True}
# return HttpResponse(json.dumps(js), mimetype="application/json")
return HttpResponseRedirect('/dashboard')
def get_random_post_override():
"""
......
......@@ -315,7 +315,7 @@ class CourseDescriptor(SequenceDescriptor):
Returns None if no url specified.
"""
return self.metadata.get('end_of_course_survey_url')
@property
def testcenter_info(self):
"""
......@@ -324,10 +324,17 @@ class CourseDescriptor(SequenceDescriptor):
TODO: decide if we expect this entry to be a single test, or if multiple tests are possible
per course.
Returns None if no testcenter info specified.
For now we expect this entry to be a single test.
Returns None if no testcenter info specified, or if no exam is included.
"""
return self.metadata.get('testcenter_info')
info = self.metadata.get('testcenter_info')
if info is None or len(info) == 0:
return None;
else:
return info.values()[0]
@property
def title(self):
return self.display_name
......
......@@ -3,6 +3,7 @@
from courseware.courses import course_image_url, get_course_about_section
from courseware.access import has_access
from certificates.models import CertificateStatuses
from student.models import get_testcenter_registrations_for_user_and_course
%>
<%inherit file="main.html" />
......@@ -222,20 +223,26 @@
<!-- TODO: need to add logic to select which of the following to display. Like certs? -->
<%
testcenter_info = course.testcenter_info
testcenter_register_target = reverse('begin_test_registration', args=[course.id])
%>
% if testcenter_info is not None:
<%
testcenter_register_target = reverse('begin_test_registration', args=[course.id])
%>
<!-- see if there is already a registration object
TODO: need to add logic for when registration can begin. -->
<%
registrations = get_testcenter_registrations_for_user_and_course(user, course.id)
%>
% if len(registrations) == 0:
<div class="message message-status is-shown exam-register">
<!-- <a href="#testcenter-register-modal" rel="leanModal" class="exam-button" data-course-id="${course.id}" data-course-number="${course.number}" id="exam_register_button">Register for Pearson exam</a>
--> <a href="${testcenter_register_target}" class="exam-button" id="exam_register_button">Register for Pearson exam</a>
<p class="message-copy">Registration for the Pearson exam is now open.</p>
</div>
% else:
<div class="message message-status is-shown">
<p class="message-copy">Your registration for the Pearson exam is pending. Within a few days, you should receive a confirmation number, which can be used to schedule your exam.</p>
<p class="message-copy">Your
<a href="${testcenter_register_target}" id="exam_register_link">registration for the Pearson exam</a>
is pending. Within a few days, you should see a confirmation number here, which can be used to schedule your exam.</p>
</div>
<div class="message message-status is-shown exam-schedule">
......@@ -244,6 +251,7 @@
<p class="exam-registration-number">Registration number: <strong>edx00015879548</strong></p>
<p class="message-copy">Write this down! You’ll need it to schedule your exam.</p>
</div>
% endif
% endif
......
......@@ -71,12 +71,9 @@
<!-- TODO: need to add logic to select which of the following to display. Like certs? -->
<%
testcenter_info = course.testcenter_info
exam_info = course.testcenter_info
%>
% if testcenter_info is not None:
<%
exam_info = testcenter_info.get('Final_Exam')
%>
% if exam_info is not None:
<p>Exam Series Code: ${exam_info.get('Exam_Series_Code')}</p>
<p>First Eligible Appointment Date: ${exam_info.get('First_Eligible_Appointment_Date')}</p>
<p>Last Eligible Appointment Date: ${exam_info.get('Last_Eligible_Appointment_Date')}</p>
......
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