From f3067cd8c88ed0b732ecc1059f6ccacebe80581e Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Fri, 14 Dec 2012 16:17:03 -0500 Subject: [PATCH 01/64] restyling of dashboard with pearson exam notifications --- lms/static/sass/multicourse/_dashboard.scss | 220 ++++++++------------ lms/templates/dashboard.html | 162 +++++++------- 2 files changed, 176 insertions(+), 206 deletions(-) diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 458888b658..249e8a0513 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -267,13 +267,12 @@ } .my-course { - @include border-radius(3px); - @include box-shadow(0 1px 8px 0 rgba(0,0,0, 0.1), inset 0 -1px 0 0 rgba(255,255,255, 0.8), inset 0 1px 0 0 rgba(255,255,255, 0.8)); + clear: both; @include clearfix; - height: 120px; margin-right: flex-gutter(); - margin-bottom: 10px; - overflow: hidden; + margin-bottom: 50px; + padding-bottom: 50px; + border-bottom: 1px solid $light-gray; position: relative; width: flex-grid(12); z-index: 20; @@ -283,13 +282,7 @@ margin-bottom: none; } - .cover { - background: rgb(225,225,225); - background-size: cover; - background-position: center center; - border: 1px solid rgb(120,120,120); - @include border-left-radius(3px); - @include box-shadow(inset 0 0 0 1px rgba(255,255,255, 0.6), 1px 0 0 0 rgba(255,255,255, 0.8)); + .cover { @include box-sizing(border-box); float: left; height: 100%; @@ -299,100 +292,51 @@ position: relative; @include transition(all, 0.15s, linear); width: 200px; + height: 120px; - .shade { - @include background-image(linear-gradient(-90deg, rgba(255,255,255, 0.3) 0%, - rgba(0,0,0, 0.3) 100%)); - bottom: 0px; - content: ""; - display: block; - left: 0px; - position: absolute; - z-index: 50; - top: 0px; - @include transition(all, 0.15s, linear); - right: 0px; - } - - .arrow { - position: absolute; - z-index: 100; + img { width: 100%; - font-size: 70px; - line-height: 110px; - text-align: center; - text-decoration: none; - color: rgba(0, 0, 0, .7); - opacity: 0; - @include transition(all, 0.15s, linear); - } - - &:hover { - .shade { - background: rgba(255,255,255, 0.3); - @include background-image(linear-gradient(-90deg, rgba(255,255,255, 0.3) 0%, - rgba(0,0,0, 0.3) 100%)); - } } } .info { - background: rgb(250,250,250); - @include background-image(linear-gradient(-90deg, rgb(253,253,253), rgb(240,240,240))); - @include box-sizing(border-box); - border: 1px solid rgb(190,190,190); - border-left: none; - @include border-right-radius(3px); - left: 201px; - height: 100%; - max-height: 100%; - padding: 0px 10px; - position: absolute; - right: 0px; - top: 0px; - z-index: 2; + @include clearfix; + padding: 0 10px 0 230px; > hgroup { - @include clearfix; - border-bottom: 1px solid rgb(210,210,210); - @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); - padding: 12px 0px; + padding: 0; width: 100%; .university { - background: rgba(255,255,255, 1); - border: 1px solid rgb(180,180,180); - @include border-radius(3px); - @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.2), 0 1px 0 0 rgba(255,255,255, 0.6)); color: $lighter-base-font-color; - display: block; - font-style: italic; font-family: $sans-serif; font-size: 16px; - font-weight: 800; - @include inline-block; - margin-right: 10px; - margin-bottom: 0; - padding: 5px 10px; - float: left; + font-weight: 400; + margin: 0 0 6px; + text-transform: none; + letter-spacing: 0; } - h3 { + .date-block { + position: absolute; + top: 0; + right: 0; + font-family: $sans-serif; + font-size: 13px; + font-style: italic; + color: $lighter-base-font-color; + } + + h3 a { display: block; - margin-bottom: 0px; - overflow: hidden; - padding-top: 2px; - text-overflow: ellipsis; - white-space: nowrap; + margin-bottom: 10px; + font-family: $sans-serif; + font-size: 34px; + line-height: 42px; + font-weight: 300; - a { - color: $base-font-color; - font-weight: 700; - text-shadow: 0 1px rgba(255,255,255, 0.6); - - &:hover { - text-decoration: underline; - } + &:hover { + text-decoration: none; } } } @@ -430,71 +374,52 @@ } .enter-course { - @include button(shiny, $blue); + @include button(simple, $blue); @include box-sizing(border-box); @include border-radius(3px); display: block; float: left; - font: normal 1rem/1.6rem $sans-serif; - letter-spacing: 1px; - padding: 6px 0px; - text-transform: uppercase; + font: normal 15px/1.6rem $sans-serif; + letter-spacing: 0; + padding: 6px 32px 7px; text-align: center; margin-top: 16px; - width: flex-grid(4); - } - } - > a:hover { - .cover { - .shade { - background: rgba(255,255,255, 0.1); - @include background-image(linear-gradient(-90deg, rgba(255,255,255, 0.3) 0%, - rgba(0,0,0, 0.3) 100%)); + &.archived { + @include button(simple, #eee); + font: normal 15px/1.6rem $sans-serif; + padding: 6px 32px 7px; + + &:hover { + text-decoration: none; + } } - .arrow { - opacity: 1; - } - } - - .info { - background: darken(rgb(250,250,250), 5%); - @include background-image(linear-gradient(-90deg, darken(rgb(253,253,253), 3%), darken(rgb(240,240,240), 5%))); - border-color: darken(rgb(190,190,190), 10%); - - .course-status { - background: darken($yellow, 3%); - border-color: darken(rgb(200,200,200), 3%); - @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); - } - - .course-status-completed { - background: #888; - color: #fff; + &:hover { + text-decoration: none; } } } } .message-status { + @include clearfix; @include border-radius(3px); - @include box-shadow(0 1px 4px 0 rgba(0,0,0, 0.1), inset 0 -1px 0 0 rgba(255,255,255, 0.8), inset 0 1px 0 0 rgba(255,255,255, 0.8)); display: none; - position: relative; - top: -15px; z-index: 10; - margin: 0 0 20px 0; + margin: 20px 0 10px; padding: 15px 20px; - font-family: "Open Sans", Verdana, Geneva, sans-serif; + font-family: $sans-serif; background: #fffcf0; border: 1px solid #ccc; .message-copy { + font-family: $sans-serif; + font-size: 13px; margin: 0; .grade-value { - font-size: 1.4rem; + font-size: 1.2rem; font-weight: bold; } } @@ -502,19 +427,18 @@ .actions { @include clearfix; list-style: none; - margin: 15px 0 0 0; + margin: 0; padding: 0; .action { float: left; - margin:0 15px 10px 0; + margin: 0 15px 0 0; .btn, .cta { display: inline-block; } .btn { - @include button(shiny, $blue); @include box-sizing(border-box); @include border-radius(3px); float: left; @@ -524,7 +448,6 @@ text-align: center; &.disabled { - @include button(shiny, #eee); cursor: default !important; &:hover { @@ -539,7 +462,6 @@ } .cta { - @include button(shiny, #666); float: left; font: normal 0.8rem/1.2rem $sans-serif; letter-spacing: 1px; @@ -549,6 +471,35 @@ } } + .exam-registration-number { + font-family: $sans-serif; + font-size: 18px; + } + + &.exam-register { + .message-copy { + margin-top: 5px; + } + } + + &.exam-schedule { + .exam-button { + margin-top: 5px; + } + } + + .exam-button { + @include button(simple, $pink); + float: right; + padding: 9px 18px 10px; + font-size: 13px; + font-weight: 400; + + &:hover { + text-decoration: none; + } + } + &.is-shown { display: block; } @@ -577,17 +528,16 @@ a.unenroll { float: right; + display: block; font-style: italic; color: #a0a0a0; text-decoration: underline; font-size: .8em; - @include inline-block; - margin-bottom: 40px; + margin-top: 32px; &:hover { color: #333; } } - } } diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index d9b57ac044..0c920afbed 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -198,87 +198,107 @@ course_target = reverse('about_course', args=[course.id]) %> - -
-
-
-
-
-
-

${get_course_about_section(course, 'university')}

-

${course.number} ${course.title}

-
-
-

+ + + + + +

+
+

% if course.has_ended(): - Course Completed - ${course.end_date_text} + Course Completed - ${course.end_date_text} % elif course.has_started(): - Course Started - ${course.start_date_text} + Course Started - ${course.start_date_text} % else: # hasn't started yet - Course Starts - ${course.start_date_text} + Course Starts - ${course.start_date_text} % endif

-
- % if course.id in show_courseware_links_for: -

View Courseware

- % endif -
- - +

${get_course_about_section(course, 'university')}

+

${course.number} ${course.title}

+ - <% - cert_status = cert_statuses.get(course.id) - %> - % if course.has_ended() and cert_status: - <% - if cert_status['status'] == 'generating': - status_css_class = 'course-status-certrendering' - elif cert_status['status'] == 'ready': - status_css_class = 'course-status-certavailable' - elif cert_status['status'] == 'notpassing': - status_css_class = 'course-status-certnotavailable' - else: - status_css_class = 'course-status-processing' - %> -
- - % if cert_status['status'] == 'processing': -

Final course details are being wrapped up at - this time. Your final standing will be available shortly.

- % elif cert_status['status'] in ('generating', 'ready', 'notpassing'): -

Your final grade: - ${"{0:.0f}%".format(float(cert_status['grade'])*100)}. - % if cert_status['status'] == 'notpassing': - Grade required for a certificate: - ${"{0:.0f}%".format(float(course.lowest_passing_grade)*100)}. - % endif -

- % endif - - % if cert_status['show_disabled_download_button'] or cert_status['show_download_url'] or cert_status['show_survey_button']: - - % endif +
+ Register for Pearson exam +

Registration for the Pearson exam is now open.

- % endif +
+

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.

+
- Unregister +
+ Schedule Pearson exam +

Registration number: edx00015879548

+

Write this down! You’ll need it to schedule your exam.

+
+ + + + <% + cert_status = cert_statuses.get(course.id) + %> + % if course.has_ended() and cert_status: + <% + if cert_status['status'] == 'generating': + status_css_class = 'course-status-certrendering' + elif cert_status['status'] == 'ready': + status_css_class = 'course-status-certavailable' + elif cert_status['status'] == 'notpassing': + status_css_class = 'course-status-certnotavailable' + else: + status_css_class = 'course-status-processing' + %> +
+ + % if cert_status['status'] == 'processing': +

Final course details are being wrapped up at + this time. Your final standing will be available shortly.

+ % elif cert_status['status'] in ('generating', 'ready', 'notpassing'): +

Your final grade: + ${"{0:.0f}%".format(float(cert_status['grade'])*100)}. + % if cert_status['status'] == 'notpassing': + Grade required for a certificate: + ${"{0:.0f}%".format(float(course.lowest_passing_grade)*100)}. + % endif +

+ % endif + + % if cert_status['show_disabled_download_button'] or cert_status['show_download_url'] or cert_status['show_survey_button']: + + % endif +
+ + % endif + + % if course.id in show_courseware_links_for: + % if course.has_ended(): + View Archived Course + % else: + View Course + % endif + % endif + Unregister +
+ + + % endfor % else: From b2117c1194b7a19c023a68ee1242876fd10639d6 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Mon, 17 Dec 2012 17:59:44 -0500 Subject: [PATCH 02/64] first dialogs --- lms/templates/dashboard.html | 3 +- lms/templates/test_center_register_modal.html | 105 ++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 lms/templates/test_center_register_modal.html diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 0c920afbed..85106ee1c2 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -219,8 +219,9 @@

${course.number} ${course.title}

+
- Register for Pearson exam + Register for Pearson exam

Registration for the Pearson exam is now open.

diff --git a/lms/templates/test_center_register_modal.html b/lms/templates/test_center_register_modal.html new file mode 100644 index 0000000000..84db6dd09b --- /dev/null +++ b/lms/templates/test_center_register_modal.html @@ -0,0 +1,105 @@ +<%namespace name='static' file='static_content.html'/> +<%! from django.core.urlresolvers import reverse %> +<%! from django_countries.countries import COUNTRIES %> +<%! from student.models import UserProfile %> +<%! from datetime import date %> +<%! import calendar %> + + + + From c7d379beb672b57293e62b11fe7031c2049d09d9 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Tue, 18 Dec 2012 03:20:02 -0500 Subject: [PATCH 03/64] add first pass at wiring test registration dialog --- common/djangoapps/student/views.py | 90 ++++++++++++++++++- lms/templates/dashboard.html | 3 +- lms/templates/test_center_register_modal.html | 58 ++++++------ lms/urls.py | 2 + 4 files changed, 123 insertions(+), 30 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 44877ef597..ad2f810b1f 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -26,7 +26,7 @@ from bs4 import BeautifulSoup from django.core.cache import cache from django_future.csrf import ensure_csrf_cookie, csrf_exempt -from student.models import (Registration, UserProfile, +from student.models import (Registration, UserProfile, TestCenterUser, PendingNameChange, PendingEmailChange, CourseEnrollment, unique_id_for_user) @@ -205,6 +205,14 @@ def dashboard(request): user = request.user enrollments = CourseEnrollment.objects.filter(user=user) + # we want to populate the registration page with the relevant information, + # if it already exists. Create an empty object otherwise. + try: + testcenteruser = TestCenterUser.objects.get(user=user) + except TestCenterUser.DoesNotExist: + testcenteruser = TestCenterUser() + testcenteruser.user = user + # Build our courses list for the user, but ignore any courses that no longer # exist (because the course IDs have changed). Still, we don't delete those # enrollments, because it could have been a data push snafu. @@ -244,6 +252,7 @@ def dashboard(request): 'show_courseware_links_for' : show_courseware_links_for, 'cert_statuses': cert_statuses, 'news': top_news, + 'testcenteruser': testcenteruser, } return render_to_response('dashboard.html', context) @@ -586,6 +595,83 @@ def create_account(request, post_override=None): js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") +@ensure_csrf_cookie +def create_test_registration(request, post_override=None): + ''' + JSON call to create test registration. + Used by form in test_center_register_modal.html, which is included + into dashboard.html + ''' + js = {'success': False} + + post_vars = post_override if post_override else request.POST + + # Confirm we have a properly formed request + for a in ['first_name', 'last_name', 'address_1', 'city', 'country']: + if a not in post_vars: + js['value'] = "Error (401 {field}). E-mail us.".format(field=a) + js['field'] = a + return HttpResponse(json.dumps(js)) + + # Confirm appropriate fields are there. + for a in ['first_name', 'last_name', 'address_1', 'city', 'country']: + if len(post_vars[a]) < 2: + error_str = {'first_name': 'First name must be minimum of two characters long.', + 'last_name': 'Last name must be minimum of two characters long.', + 'address_1': 'Last name must be minimum of two characters long.', + 'city': 'Last name must be minimum of two characters long.', + 'country': 'Last name must be minimum of two characters long.', + } + js['value'] = error_str[a] + js['field'] = a + return HttpResponse(json.dumps(js)) + + # Once the test_center_user information has been validated, create the entries: + ret = _do_create_or_update_test_center_user(post_vars) + if isinstance(ret,HttpResponse): # if there was an error then return that + return ret + + + (user, profile, testcenter_user, testcenter_registration) = ret + + + # only do the following if there is accommodation text to send, + # and a destination to which to send it: + if 'accommodation' in post_vars and settings.MITX_FEATURES.get('ACCOMMODATION_EMAIL'): + d = {'accommodation': post_vars['accommodation'] + } + + # composes accommodation email + subject = render_to_string('emails/accommodation_email_subject.txt', d) + # Email subject *must not* contain newlines + subject = ''.join(subject.splitlines()) + message = render_to_string('emails/accommodation_email.txt', d) + + # skip if destination email address is not specified + try: + dest_addr = settings.MITX_FEATURES['ACCOMMODATION_EMAIL'] + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False) + except: + log.exception(sys.exc_info()) + js['value'] = 'Could not send accommodation e-mail.' + return HttpResponse(json.dumps(js)) + + + if DoExternalAuth: + eamap.user = login_user + eamap.dtsignup = datetime.datetime.now() + eamap.save() + log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'],eamap)) + + if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'): + log.debug('bypassing activation email') + login_user.is_active = True + login_user.save() + + # statsd.increment("common.student.account_created") + + js = {'success': True} + return HttpResponse(json.dumps(js), mimetype="application/json") def get_random_post_override(): """ @@ -641,7 +727,7 @@ def password_reset(request): # By default, Django doesn't allow Users with is_active = False to reset their passwords, # but this bites people who signed up a long time ago, never activated, and forgot their - # password. So for their sake, we'll auto-activate a user for whome password_reset is called. + # password. So for their sake, we'll auto-activate a user for whom password_reset is called. try: user = User.objects.get(email=request.POST['email']) user.is_active = True diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 85106ee1c2..f8e4b2ab57 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -7,6 +7,7 @@ <%inherit file="main.html" /> <%namespace name='static' file='static_content.html'/> +<%include file="test_center_register_modal.html" /> <%block name="title">Dashboard @@ -221,7 +222,7 @@
- Register for Pearson exam + Register for Pearson exam

Registration for the Pearson exam is now open.

diff --git a/lms/templates/test_center_register_modal.html b/lms/templates/test_center_register_modal.html index 84db6dd09b..5638d4750b 100644 --- a/lms/templates/test_center_register_modal.html +++ b/lms/templates/test_center_register_modal.html @@ -17,6 +17,10 @@
+ + + +
- - - - - - + + + + + + - + - +
- - - - - - + + + + + + - + - + - + - +
- + - - - + + + - - - - - + + + + +
diff --git a/lms/urls.py b/lms/urls.py index 0a76907380..97f4613b8f 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -43,6 +43,8 @@ urlpatterns = ('', url(r'^create_account$', 'student.views.create_account'), url(r'^activate/(?P[^/]*)$', 'student.views.activate_account', name="activate"), + url(r'^create_test_registration$', 'student.views.create_test_registration'), + url(r'^password_reset/$', 'student.views.password_reset', name='password_reset'), ## Obsolete Django views for password resets ## TODO: Replace with Mako-ized views From bc40a7f12719b8a2d6d7164bb8087d2a1ec584be Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Tue, 18 Dec 2012 16:16:55 -0500 Subject: [PATCH 04/64] make test-reg dialog non-modal, and pass course_id in URL. Add to course info. --- common/djangoapps/student/views.py | 88 +++++++++- common/lib/xmodule/xmodule/course_module.py | 12 ++ lms/templates/dashboard.html | 36 ++-- lms/templates/test_center_register.html | 179 ++++++++++++++++++++ lms/urls.py | 1 + 5 files changed, 301 insertions(+), 15 deletions(-) create mode 100644 lms/templates/test_center_register.html diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index ad2f810b1f..52fcbb0152 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -304,7 +304,7 @@ def change_enrollment(request): try: course = course_from_id(course_id) except ItemNotFoundError: - log.warning("User {0} tried to enroll in non-existant course {1}" + log.warning("User {0} tried to enroll in non-existent course {1}" .format(user.username, enrollment.course_id)) return {'success': False, 'error': 'The course requested does not exist.'} @@ -595,6 +595,92 @@ def create_account(request, post_override=None): js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") +@login_required +@ensure_csrf_cookie +def begin_test_registration(request, course_id): + user = request.user + + # we want to populate the registration page with the relevant information, + # if it already exists. Create an empty object otherwise. + try: + testcenteruser = TestCenterUser.objects.get(user=user) + except TestCenterUser.DoesNotExist: + testcenteruser = TestCenterUser() + testcenteruser.user = user + + try: + course = (course_from_id(course_id)) + except ItemNotFoundError: + log.error("User {0} enrolled in non-existent course {1}" + .format(user.username, course_id)) + + # placeholder for possible messages... + message = "" + if not user.is_active: + message = render_to_string('registration/activate_account_notice.html', {'email': user.email}) + + context = {'course': course, + 'user': user, + 'message': message, + 'testcenteruser': testcenteruser, + } + + return render_to_response('test_center_register.html', context) + + +def _do_create_or_update_test_center_user(post_vars): + """ + Given cleaned post variables, create the TestCenterUser and UserProfile objects, as well as the + registration for this user. + + Returns a tuple (User, UserProfile, TestCenterUser). + + Note: this function is also used for creating test users. + """ + user = User(username=post_vars['username'], + email=post_vars['email'], + is_active=False) + user.set_password(post_vars['password']) + registration = Registration() + # 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 + try: + user.save() + except IntegrityError: + js = {'success': False} + # 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 + + registration.register(user) + + profile = UserProfile(user=user) + profile.name = post_vars['name'] + profile.level_of_education = post_vars.get('level_of_education') + profile.gender = post_vars.get('gender') + profile.mailing_address = post_vars.get('mailing_address') + profile.goals = post_vars.get('goals') + + try: + profile.year_of_birth = int(post_vars['year_of_birth']) + except (ValueError, KeyError): + profile.year_of_birth = None # If they give us garbage, just ignore it instead + # of asking them to put an integer. + try: + profile.save() + except Exception: + log.exception("UserProfile creation failed for user {0}.".format(user.id)) + return (user, profile, registration) + @ensure_csrf_cookie def create_test_registration(request, post_override=None): ''' diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 3506c72bd7..a4cf87b333 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -316,6 +316,18 @@ class CourseDescriptor(SequenceDescriptor): """ return self.metadata.get('end_of_course_survey_url') + @property + def testcenter_info(self): + """ + Pull from policy. + + 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. + """ + return self.metadata.get('testcenter_info') + @property def title(self): return self.display_name diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index f8e4b2ab57..f771f3e098 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -7,7 +7,6 @@ <%inherit file="main.html" /> <%namespace name='static' file='static_content.html'/> -<%include file="test_center_register_modal.html" /> <%block name="title">Dashboard @@ -221,22 +220,31 @@ -
- Register for Pearson exam -

Registration for the Pearson exam is now open.

-
+ <% + testcenter_info = course.testcenter_info + %> + % if testcenter_info is not None: + + <% + testcenter_register_target = reverse('begin_test_registration', args=[course.id]) + %> +
+ Register for Pearson exam +

Registration for the Pearson exam is now open.

+
-
-

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.

-
+
+

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.

+
-
- Schedule Pearson exam -

Registration number: edx00015879548

-

Write this down! You’ll need it to schedule your exam.

-
+
+ Schedule Pearson exam +

Registration number: edx00015879548

+

Write this down! You’ll need it to schedule your exam.

+
- + % endif <% cert_status = cert_statuses.get(course.id) diff --git a/lms/templates/test_center_register.html b/lms/templates/test_center_register.html new file mode 100644 index 0000000000..c71c40610d --- /dev/null +++ b/lms/templates/test_center_register.html @@ -0,0 +1,179 @@ +<%! + from django.core.urlresolvers import reverse + from courseware.courses import course_image_url, get_course_about_section + from courseware.access import has_access + from certificates.models import CertificateStatuses +%> +<%inherit file="main.html" /> + +<%namespace name='static' file='static_content.html'/> + +<%block name="title">Sign Up for Pearson VUE Test Center Proctoring + +<%block name="js_extra"> + + + +
+ + + %if message: +
+ ${message} +
+ %endif + +
+
+
+

+
+
+ + + +
+
+

+ % if course.has_ended(): + Course Completed - ${course.end_date_text} + % elif course.has_started(): + Course Started - ${course.start_date_text} + % else: # hasn't started yet + Course Starts - ${course.start_date_text} + % endif +

+

${get_course_about_section(course, 'university')}

+

${course.number} ${course.title}

+
+ + + <% + testcenter_info = course.testcenter_info + %> + % if testcenter_info is not None: + <% + exam_info = testcenter_info.get('Final_Exam') + %> +

Exam Series Code: ${exam_info.get('Exam_Series_Code')}

+

First Eligible Appointment Date: ${exam_info.get('First_Eligible_Appointment_Date')}

+

Last Eligible Appointment Date: ${exam_info.get('Last_Eligible_Appointment_Date')}

+ % endif + +
+ + +
+
+ + +
+ + + + + + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ +
+ + +
+ + +
+ + +
+ + + + + +
+ + + + +
+ +
+ + + + + + +
+ + + + +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+ +
+
+ + + + \ No newline at end of file diff --git a/lms/urls.py b/lms/urls.py index 97f4613b8f..e81e25e86a 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -43,6 +43,7 @@ urlpatterns = ('', url(r'^create_account$', 'student.views.create_account'), url(r'^activate/(?P[^/]*)$', 'student.views.activate_account', name="activate"), + url(r'^begin_test_registration/(?P[^/]+/[^/]+/[^/]+)$', 'student.views.begin_test_registration', name="begin_test_registration"), url(r'^create_test_registration$', 'student.views.create_test_registration'), url(r'^password_reset/$', 'student.views.password_reset', name='password_reset'), From f912ffe8a2968413b5ffdb61414de9f7cb9d0b70 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Tue, 18 Dec 2012 18:31:41 -0500 Subject: [PATCH 05/64] add persistence of testcenter_user registration --- common/djangoapps/student/models.py | 15 ++++ common/djangoapps/student/views.py | 113 +++++++++++++++------------- 2 files changed, 74 insertions(+), 54 deletions(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 2f5bc3ac04..89e2548bbb 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -189,10 +189,25 @@ class TestCenterUser(models.Model): # Company company_name = models.CharField(max_length=50, blank=True) + @staticmethod + def user_provided_fields(): + return [ 'first_name', 'middle_name', 'last_name', 'suffix', 'salutation', + 'address_1', 'address_2', 'address_3', 'city', 'state', 'postal_code', 'country', + 'phone', 'extension', 'phone_country_code', 'fax', 'fax_country_code', 'company_name'] + @property def email(self): return self.user.email +class TestCenterRegistration(models.Model): + testcenter_user = models.ForeignKey(TestCenterUser, unique=True, default=None) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) + updated_at = models.DateTimeField(auto_now=True, db_index=True) + accommodation_request = models.CharField(max_length=1024) + # TODO: this should be an enumeration: + accommodation_code = models.CharField(max_length=64) + + def unique_id_for_user(user): """ Return a unique id for a user, suitable for inserting into diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 52fcbb0152..d972cb326c 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -26,7 +26,7 @@ from bs4 import BeautifulSoup from django.core.cache import cache from django_future.csrf import ensure_csrf_cookie, csrf_exempt -from student.models import (Registration, UserProfile, TestCenterUser, +from student.models import (Registration, UserProfile, TestCenterUser, TestCenterRegistration, PendingNameChange, PendingEmailChange, CourseEnrollment, unique_id_for_user) @@ -614,7 +614,7 @@ def begin_test_registration(request, course_id): log.error("User {0} enrolled in non-existent course {1}" .format(user.username, course_id)) - # placeholder for possible messages... + # TODO: placeholder for possible messages... message = "" if not user.is_active: message = render_to_string('registration/activate_account_notice.html', {'email': user.email}) @@ -634,52 +634,68 @@ def _do_create_or_update_test_center_user(post_vars): registration for this user. Returns a tuple (User, UserProfile, TestCenterUser). - - Note: this function is also used for creating test users. + """ - user = User(username=post_vars['username'], - email=post_vars['email'], - is_active=False) - user.set_password(post_vars['password']) - registration = Registration() + + # 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'] + user = User.objects.get(username=username) + + needs_saving = False + try: + testcenter_user = TestCenterUser.objects.get(user=user) + # found a TestCenterUser, so check to see if it has changed + needs_updating = any([testcenter_user.__getattribute__(fieldname) != post_vars[fieldname] + for fieldname in TestCenterUser.user_provided_fields()]) + + if needs_updating: + # TODO: what do we set a timestamp to, in order to get now()? + 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 + + 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 + testcenter_user.client_candidate_id = 'edx' + '123456' # some unique value + testcenter_user.user_updated_at = datetime.datetime.now() + 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 - try: - user.save() - except IntegrityError: - js = {'success': False} - # 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 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)) - 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 + + + registration = TestCenterRegistration(testcenter_user = testcenter_user) + # registration.register(user) - raise + registration.accommodation_request = post_vars['accommodations'] - registration.register(user) - - profile = UserProfile(user=user) - profile.name = post_vars['name'] - profile.level_of_education = post_vars.get('level_of_education') - profile.gender = post_vars.get('gender') - profile.mailing_address = post_vars.get('mailing_address') - profile.goals = post_vars.get('goals') - - try: - profile.year_of_birth = int(post_vars['year_of_birth']) - except (ValueError, KeyError): - profile.year_of_birth = None # If they give us garbage, just ignore it instead - # of asking them to put an integer. - try: - profile.save() - except Exception: - log.exception("UserProfile creation failed for user {0}.".format(user.id)) - return (user, profile, registration) + return (user, testcenter_user, registration) @ensure_csrf_cookie def create_test_registration(request, post_override=None): @@ -718,7 +734,7 @@ def create_test_registration(request, post_override=None): return ret - (user, profile, testcenter_user, testcenter_registration) = ret + (user, testcenter_user, testcenter_registration) = ret # only do the following if there is accommodation text to send, @@ -743,17 +759,6 @@ def create_test_registration(request, post_override=None): return HttpResponse(json.dumps(js)) - if DoExternalAuth: - eamap.user = login_user - eamap.dtsignup = datetime.datetime.now() - eamap.save() - log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'],eamap)) - - if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'): - log.debug('bypassing activation email') - login_user.is_active = True - login_user.save() - # statsd.increment("common.student.account_created") js = {'success': True} From f472ac60f511081ef75fb40af1dc610fb173ca23 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Wed, 19 Dec 2012 00:21:49 -0500 Subject: [PATCH 06/64] minor tweaks to test center registration --- common/djangoapps/student/models.py | 30 ++++++++++++++++++++++++++--- common/djangoapps/student/views.py | 15 ++++++++------- lms/templates/dashboard.html | 3 ++- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 89e2548bbb..b7b2b4e21d 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -200,13 +200,37 @@ class TestCenterUser(models.Model): return self.user.email class TestCenterRegistration(models.Model): + """ + This is our representation of a user's registration for in-person testing, + and specifically for Pearson at this point. A few things to note: + + * Pearson only supports Latin-1, so we have to make sure that the data we + capture here will work with that encoding. This is less of an issue + than for the TestCenterUser. + * Registrations are only created here when a user registers to take an exam in person. + + 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 + testcenter_user = models.ForeignKey(TestCenterUser, unique=True, default=None) created_at = models.DateTimeField(auto_now_add=True, db_index=True) updated_at = models.DateTimeField(auto_now=True, db_index=True) - accommodation_request = models.CharField(max_length=1024) - # TODO: this should be an enumeration: - accommodation_code = models.CharField(max_length=64) + course_id = models.CharField(max_length=128, db_index=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) + + @property + def candidate_id(self): + return self.testcenter_user.candidate_id + + @property + def client_candidate_id(self): + return self.testcenter_user.client_candidate_id def unique_id_for_user(user): """ diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index d972cb326c..cfb6b3a39b 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -207,11 +207,11 @@ def dashboard(request): # we want to populate the registration page with the relevant information, # if it already exists. Create an empty object otherwise. - try: - testcenteruser = TestCenterUser.objects.get(user=user) - except TestCenterUser.DoesNotExist: - testcenteruser = TestCenterUser() - testcenteruser.user = user +# try: +# testcenteruser = TestCenterUser.objects.get(user=user) +# except TestCenterUser.DoesNotExist: +# testcenteruser = TestCenterUser() +# testcenteruser.user = user # Build our courses list for the user, but ignore any courses that no longer # exist (because the course IDs have changed). Still, we don't delete those @@ -252,7 +252,8 @@ def dashboard(request): 'show_courseware_links_for' : show_courseware_links_for, 'cert_statuses': cert_statuses, 'news': top_news, - 'testcenteruser': testcenteruser, +# No longer needed here...move to begin_registration +# 'testcenteruser': testcenteruser, } return render_to_response('dashboard.html', context) @@ -692,7 +693,7 @@ def _do_create_or_update_test_center_user(post_vars): registration = TestCenterRegistration(testcenter_user = testcenter_user) # registration.register(user) - + registration.course_id = post_vars['course_id'] registration.accommodation_request = post_vars['accommodations'] return (user, testcenter_user, registration) diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index f771f3e098..c7a8df077a 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -239,7 +239,8 @@
- Schedule Pearson exam + + Schedule Pearson exam

Registration number: edx00015879548

Write this down! You’ll need it to schedule your exam.

From ee99080687a8f9b7312bab14e02d6a4ea4de7a7a Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Wed, 19 Dec 2012 16:53:13 -0500 Subject: [PATCH 07/64] add additional fields to testcenter user and update test center registration. --- ...022_add_more_fields_to_test_center_user.py | 198 ++++++++++++++++++ common/djangoapps/student/models.py | 50 ++++- common/djangoapps/student/views.py | 75 ++++--- common/lib/xmodule/xmodule/course_module.py | 15 +- lms/templates/dashboard.html | 20 +- lms/templates/test_center_register.html | 7 +- 6 files changed, 314 insertions(+), 51 deletions(-) create mode 100644 common/djangoapps/student/migrations/0022_add_more_fields_to_test_center_user.py diff --git a/common/djangoapps/student/migrations/0022_add_more_fields_to_test_center_user.py b/common/djangoapps/student/migrations/0022_add_more_fields_to_test_center_user.py new file mode 100644 index 0000000000..4a6481f80c --- /dev/null +++ b/common/djangoapps/student/migrations/0022_add_more_fields_to_test_center_user.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'TestCenterUser.upload_status' + db.add_column('student_testcenteruser', 'upload_status', + self.gf('django.db.models.fields.CharField')(default='', max_length=20, blank=True), + keep_default=False) + + # Adding field 'TestCenterUser.confirmed_at' + db.add_column('student_testcenteruser', 'confirmed_at', + self.gf('django.db.models.fields.DateTimeField')(null=True, db_index=True), + keep_default=False) + + # Adding field 'TestCenterUser.upload_error_message' + db.add_column('student_testcenteruser', 'upload_error_message', + self.gf('django.db.models.fields.CharField')(default='', max_length=512, blank=True), + keep_default=False) + + # Adding model 'TestCenterRegistration' + db.create_table('student_testcenterregistration', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('testcenter_user', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['student.TestCenterUser'], unique=True)), + ('course_id', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)), + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, db_index=True, blank=True)), + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)), + ('user_updated_at', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), + ('client_authorization_id', self.gf('django.db.models.fields.CharField')(unique=True, max_length=20, db_index=True)), + ('exam_series_code', self.gf('django.db.models.fields.CharField')(max_length=15, db_index=True)), + ('eligibility_appointment_date_first', self.gf('django.db.models.fields.DateField')(db_index=True)), + ('eligibility_appointment_date_last', self.gf('django.db.models.fields.DateField')(db_index=True)), + ('accommodation_code', self.gf('django.db.models.fields.CharField')(max_length=64, blank=True)), + ('accommodation_request', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)), + ('upload_status', self.gf('django.db.models.fields.CharField')(max_length=20, blank=True)), + ('confirmed_at', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), + ('upload_error_message', self.gf('django.db.models.fields.CharField')(max_length=512, blank=True)), + )) + db.send_create_signal('student', ['TestCenterRegistration']) + + + def backwards(self, orm): + # Deleting model 'TestCenterRegistration' + db.delete_table('student_testcenterregistration') + + # Deleting field 'TestCenterUser.upload_status' + db.delete_column('student_testcenteruser', 'upload_status') + + # Deleting field 'TestCenterUser.confirmed_at' + db.delete_column('student_testcenteruser', 'confirmed_at') + + # Deleting field 'TestCenterUser.upload_error_message' + db.delete_column('student_testcenteruser', 'upload_error_message') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'student.courseenrollment': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.testcenterregistration': { + 'Meta': {'object_name': 'TestCenterRegistration'}, + 'accommodation_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'accommodation_request': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'client_authorization_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), + 'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'eligibility_appointment_date_first': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), + 'eligibility_appointment_date_last': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), + 'exam_series_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'testcenter_user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['student.TestCenterUser']", 'unique': 'True'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'upload_error_message': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}), + 'upload_status': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}) + }, + 'student.testcenteruser': { + 'Meta': {'object_name': 'TestCenterUser'}, + 'address_1': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'address_2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'address_3': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'candidate_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'client_candidate_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'extension': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8', 'blank': 'True'}), + 'fax': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}), + 'fax_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'middle_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '35'}), + 'phone_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}), + 'postal_code': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'blank': 'True'}), + 'salutation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'state': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}), + 'suffix': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'upload_error_message': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}), + 'upload_status': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'unique': 'True'}), + 'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index b7b2b4e21d..6b73c2319f 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -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 diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index cfb6b3a39b..4d5728792e 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -1,12 +1,12 @@ 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)) + except IntegrityError, ie: + message = ie + context = {'course': course, + 'user': user, + 'message': message, + 'testcenteruser': testcenter_user, + } + return render_to_response('test_center_register.html', context) - 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 - - + # 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(): """ diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index a4cf87b333..715b263b59 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -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 diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index c7a8df077a..13e393180a 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -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 @@ <% 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]) - %> + + + <% + registrations = get_testcenter_registrations_for_user_and_course(user, course.id) + %> + % if len(registrations) == 0:
Register for Pearson exam

Registration for the Pearson exam is now open.

- + % else:
-

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.

+

Your + registration for the Pearson exam + is pending. Within a few days, you should see a confirmation number here, which can be used to schedule your exam.

@@ -244,6 +251,7 @@

Registration number: edx00015879548

Write this down! You’ll need it to schedule your exam.

+ % endif % endif diff --git a/lms/templates/test_center_register.html b/lms/templates/test_center_register.html index c71c40610d..51b271dfc3 100644 --- a/lms/templates/test_center_register.html +++ b/lms/templates/test_center_register.html @@ -71,12 +71,9 @@ <% - 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:

Exam Series Code: ${exam_info.get('Exam_Series_Code')}

First Eligible Appointment Date: ${exam_info.get('First_Eligible_Appointment_Date')}

Last Eligible Appointment Date: ${exam_info.get('Last_Eligible_Appointment_Date')}

From d5bd2313c1baaa395bda60868f467577387a4d8b Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Thu, 20 Dec 2012 04:16:00 -0500 Subject: [PATCH 08/64] fix migrations, removing one field and making another nullable. Update registration page to allow for editing only demographics. --- ...022_add_more_fields_to_test_center_user.py | 15 ++-- common/djangoapps/student/models.py | 13 ++-- common/djangoapps/student/views.py | 75 ++++++++++-------- lms/templates/dashboard.html | 3 +- lms/templates/test_center_register.html | 78 ++++++++++++++++--- 5 files changed, 128 insertions(+), 56 deletions(-) diff --git a/common/djangoapps/student/migrations/0022_add_more_fields_to_test_center_user.py b/common/djangoapps/student/migrations/0022_add_more_fields_to_test_center_user.py index 4a6481f80c..1bffec2213 100644 --- a/common/djangoapps/student/migrations/0022_add_more_fields_to_test_center_user.py +++ b/common/djangoapps/student/migrations/0022_add_more_fields_to_test_center_user.py @@ -13,8 +13,8 @@ class Migration(SchemaMigration): self.gf('django.db.models.fields.CharField')(default='', max_length=20, blank=True), keep_default=False) - # Adding field 'TestCenterUser.confirmed_at' - db.add_column('student_testcenteruser', 'confirmed_at', + # Adding field 'TestCenterUser.uploaded_at' + db.add_column('student_testcenteruser', 'uploaded_at', self.gf('django.db.models.fields.DateTimeField')(null=True, db_index=True), keep_default=False) @@ -31,14 +31,13 @@ class Migration(SchemaMigration): ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, db_index=True, blank=True)), ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)), ('user_updated_at', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), - ('client_authorization_id', self.gf('django.db.models.fields.CharField')(unique=True, max_length=20, db_index=True)), ('exam_series_code', self.gf('django.db.models.fields.CharField')(max_length=15, db_index=True)), ('eligibility_appointment_date_first', self.gf('django.db.models.fields.DateField')(db_index=True)), ('eligibility_appointment_date_last', self.gf('django.db.models.fields.DateField')(db_index=True)), ('accommodation_code', self.gf('django.db.models.fields.CharField')(max_length=64, blank=True)), ('accommodation_request', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)), ('upload_status', self.gf('django.db.models.fields.CharField')(max_length=20, blank=True)), - ('confirmed_at', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), + ('uploaded_at', self.gf('django.db.models.fields.DateTimeField')(null=True, db_index=True)), ('upload_error_message', self.gf('django.db.models.fields.CharField')(max_length=512, blank=True)), )) db.send_create_signal('student', ['TestCenterRegistration']) @@ -51,8 +50,8 @@ class Migration(SchemaMigration): # Deleting field 'TestCenterUser.upload_status' db.delete_column('student_testcenteruser', 'upload_status') - # Deleting field 'TestCenterUser.confirmed_at' - db.delete_column('student_testcenteruser', 'confirmed_at') + # Deleting field 'TestCenterUser.uploaded_at' + db.delete_column('student_testcenteruser', 'uploaded_at') # Deleting field 'TestCenterUser.upload_error_message' db.delete_column('student_testcenteruser', 'upload_error_message') @@ -127,7 +126,7 @@ class Migration(SchemaMigration): 'accommodation_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), 'accommodation_request': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), 'client_authorization_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), - 'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}), 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), 'eligibility_appointment_date_first': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), @@ -149,7 +148,7 @@ class Migration(SchemaMigration): 'city': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), 'client_candidate_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), - 'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), 'country': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}), 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), 'extension': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8', 'blank': 'True'}), diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 6b73c2319f..716c472330 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -194,7 +194,7 @@ class TestCenterUser(models.Model): # Confirmation upload_status = models.CharField(max_length=20, blank=True) # 'Error' or 'Accepted' - confirmed_at = models.DateTimeField(null=True, db_index=True) + uploaded_at = models.DateTimeField(null=True, db_index=True) upload_error_message = models.CharField(max_length=512, blank=True) @staticmethod @@ -236,7 +236,7 @@ class TestCenterRegistration(models.Model): 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) + # 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) @@ -250,7 +250,7 @@ class TestCenterRegistration(models.Model): # Confirmation upload_status = models.CharField(max_length=20, blank=True) # 'Error' or 'Accepted' - confirmed_at = models.DateTimeField(db_index=True) + uploaded_at = models.DateTimeField(null=True, db_index=True) upload_error_message = models.CharField(max_length=512, blank=True) @property @@ -261,12 +261,15 @@ class TestCenterRegistration(models.Model): def client_candidate_id(self): return self.testcenter_user.client_candidate_id - + @property + def client_authorization_id(self): + # TODO: make this explicitly into a string object: + return self.id def get_testcenter_registrations_for_user_and_course(user, course_id): try: tcu = TestCenterUser.objects.get(user=user) - except User.DoesNotExist: + except TestCenterUser.DoesNotExist: return [] return TestCenterRegistration.objects.filter(testcenter_user=tcu, course_id=course_id) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 4d5728792e..62943478a0 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -10,6 +10,7 @@ import sys import urllib import uuid + from django.conf import settings from django.contrib.auth import logout, authenticate, login from django.contrib.auth.forms import PasswordResetForm @@ -17,6 +18,7 @@ from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.core.context_processors import csrf from django.core.mail import send_mail +from django.core.urlresolvers import reverse from django.core.validators import validate_email, validate_slug, ValidationError from django.db import IntegrityError from django.http import HttpResponse, HttpResponseForbidden, Http404,\ @@ -29,7 +31,8 @@ from django.core.cache import cache from django_future.csrf import ensure_csrf_cookie, csrf_exempt from student.models import (Registration, UserProfile, TestCenterUser, TestCenterRegistration, PendingNameChange, PendingEmailChange, - CourseEnrollment, unique_id_for_user) + CourseEnrollment, unique_id_for_user, + get_testcenter_registrations_for_user_and_course) from certificates.models import CertificateStatuses, certificate_status_for_student @@ -617,11 +620,7 @@ def begin_test_registration(request, course_id): log.error("User {0} enrolled in non-existent course {1}" .format(user.username, course_id)) - # TODO: placeholder for possible messages... message = "" - if not user.is_active: - message = render_to_string('registration/activate_account_notice.html', {'email': user.email}) - context = {'course': course, 'user': user, 'message': message, @@ -676,7 +675,7 @@ def _do_create_or_update_test_center_user(post_vars): try: testcenter_user.save() except IntegrityError, ie: - message = ie + message = "%s" % ie context = {'course': course, 'user': user, 'message': message, @@ -685,31 +684,45 @@ def _do_create_or_update_test_center_user(post_vars): return render_to_response('test_center_register.html', context) # create and save the registration: - registration = TestCenterRegistration(testcenter_user = testcenter_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) + needs_saving = False + registrations = get_testcenter_registrations_for_user_and_course(user, course.id) + # In future, this should check the exam series code of the registrations, if there + # were multiple. + if len(registrations) > 0: + registration = registrations[0] + # check to see if registration changed. Should check appointment dates too... + # And later should check changes in accommodation_code. + # But at the moment, we don't expect anything to cause this to change + # right now. + + else: + registration = TestCenterRegistration(testcenter_user = testcenter_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() + needs_saving = 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. + # Can we just use the id field of the registration? Lets... + + if needs_saving: + try: + registration.save() + except IntegrityError, ie: + message = "%s" % 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) @ensure_csrf_cookie @@ -778,7 +791,7 @@ def create_test_registration(request, post_override=None): # js = {'success': True} # return HttpResponse(json.dumps(js), mimetype="application/json") - return HttpResponseRedirect('/dashboard') + return HttpResponseRedirect(reverse('dashboard')) def get_random_post_override(): """ diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 13e393180a..1f1553d90c 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -234,8 +234,7 @@ %> % if len(registrations) == 0:
- Register for Pearson exam + Register for Pearson exam

Registration for the Pearson exam is now open.

% else: diff --git a/lms/templates/test_center_register.html b/lms/templates/test_center_register.html index 51b271dfc3..8c99b4f8ef 100644 --- a/lms/templates/test_center_register.html +++ b/lms/templates/test_center_register.html @@ -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" /> @@ -34,14 +35,7 @@ -
- - - %if message: -
- ${message} -
- %endif +
@@ -81,7 +75,63 @@
+ + <% + registrations = get_testcenter_registrations_for_user_and_course(user, course.id) + %> + + % if len(registrations) > 0: + <% + registration = registrations[0] + %> +
Already Registered
+

Here is the current state of your registration, for debugging purposes:

+ +
  • id: ${registration.id}
  • +
  • testcenter_user_id: ${registration.testcenter_user_id}
  • +
  • course_id: ${registration.course_id}
  • +
  • accommodation codes: ${registration.accommodation_code}
  • +
  • accommodation request: ${registration.accommodation_request}
  • +
  • created_at: ${registration.created_at}
  • +
  • updated_at: ${registration.updated_at}
  • +
  • user_updated_at: ${registration.user_updated_at}
  • +
  • upload_status: ${registration.upload_status}
  • +
  • upload_error_message: ${registration.upload_error_message}
  • +
    + + + + <% + regstatus = "registration pending acknowledgement by Pearson" + + if registration.upload_status == 'Accepted': + regstatus = "registration approved by Pearson" + elif registration.upload_status == 'Error': + regstatus = "registration rejected by Pearson: %s" % registration.upload_error_message + elif len(registration.accommodation_request) > 0 and registration.accommodation_code == '': + regstatus = "pending approval of accommodation request" + %> +

    Current status: ${regstatus}

    + +

    The demographic information provided below was used to register + for the exam listed above. Changes to this information + may be submitted below.

    +
    + % else: +

    The demographic information must be provided below in order to register + for the exam listed above.

    + % endif + + + % if message: +
    +

    ${message}

    +
    + % endif +
    @@ -91,7 +141,6 @@ -
    @@ -150,15 +199,24 @@
    - +

    The following is included here just so it can be input within the form. But it + is not part of the demographics, and it is not something that can be changed once input.

    +
    + % if len(registrations) > 0: +
    + +
    + % else:
    + % endif +
    From 5119257f3e2ef7357b541ad18fa736f19b5e47f1 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 20 Dec 2012 12:24:31 -0500 Subject: [PATCH 09/64] test center - fixed dashboard link font issue and started cleaning up markup in reg form - wip --- lms/static/sass/application.scss | 1 + lms/static/sass/multicourse/_dashboard.scss | 4 + lms/templates/test_center_register.html | 309 +++++++++----------- 3 files changed, 150 insertions(+), 164 deletions(-) diff --git a/lms/static/sass/application.scss b/lms/static/sass/application.scss index 944d3b2884..4e532cf30e 100644 --- a/lms/static/sass/application.scss +++ b/lms/static/sass/application.scss @@ -19,6 +19,7 @@ @import 'multicourse/home'; @import 'multicourse/dashboard'; +@import 'multicourse/testcenter-register'; @import 'multicourse/courses'; @import 'multicourse/course_about'; @import 'multicourse/jobs'; diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 249e8a0513..8383b01a54 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -418,6 +418,10 @@ font-size: 13px; margin: 0; + a { + font-family: $sans-serif; + } + .grade-value { font-size: 1.2rem; font-weight: bold; diff --git a/lms/templates/test_center_register.html b/lms/templates/test_center_register.html index 8c99b4f8ef..f1b071d121 100644 --- a/lms/templates/test_center_register.html +++ b/lms/templates/test_center_register.html @@ -9,7 +9,7 @@ <%namespace name='static' file='static_content.html'/> -<%block name="title">Sign Up for Pearson VUE Test Center Proctoring +<%block name="title">Pearson VUE Test Center Proctoring - Sign Up <%block name="js_extra"> -
    +
    -
    -
    -
    -

    -
    -
    + - +
    +
    +

    + % if course.has_ended(): + Course Completed - ${course.end_date_text} + % elif course.has_started(): + Course Started - ${course.start_date_text} + % else: # hasn't started yet + Course Starts - ${course.start_date_text} + % endif +

    +

    ${get_course_about_section(course, 'university')}

    +

    ${course.number} ${course.title}

    +
    -
    -
    -

    - % if course.has_ended(): - Course Completed - ${course.end_date_text} - % elif course.has_started(): - Course Started - ${course.start_date_text} - % else: # hasn't started yet - Course Starts - ${course.start_date_text} - % endif -

    -

    ${get_course_about_section(course, 'university')}

    -

    ${course.number} ${course.title}

    -
    - - - <% - exam_info = course.testcenter_info - %> - % if exam_info is not None: -

    Exam Series Code: ${exam_info.get('Exam_Series_Code')}

    -

    First Eligible Appointment Date: ${exam_info.get('First_Eligible_Appointment_Date')}

    -

    Last Eligible Appointment Date: ${exam_info.get('Last_Eligible_Appointment_Date')}

    - % endif - -
    + + <% + exam_info = course.testcenter_info + %> + % if exam_info is not None: +

    Exam Series Code: ${exam_info.get('Exam_Series_Code')}

    +

    First Eligible Appointment Date: ${exam_info.get('First_Eligible_Appointment_Date')}

    +

    Last Eligible Appointment Date: ${exam_info.get('Last_Eligible_Appointment_Date')}

    + % endif +
    @@ -86,40 +78,41 @@ registration = registrations[0] %> -
    Already Registered
    -

    Here is the current state of your registration, for debugging purposes:

    - -
  • id: ${registration.id}
  • -
  • testcenter_user_id: ${registration.testcenter_user_id}
  • -
  • course_id: ${registration.course_id}
  • -
  • accommodation codes: ${registration.accommodation_code}
  • -
  • accommodation request: ${registration.accommodation_request}
  • -
  • created_at: ${registration.created_at}
  • -
  • updated_at: ${registration.updated_at}
  • -
  • user_updated_at: ${registration.user_updated_at}
  • -
  • upload_status: ${registration.upload_status}
  • -
  • upload_error_message: ${registration.upload_error_message}
  • -
    +
    Already Registered
    +

    Here is the current state of your registration, for debugging purposes:

    + +
  • id: ${registration.id}
  • +
  • testcenter_user_id: ${registration.testcenter_user_id}
  • +
  • course_id: ${registration.course_id}
  • +
  • accommodation codes: ${registration.accommodation_code}
  • +
  • accommodation request: ${registration.accommodation_request}
  • +
  • created_at: ${registration.created_at}
  • +
  • updated_at: ${registration.updated_at}
  • +
  • user_updated_at: ${registration.user_updated_at}
  • +
  • upload_status: ${registration.upload_status}
  • +
  • upload_error_message: ${registration.upload_error_message}
  • +
    + + + + <% + regstatus = "registration pending acknowledgement by Pearson" - - - <% - regstatus = "registration pending acknowledgement by Pearson" - - if registration.upload_status == 'Accepted': - regstatus = "registration approved by Pearson" - elif registration.upload_status == 'Error': - regstatus = "registration rejected by Pearson: %s" % registration.upload_error_message - elif len(registration.accommodation_request) > 0 and registration.accommodation_code == '': - regstatus = "pending approval of accommodation request" - %> -

    Current status: ${regstatus}

    - -

    The demographic information provided below was used to register - for the exam listed above. Changes to this information - may be submitted below.

    -
    + if registration.upload_status == 'Accepted': + regstatus = "registration approved by Pearson" + elif registration.upload_status == 'Error': + regstatus = "registration rejected by Pearson: %s" % registration.upload_error_message + elif len(registration.accommodation_request) > 0 and registration.accommodation_code == '': + regstatus = "pending approval of accommodation request" + %> +

    Current status: ${regstatus}

    + +

    The demographic information provided below was used to register + for the exam listed above. Changes to this information + may be submitted below.

    +
    + % else:

    The demographic information must be provided below in order to register for the exam listed above.

    @@ -127,106 +120,94 @@ % if message: -
    -

    ${message}

    -
    +
    +

    ${message}

    +
    % endif -
    -
    - - -
    - - - - - - -
    - - -
    - - -
    - - -
    - - -
    - - -
    -
    - -
    - - -
    - - -
    - - -
    - - - - - -
    - - - - -
    - -
    - - - - - - -
    - - - - -
    - - -
    -

    The following is included here just so it can be input within the form. But it - is not part of the demographics, and it is not something that can be changed once input.

    -
    -
    - - -
    - - % if len(registrations) > 0: -
    - -
    - % else: -
    - -
    - % endif + -
    + + + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    - + +
    + + +
    + + +
    + + +
    + + + + + +
    + + + + +
    + +
    + + + + + + +
    + + + + +
    + + +
    +
    + +

    The following is included here just so it can be input within the form. But it is not part of the demographics, and it is not something that can be changed once input.

    + +
    + +
    + +% if len(registrations) > 0: +
    + +
    + % else: +
    + +
    + % endif + +
    From 76f6ee18772d905033d2ff65218f9ca8c3dd2588 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 20 Dec 2012 13:00:08 -0500 Subject: [PATCH 10/64] test center - reorg of HTML - wip --- lms/templates/test_center_register.html | 132 ++++++++++++++---------- 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/lms/templates/test_center_register.html b/lms/templates/test_center_register.html index f1b071d121..8d5f7cb206 100644 --- a/lms/templates/test_center_register.html +++ b/lms/templates/test_center_register.html @@ -35,13 +35,17 @@ +<% +exam_info = course.testcenter_info +%> +
    -
    +

    % if course.has_ended(): @@ -57,66 +61,86 @@

    - <% - exam_info = course.testcenter_info - %> % if exam_info is not None:

    Exam Series Code: ${exam_info.get('Exam_Series_Code')}

    First Eligible Appointment Date: ${exam_info.get('First_Eligible_Appointment_Date')}

    Last Eligible Appointment Date: ${exam_info.get('Last_Eligible_Appointment_Date')}

    % endif + + + <% + registrations = get_testcenter_registrations_for_user_and_course(user, course.id) + %> + + % if len(registrations) > 0: + <% + registration = registrations[0] + %> + +
    Already Registered
    +

    Here is the current state of your registration, for debugging purposes:

    + +
  • id: ${registration.id}
  • +
  • testcenter_user_id: ${registration.testcenter_user_id}
  • +
  • course_id: ${registration.course_id}
  • +
  • accommodation codes: ${registration.accommodation_code}
  • +
  • accommodation request: ${registration.accommodation_request}
  • +
  • created_at: ${registration.created_at}
  • +
  • updated_at: ${registration.updated_at}
  • +
  • user_updated_at: ${registration.user_updated_at}
  • +
  • upload_status: ${registration.upload_status}
  • +
  • upload_error_message: ${registration.upload_error_message}
  • +
    + + + + <% + regstatus = "registration pending acknowledgement by Pearson" + + if registration.upload_status == 'Accepted': + regstatus = "registration approved by Pearson" + elif registration.upload_status == 'Error': + regstatus = "registration rejected by Pearson: %s" % registration.upload_error_message + elif len(registration.accommodation_request) > 0 and registration.accommodation_code == '': + regstatus = "pending approval of accommodation request" + %> +

    Current status: ${regstatus}

    + +

    The demographic information provided below was used to register + for the exam listed above. Changes to this information + may be submitted below.

    +
    + + % else: +

    The demographic information must be provided below in order to register + for the exam listed above.

    + % endif
    - - - <% - registrations = get_testcenter_registrations_for_user_and_course(user, course.id) - %> - - % if len(registrations) > 0: - <% - registration = registrations[0] - %> - -
    Already Registered
    -

    Here is the current state of your registration, for debugging purposes:

    - -
  • id: ${registration.id}
  • -
  • testcenter_user_id: ${registration.testcenter_user_id}
  • -
  • course_id: ${registration.course_id}
  • -
  • accommodation codes: ${registration.accommodation_code}
  • -
  • accommodation request: ${registration.accommodation_request}
  • -
  • created_at: ${registration.created_at}
  • -
  • updated_at: ${registration.updated_at}
  • -
  • user_updated_at: ${registration.user_updated_at}
  • -
  • upload_status: ${registration.upload_status}
  • -
  • upload_error_message: ${registration.upload_error_message}
  • -
    - - - - <% - regstatus = "registration pending acknowledgement by Pearson" - - if registration.upload_status == 'Accepted': - regstatus = "registration approved by Pearson" - elif registration.upload_status == 'Error': - regstatus = "registration rejected by Pearson: %s" % registration.upload_error_message - elif len(registration.accommodation_request) > 0 and registration.accommodation_code == '': - regstatus = "pending approval of accommodation request" - %> -

    Current status: ${regstatus}

    - -

    The demographic information provided below was used to register - for the exam listed above. Changes to this information - may be submitted below.

    -
    - % else: -

    The demographic information must be provided below in order to register - for the exam listed above.

    - % endif +
    +
    +
    +

    ${get_course_about_section(course, 'university')} ${course.number} ${course.title}

    +

    Register for a Pearson VUE Proctored Exam

    +
    + + + % if course.has_ended(): + Course Completed: ${course.end_date_text} + % elif course.has_started(): + Course Started: ${course.start_date_text} + % else: # hasn't started yet + Course Starts: ${course.start_date_text} + % endif + +
    + +
    + +
    +
    % if message: From 2788ad8629d8f0371339543df5d8c3e921a73882 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Fri, 21 Dec 2012 01:39:18 -0500 Subject: [PATCH 11/64] switch back to returning json to test_center_register template, and enable jquery handling in template. --- common/djangoapps/student/views.py | 40 ++++++++++++------------- lms/templates/test_center_register.html | 16 ++++------ 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 62943478a0..29d8a206cb 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -675,13 +675,18 @@ def _do_create_or_update_test_center_user(post_vars): try: testcenter_user.save() except IntegrityError, ie: - message = "%s" % ie - context = {'course': course, - 'user': user, - 'message': message, - 'testcenteruser': testcenter_user, - } - return render_to_response('test_center_register.html', context) + js = {'success': False} + error_msg = unicode(ie); + # attempt to find a field name to signal + for fieldname in TestCenterUser.user_provided_fields(): + if error_msg.find(fieldname) >= 0: + js['value'] = error_msg + js['field'] = fieldname + return HttpResponse(json.dumps(js)) + # otherwise just return the error message + js['value'] = error_msg + js['field'] = "General Error" + return HttpResponse(json.dumps(js)) # create and save the registration: needs_saving = False @@ -715,13 +720,7 @@ def _do_create_or_update_test_center_user(post_vars): try: registration.save() except IntegrityError, ie: - message = "%s" % ie - context = {'course': course, - 'user': user, - 'message': message, - 'testcenteruser': testcenter_user, - } - return render_to_response('test_center_register.html', context) + raise return (user, testcenter_user, registration) @@ -743,14 +742,14 @@ def create_test_registration(request, post_override=None): js['field'] = a return HttpResponse(json.dumps(js)) - # Confirm appropriate fields are there. + # Confirm appropriate fields are filled in with something for now for a in ['first_name', 'last_name', 'address_1', 'city', 'country']: if len(post_vars[a]) < 2: error_str = {'first_name': 'First name must be minimum of two characters long.', 'last_name': 'Last name must be minimum of two characters long.', - 'address_1': 'Last name must be minimum of two characters long.', - 'city': 'Last name must be minimum of two characters long.', - 'country': 'Last name must be minimum of two characters long.', + 'address_1': 'Address must be minimum of two characters long.', + 'city': 'City must be minimum of two characters long.', + 'country': 'Country must be minimum of two characters long.', } js['value'] = error_str[a] js['field'] = a @@ -789,9 +788,8 @@ def create_test_registration(request, post_override=None): # TODO: enable appropriate stat # statsd.increment("common.student.account_created") -# js = {'success': True} -# return HttpResponse(json.dumps(js), mimetype="application/json") - return HttpResponseRedirect(reverse('dashboard')) + js = {'success': True} + return HttpResponse(json.dumps(js), mimetype="application/json") def get_random_post_override(): """ diff --git a/lms/templates/test_center_register.html b/lms/templates/test_center_register.html index 8c99b4f8ef..b4d47bf487 100644 --- a/lms/templates/test_center_register.html +++ b/lms/templates/test_center_register.html @@ -15,19 +15,13 @@ From e170afa9276df4ae09e1508af18f17affc5ac4a1 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 21 Dec 2012 13:23:20 -0500 Subject: [PATCH 14/64] test center registration - forgot to add/commit corresponding SASS file --- .../multicourse/_testcenter-register.scss | 412 ++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 lms/static/sass/multicourse/_testcenter-register.scss diff --git a/lms/static/sass/multicourse/_testcenter-register.scss b/lms/static/sass/multicourse/_testcenter-register.scss new file mode 100644 index 0000000000..9286155c48 --- /dev/null +++ b/lms/static/sass/multicourse/_testcenter-register.scss @@ -0,0 +1,412 @@ +// ========== + +$baseline: 20px; +$yellow: rgb(255, 235, 169); +$red: rgb(178, 6, 16); + +// ========== + +.testcenter-register { + @include clearfix; + padding: 60px 0px 120px; + + // basic layout + .introduction { + width: flex-grid(12); + } + + .message-status-registration { + width: flex-grid(12); + } + + .content, aside { + @include box-sizing(border-box); + } + + .content { + margin-right: flex-gutter(); + width: flex-grid(8); + float: left; + } + + aside { + margin: 0; + width: flex-grid(4); + float: left; + } + + // introduction + .introduction { + + header { + + h2 { + margin: 0; + font-family: $sans-serif; + font-size: 16px; + color: $lighter-base-font-color; + } + + h1 { + font-family: $sans-serif; + font-size: 34px; + text-align: left; + } + } + } + + // content + .content { + background: rgb(255,255,255); + } + + // form + .form-fields-primary, .form-fields-secondary { + border-bottom: 1px solid rgba(0,0,0,0.25); + @include box-shadow(0 1px 2px 0 rgba(0,0,0, 0.1)); + } + + form { + border: 1px solid rgb(216, 223, 230); + @include border-radius(3px); + @include box-shadow(0 1px 2px 0 rgba(0,0,0, 0.2)); + + .instructions { + margin: 0; + padding: ($baseline*1.5) ($baseline*1.5) 0 ($baseline*1.5); + font-family: $sans-serif; + font-size: 14px; + } + + fieldset { + border-bottom: 1px solid rgba(216, 223, 230, 0.50); + padding: ($baseline*1.5); + } + + .form-actions { + padding: ($baseline*1.5); + + button[type="submit"] { + display: block; + width: 100%; + @include button(simple, $blue); + @include box-sizing(border-box); + @include border-radius(3px); + font: bold 15px/1.6rem $sans-serif; + letter-spacing: 0; + padding: ($baseline*0.75) $baseline; + text-align: center; + } + } + + .list-input { + margin: 0; + padding: 0; + list-style: none; + + .field { + border-bottom: 1px dotted rgba(216, 223, 230, 0.5); + margin: 0 0 $baseline 0; + padding: 0 0 $baseline 0; + + &:last-child { + border: none; + margin-bottom: 0; + padding-bottom: 0; + } + + &.disabled { + color: rgba(0,0,0,.25); + + label { + color: rgba(0,0,0,.25); + + &:after { + content: "(Disabled Currently)"; + margin-left: ($baseline/4); + } + } + + textarea, input { + background: rgb(255,255,255); + color: rgba(0,0,0,.25); + } + } + + &.error { + + label { + color: $red; + } + + input, textarea { + border-color: tint($red,50%); + } + } + + &.required { + + label { + font-weight: bold; + } + + label:after { + margin-left: ($baseline/4); + content: "*"; + } + } + + label, input, textarea { + display: block; + font-family: $sans-serif; + font-style: normal; + } + + label { + margin: 0 0 ($baseline/4) 0; + @include transition(color, 0.15s, ease-in-out); + + &.is-focused { + color: $blue; + } + } + + input, textarea { + width: 100%; + padding: $baseline ($baseline*.75); + + &.long { + width: 100%; + } + + &.short { + width: 25%; + } + } + + textarea.long { + height: ($baseline*5); + } + } + + .field-group { + @include clearfix(); + border-bottom: 1px dotted rgba(216, 223, 230, 0.5); + margin: 0 0 $baseline 0; + padding: 0 0 $baseline 0; + + .field { + display: block; + float: left; + border-bottom: none; + margin: 0 ($baseline) 0 0; + padding-bottom: 0; + + input, textarea { + width: 100%; + } + } + + &.addresses { + + .field { + width: 45%; + } + } + + &.postal { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; + + } + + &.phoneinfo { + + } + } + } + } + + + // aside + aside { + padding-left: $baseline; + + .message-status { + @include border-radius(3px); + margin: 0 0 ($baseline*2) 0; + padding: $baseline; + border: 1px solid #ccc; + background: tint($yellow,85%); + + p { + margin: 0 0 ($baseline/4) 0; + padding: 0; + font-size: 13px; + font-family: $sans-serif; + } + + .label, .value { + display: block; + } + + .label { + margin-right: ($baseline/4); + text-transform: uppercase; + letter-spacing: 1px; + } + + .value { + color: rgba(0,0,0,0.9); + font-size: 14px; + } + + .registration-status { + margin: 0 0 ($baseline/2) 0; + + .label { + margin: 0 0 ($baseline/2) 0; + color: #ccc; + } + } + + .registration-number { + + .label { + text-transform: none; + letter-spacing: 0; + } + + .label, .value { + display: inline-block + } + } + + .message-copy { + margin: 0; + color: rgba(0,0,0,0.65); + } + } + + .registration-accepted { + + .message-copy { + margin: 0 0 ($baseline/2) 0; + } + + .exam-button { + @include button(simple, $pink); + display: block; + padding: ($baseline/2) $baseline; + font-size: 13px; + font-weight: bold; + + &:hover { + text-decoration: none; + } + } + } + + .details { + border-bottom: 1px solid rgba(216, 223, 230, 0.5); + margin: 0 0 $baseline 0; + padding: 0 0 $baseline 0; + font-family: $sans-serif; + font-size: 14px; + + &:last-child { + border: none; + margin-bottom: 0; + padding-bottom: 0; + } + + h4 { + margin: 0 0 ($baseline/2) 0; + font-family: $sans-serif; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #ccc; + } + + .label, .value { + display: inline-block; + } + + .label { + color: rgba(0,0,0,.65); + margin-right: ($baseline/2); + } + + .value { + color: rgb(0,0,0); + } + } + + .details-course { + + } + + .details-registration { + + ul { + margin: 0; + padding: 0; + list-style: none; + + li { + margin: 0 0 ($baseline/4) 0; + } + } + } + } + + // status messages + .message { + @include border-radius(3px); + display: none; + margin: $baseline 0; + padding: ($baseline/2) $baseline; + + &.is-shown { + display: block; + } + + .message-copy { + font-family: $sans-serif; + font-size: 14px; + } + + // submission error + &.submission-error { + border: 1px solid tint($red,85%); + background: tint($red,95%); + + p { + color: $red; + } + } + + // submission success + &.submission-saved { + border: 1px solid tint($blue,85%); + background: tint($blue,95%); + + .message-copy { + color: $blue; + } + } + } + + + // hidden + .is-hidden { + display: none; + } + + // temp + .output-raw { + display: none; + } +} \ No newline at end of file From 4abd9cd3d437a149973655af10ed49654efb57dd Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Wed, 2 Jan 2013 18:56:25 -0500 Subject: [PATCH 15/64] get closer to working again --- common/djangoapps/student/models.py | 57 ++++++++++++++++- common/djangoapps/student/views.py | 81 ++++++++++++++----------- lms/templates/test_center_register.html | 76 ++++++++++++++--------- 3 files changed, 148 insertions(+), 66 deletions(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 716c472330..88b3c3cd80 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -47,6 +47,7 @@ from django.contrib.auth.models import User from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver +from django.forms import ModelForm import comment_client as cc from django_comment_client.models import Role @@ -194,7 +195,7 @@ class TestCenterUser(models.Model): # Confirmation upload_status = models.CharField(max_length=20, blank=True) # 'Error' or 'Accepted' - uploaded_at = models.DateTimeField(null=True, db_index=True) + uploaded_at = models.DateTimeField(null=True, blank=True, db_index=True) upload_error_message = models.CharField(max_length=512, blank=True) @staticmethod @@ -206,7 +207,56 @@ class TestCenterUser(models.Model): @property def email(self): return self.user.email + + def needs_update(self, dict): +# needs_updating = any([__getattribute__(fieldname) != dict[fieldname] +# for fieldname in TestCenterUser.user_provided_fields()]) + for fieldname in TestCenterUser.user_provided_fields(): + if self.__getattribute__(fieldname) != dict[fieldname]: + return True + + return False + + def update(self, dict): + # leave user and client_candidate_id as before + self.user_updated_at = datetime.now() + for fieldname in TestCenterUser.user_provided_fields(): + self.__setattr__(fieldname, dict[fieldname]) + @staticmethod + def create(user, dict): + testcenter_user = TestCenterUser(user=user) + testcenter_user.update(dict) + # testcenter_user.candidate_id remains unset + # TODO: assign an ID of our own: + testcenter_user.client_candidate_id = 'edx' + '123456' # some unique value + + +class TestCenterUserForm(ModelForm): + class Meta: + model = TestCenterUser + fields = ( 'first_name', 'middle_name', 'last_name', 'suffix', 'salutation', + 'address_1', 'address_2', 'address_3', 'city', 'state', 'postal_code', 'country', + 'phone', 'extension', 'phone_country_code', 'fax', 'fax_country_code', 'company_name') + + + + + + +ACCOMODATION_CODES = ( + ('EQPMNT', 'Equipment'), + ('ET12ET', 'Extra Time - 1/2 Exam Time'), + ('ET30MN', 'Extra Time - 30 Minutes'), + ('ETDBTM', 'Extra Time - Double Time'), + ('SEPRMM', 'Separate Room'), + ('SRREAD', 'Separate Room & Reader'), + ('SRRERC', 'Separate Room & Reader/Recorder'), + ('SRRECR', 'Separate Room & Recorder'), + ('SRSEAN', 'Separate Room & Service Animal'), + ('SRSGNR', 'Separate Room & Sign Lang Interp'), + ) + class TestCenterRegistration(models.Model): """ This is our representation of a user's registration for in-person testing, @@ -242,7 +292,8 @@ class TestCenterRegistration(models.Model): 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: + + # this is really a list of codes, using an '*' as a delimiter. accommodation_code = models.CharField(max_length=64, blank=True) # store the original text of the accommodation request. @@ -250,7 +301,7 @@ class TestCenterRegistration(models.Model): # Confirmation upload_status = models.CharField(max_length=20, blank=True) # 'Error' or 'Accepted' - uploaded_at = models.DateTimeField(null=True, db_index=True) + uploaded_at = models.DateTimeField(null=True, blank=True, db_index=True) upload_error_message = models.CharField(max_length=512, blank=True) @property diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 29d8a206cb..47992554f2 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -29,7 +29,7 @@ from bs4 import BeautifulSoup from django.core.cache import cache from django_future.csrf import ensure_csrf_cookie, csrf_exempt -from student.models import (Registration, UserProfile, TestCenterUser, TestCenterRegistration, +from student.models import (Registration, UserProfile, TestCenterUser, TestCenterUserForm, TestCenterRegistration, PendingNameChange, PendingEmailChange, CourseEnrollment, unique_id_for_user, get_testcenter_registrations_for_user_and_course) @@ -650,29 +650,41 @@ def _do_create_or_update_test_center_user(post_vars): try: testcenter_user = TestCenterUser.objects.get(user=user) # found a TestCenterUser, so check to see if it has changed - needs_updating = any([testcenter_user.__getattribute__(fieldname) != post_vars[fieldname] - for fieldname in TestCenterUser.user_provided_fields()]) - +# needs_updating = any([testcenter_user.__getattribute__(fieldname) != post_vars[fieldname] +# for fieldname in TestCenterUser.user_provided_fields()]) + needs_updating = testcenter_user.needs_update(post_vars) if needs_updating: - # leave user and client_candidate_id as before - testcenter_user.user_updated_at = datetime.datetime.now() - for fieldname in TestCenterUser.user_provided_fields(): - testcenter_user.__setattr__(fieldname, post_vars[fieldname]) +# # leave user and client_candidate_id as before +# testcenter_user.user_updated_at = datetime.datetime.now() +# for fieldname in TestCenterUser.user_provided_fields(): +# testcenter_user.__setattr__(fieldname, post_vars[fieldname]) + testcenter_user.update(post_vars) needs_saving = True except TestCenterUser.DoesNotExist: # did not find the TestCenterUser, so create a new one - testcenter_user = TestCenterUser(user=user) - for fieldname in TestCenterUser.user_provided_fields(): - testcenter_user.__setattr__(fieldname, post_vars[fieldname]) - # testcenter_user.candidate_id remains unset - testcenter_user.client_candidate_id = 'edx' + '123456' # some unique value - testcenter_user.user_updated_at = datetime.datetime.now() + testcenter_user = TestCenterUser.create(user, post_vars) +# testcenter_user = TestCenterUser(user=user) +# testcenter_user.update(post_vars) +## for fieldname in TestCenterUser.user_provided_fields(): +## testcenter_user.__setattr__(fieldname, post_vars[fieldname]) +# # testcenter_user.candidate_id remains unset +# testcenter_user.client_candidate_id = 'edx' + '123456' # some unique value +## testcenter_user.user_updated_at = datetime.datetime.now() needs_saving = True - # additional validation occurs at save time, so handle exceptions if needs_saving: try: + # first perform validation on the user information + # using a Django Form. + form = TestCenterUserForm(testcenter_user) + if not form.is_valid(): + response_data = {'success': False} + # return a list of errors... + response_data['field_errors'] = form.errors + response_data['non_field_errors'] = form.non_field_errors() + return HttpResponse(json.dumps(response_data)) + testcenter_user.save() except IntegrityError, ie: js = {'success': False} @@ -728,7 +740,7 @@ def _do_create_or_update_test_center_user(post_vars): def create_test_registration(request, post_override=None): ''' JSON call to create test registration. - Used by form in test_center_register_modal.html, which is included + Used by form in test_center_register.html, which is called from into dashboard.html ''' js = {'success': False} @@ -736,24 +748,24 @@ def create_test_registration(request, post_override=None): post_vars = post_override if post_override else request.POST # Confirm we have a properly formed request - for a in ['first_name', 'last_name', 'address_1', 'city', 'country']: - if a not in post_vars: - js['value'] = "Error (401 {field}). E-mail us.".format(field=a) - js['field'] = a - return HttpResponse(json.dumps(js)) - - # Confirm appropriate fields are filled in with something for now - for a in ['first_name', 'last_name', 'address_1', 'city', 'country']: - if len(post_vars[a]) < 2: - error_str = {'first_name': 'First name must be minimum of two characters long.', - 'last_name': 'Last name must be minimum of two characters long.', - 'address_1': 'Address must be minimum of two characters long.', - 'city': 'City must be minimum of two characters long.', - 'country': 'Country must be minimum of two characters long.', - } - js['value'] = error_str[a] - js['field'] = a - return HttpResponse(json.dumps(js)) +# for a in ['first_name', 'last_name', 'address_1', 'city', 'country']: +# if a not in post_vars: +# js['value'] = "Error (401 {field}). E-mail us.".format(field=a) +# js['field'] = a +# return HttpResponse(json.dumps(js)) +# +# # Confirm appropriate fields are filled in with something for now +# for a in ['first_name', 'last_name', 'address_1', 'city', 'country']: +# if len(post_vars[a]) < 2: +# error_str = {'first_name': 'First name must be minimum of two characters long.', +# 'last_name': 'Last name must be minimum of two characters long.', +# 'address_1': 'Address must be minimum of two characters long.', +# 'city': 'City must be minimum of two characters long.', +# 'country': 'Country must be minimum of two characters long.', +# } +# js['value'] = error_str[a] +# js['field'] = a +# return HttpResponse(json.dumps(js)) # Once the test_center_user information has been validated, create the entries: ret = _do_create_or_update_test_center_user(post_vars) @@ -791,6 +803,7 @@ def create_test_registration(request, post_override=None): js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") + def get_random_post_override(): """ Return a dictionary suitable for passing to post_vars of _do_create_account or post_override diff --git a/lms/templates/test_center_register.html b/lms/templates/test_center_register.html index 0860adfe6c..d6a38e1bb4 100644 --- a/lms/templates/test_center_register.html +++ b/lms/templates/test_center_register.html @@ -45,6 +45,13 @@ exam_info = course.testcenter_info If the user has already registered in the past for a test center, then also display their ID. --> + + <% + registrations = get_testcenter_registrations_for_user_and_course(user, course.id) + %> + +

    @@ -67,12 +74,6 @@ exam_info = course.testcenter_info

    Last Eligible Appointment Date: ${exam_info.get('Last_Eligible_Appointment_Date')}

    % endif - - <% - registrations = get_testcenter_registrations_for_user_and_course(user, course.id) - %> - % if len(registrations) > 0: <% registration = registrations[0] @@ -161,14 +162,14 @@ exam_info = course.testcenter_info % if len(registrations) > 0:

    - Please complete the following form to update your demographic information used in your Pearson VUE Porctored Exam. Required fields are noted by bold text and an asterisk (*). + Please complete the following form to update your demographic information used in your Pearson VUE Proctored Exam. Required fields are noted by bold text and an asterisk (*).

    % else:

    - Please provide the following demographic information to register for a Pearson VUE Porctored Exam. Required fields are noted by bold text and an asterisk (*). + Please provide the following demographic information to register for a Pearson VUE Proctored Exam. Required fields are noted by bold text and an asterisk (*).

    % endif - + @@ -181,24 +182,24 @@ exam_info = course.testcenter_info
    1. - +
    2. - +
    3. - +
    4. - +
    5. - +
    @@ -209,30 +210,34 @@ exam_info = course.testcenter_info
    1. - +
    2. - +
      - +
    3. +
      + + +
      - +
      - +
      - +
    @@ -245,30 +250,30 @@ exam_info = course.testcenter_info
  • - +
    - +
    - +
  • - +
    - +
  • - +
  • @@ -312,38 +317,51 @@ exam_info = course.testcenter_info
    -
    \ No newline at end of file +
    From c76f37050a4b711de5a4bceea6a490799089ed18 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Thu, 3 Jan 2013 03:22:37 -0500 Subject: [PATCH 16/64] introduce form to template --- common/djangoapps/student/views.py | 154 ++++++++++++------------ lms/templates/test_center_register.html | 128 +++++--------------- 2 files changed, 103 insertions(+), 179 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 47992554f2..202bfefac5 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -39,7 +39,6 @@ from certificates.models import CertificateStatuses, certificate_status_for_stud from xmodule.course_module import CourseDescriptor from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.django import modulestore -from xmodule.modulestore.exceptions import ItemNotFoundError #from datetime import date from collections import namedtuple @@ -209,14 +208,6 @@ def _cert_info(user, course, cert_status): def dashboard(request): user = request.user enrollments = CourseEnrollment.objects.filter(user=user) - - # we want to populate the registration page with the relevant information, - # if it already exists. Create an empty object otherwise. -# try: -# testcenteruser = TestCenterUser.objects.get(user=user) -# except TestCenterUser.DoesNotExist: -# testcenteruser = TestCenterUser() -# testcenteruser.user = user # Build our courses list for the user, but ignore any courses that no longer # exist (because the course IDs have changed). Still, we don't delete those @@ -603,9 +594,27 @@ def create_account(request, post_override=None): @login_required @ensure_csrf_cookie -def begin_test_registration(request, course_id): - user = request.user - +def begin_test_registration(request, course_id, form=None, message=''): + user = request.user + + try: + course = (course_from_id(course_id)) + except ItemNotFoundError: + log.error("User {0} enrolled in non-existent course {1}" + .format(user.username, course_id)) + + # get the exam to be registered for: + # (For now, we just assume there is one at most.) + exam_info = course.testcenter_info + + # figure out if the user is already registered for this exam: + # (Again, for now we assume that any registration that exists is for this exam.) + registrations = get_testcenter_registrations_for_user_and_course(user, course_id) + if len(registrations) > 0: + registration = registrations[0] + else: + registration = None + # we want to populate the registration page with the relevant information, # if it already exists. Create an empty object otherwise. try: @@ -613,31 +622,31 @@ def begin_test_registration(request, course_id): except TestCenterUser.DoesNotExist: testcenteruser = TestCenterUser() testcenteruser.user = user - - try: - course = (course_from_id(course_id)) - except ItemNotFoundError: - log.error("User {0} enrolled in non-existent course {1}" - .format(user.username, course_id)) - message = "" + if form is None: + form = TestCenterUserForm(instance=testcenteruser) + context = {'course': course, 'user': user, 'message': message, 'testcenteruser': testcenteruser, + 'registration': registration, + 'form': form, + 'exam_info': exam_info, } return render_to_response('test_center_register.html', context) +@ensure_csrf_cookie +def create_test_registration(request, post_override=None): + ''' + JSON call to create test registration. + Used by form in test_center_register.html, which is called from + into dashboard.html + ''' + # js = {'success': False} -def _do_create_or_update_test_center_user(post_vars): - """ - Given cleaned post variables, create the TestCenterUser and UserProfile objects, as well as the - registration for this user. - - Returns a tuple (User, UserProfile, TestCenterUser). - - """ + post_vars = post_override if post_override else request.POST # first determine if we need to create a new TestCenterUser, or if we are making any update # to an existing TestCenterUser. @@ -646,46 +655,43 @@ def _do_create_or_update_test_center_user(post_vars): course_id = post_vars['course_id'] course = (course_from_id(course_id)) # assume it will be found.... - needs_saving = False +# needs_saving = False try: testcenter_user = TestCenterUser.objects.get(user=user) - # found a TestCenterUser, so check to see if it has changed -# needs_updating = any([testcenter_user.__getattribute__(fieldname) != post_vars[fieldname] -# for fieldname in TestCenterUser.user_provided_fields()]) - needs_updating = testcenter_user.needs_update(post_vars) - if needs_updating: -# # leave user and client_candidate_id as before -# testcenter_user.user_updated_at = datetime.datetime.now() -# for fieldname in TestCenterUser.user_provided_fields(): -# testcenter_user.__setattr__(fieldname, post_vars[fieldname]) - testcenter_user.update(post_vars) - needs_saving = True - except TestCenterUser.DoesNotExist: - # did not find the TestCenterUser, so create a new one - testcenter_user = TestCenterUser.create(user, post_vars) -# testcenter_user = TestCenterUser(user=user) -# testcenter_user.update(post_vars) -## for fieldname in TestCenterUser.user_provided_fields(): -## testcenter_user.__setattr__(fieldname, post_vars[fieldname]) -# # testcenter_user.candidate_id remains unset -# testcenter_user.client_candidate_id = 'edx' + '123456' # some unique value -## testcenter_user.user_updated_at = datetime.datetime.now() - needs_saving = True + testcenter_user = TestCenterUser(user=user) - if needs_saving: + needs_updating = testcenter_user.needs_update(post_vars) +# if needs_updating: +# testcenter_user.update(post_vars) +# needs_saving = True + + # except TestCenterUser.DoesNotExist: + # did not find the TestCenterUser, so create a new one +# testcenter_user = TestCenterUser.create(user, post_vars) +# needs_saving = True + + # perform validation: + if needs_updating: try: # first perform validation on the user information # using a Django Form. - form = TestCenterUserForm(testcenter_user) + form = TestCenterUserForm(instance=testcenter_user, data=post_vars) if not form.is_valid(): - response_data = {'success': False} - # return a list of errors... - response_data['field_errors'] = form.errors - response_data['non_field_errors'] = form.non_field_errors() - return HttpResponse(json.dumps(response_data)) + return begin_test_registration(request, course_id, form, 'failed to validate') +# response_data = {'success': False} +# # return a list of errors... +# response_data['field_errors'] = form.errors +# response_data['non_field_errors'] = form.non_field_errors() +# return HttpResponse(json.dumps(response_data)) + + new_user = form.save(commit=False) + # create additional values here: + new_user.user_updated_at = datetime.datetime.now() + # TODO: create client value.... + new_user.save() - testcenter_user.save() + # testcenter_user.save() except IntegrityError, ie: js = {'success': False} error_msg = unicode(ie); @@ -715,7 +721,7 @@ def _do_create_or_update_test_center_user(post_vars): else: registration = TestCenterRegistration(testcenter_user = testcenter_user) registration.course_id = post_vars['course_id'] - registration.accommodation_request = post_vars['accommodations'] + registration.accommodation_request = post_vars.get('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') @@ -734,18 +740,8 @@ def _do_create_or_update_test_center_user(post_vars): except IntegrityError, ie: raise - return (user, testcenter_user, registration) +# return (user, testcenter_user, registration) -@ensure_csrf_cookie -def create_test_registration(request, post_override=None): - ''' - JSON call to create test registration. - Used by form in test_center_register.html, which is called from - into dashboard.html - ''' - js = {'success': False} - - post_vars = post_override if post_override else request.POST # Confirm we have a properly formed request # for a in ['first_name', 'last_name', 'address_1', 'city', 'country']: @@ -768,13 +764,12 @@ def create_test_registration(request, post_override=None): # return HttpResponse(json.dumps(js)) # Once the test_center_user information has been validated, create the entries: - ret = _do_create_or_update_test_center_user(post_vars) - if isinstance(ret,HttpResponse): # if there was an error then return that - return ret - - - (user, testcenter_user, testcenter_registration) = ret - +# ret = _do_create_or_update_test_center_user(post_vars) +# if isinstance(ret,HttpResponse): # if there was an error then return that +# return ret +# +# +# (user, testcenter_user, testcenter_registration) = ret # only do the following if there is accommodation text to send, # and a destination to which to send it: @@ -800,8 +795,9 @@ def create_test_registration(request, post_override=None): # 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(reverse('dashboard')) def get_random_post_override(): diff --git a/lms/templates/test_center_register.html b/lms/templates/test_center_register.html index d6a38e1bb4..fe175b11e4 100644 --- a/lms/templates/test_center_register.html +++ b/lms/templates/test_center_register.html @@ -10,18 +10,18 @@ <%namespace name='static' file='static_content.html'/> <%block name="title">Pearson VUE Test Center Proctoring - Sign Up - +<%doc> <%block name="js_extra"> + -<% -exam_info = course.testcenter_info -%>
    - - - - - <% - registrations = get_testcenter_registrations_for_user_and_course(user, course.id) - %> - - -
    -
    -

    - % if course.has_ended(): - Course Completed - ${course.end_date_text} - % elif course.has_started(): - Course Started - ${course.start_date_text} - % else: # hasn't started yet - Course Starts - ${course.start_date_text} - % endif -

    -

    ${get_course_about_section(course, 'university')}

    -

    ${course.number} ${course.title}

    -
    - - - % if exam_info is not None: -

    Exam Series Code: ${exam_info.get('Exam_Series_Code')}

    -

    First Eligible Appointment Date: ${exam_info.get('First_Eligible_Appointment_Date')}

    -

    Last Eligible Appointment Date: ${exam_info.get('Last_Eligible_Appointment_Date')}

    - % endif - - % if len(registrations) > 0: - <% - registration = registrations[0] - %> - -
    Already Registered
    -

    Here is the current state of your registration, for debugging purposes:

    - -
  • id: ${registration.id}
  • -
  • testcenter_user_id: ${registration.testcenter_user_id}
  • -
  • course_id: ${registration.course_id}
  • -
  • accommodation codes: ${registration.accommodation_code}
  • -
  • accommodation request: ${registration.accommodation_request}
  • -
  • created_at: ${registration.created_at}
  • -
  • updated_at: ${registration.updated_at}
  • -
  • user_updated_at: ${registration.user_updated_at}
  • -
  • upload_status: ${registration.upload_status}
  • -
  • upload_error_message: ${registration.upload_error_message}
  • -
    - - - - <% - regstatus = "registration pending acknowledgement by Pearson" - - if registration.upload_status == 'Accepted': - regstatus = "registration approved by Pearson" - elif registration.upload_status == 'Error': - regstatus = "registration rejected by Pearson: %s" % registration.upload_error_message - elif len(registration.accommodation_request) > 0 and registration.accommodation_code == '': - regstatus = "pending approval of accommodation request" - %> -

    Current status: ${regstatus}

    - -

    The demographic information provided below was used to register - for the exam listed above. Changes to this information - may be submitted below.

    -
    - - % else: -

    The demographic information must be provided below in order to register - for the exam listed above.

    - % endif -

    ${get_course_about_section(course, 'university')} ${course.number} ${course.title}

    - % if len(registrations) > 0: + % if registration:

    Your Pearson VUE Proctored Exam Registration

    % else:

    Register for a Pearson VUE Proctored Exam

    @@ -136,8 +55,9 @@ exam_info = course.testcenter_info
    - -
    + +

    Your registration data has been updated and saved.

    @@ -148,10 +68,12 @@ exam_info = course.testcenter_info
    % endif - + + % if form.errors and len(form.errors) > 0:

    We're Sorry, but there was an error with the information you provided below:

    + % endif
    @@ -160,7 +82,7 @@ exam_info = course.testcenter_info
    - % if len(registrations) > 0: + % if registration:

    Please complete the following form to update your demographic information used in your Pearson VUE Proctored Exam. Required fields are noted by bold text and an asterisk (*).

    @@ -181,15 +103,23 @@ exam_info = course.testcenter_info
    1. + + ${form['salutation']} +
    2. +
    3. + + ${form['first_name']} +
    4. + -
    5. + --> +
    6. @@ -281,7 +211,7 @@ exam_info = course.testcenter_info
      - % if len(registrations) > 0: + % if registration:

      Fields below this point are not part of the demographics, and are not editable currently.

      % else:

      Fields below this point are not part of the demographics, and cannot be changed once submitted.

      @@ -291,7 +221,7 @@ exam_info = course.testcenter_info
        - % if len(registrations) > 0: + % if registration:
      1. @@ -307,7 +237,7 @@ exam_info = course.testcenter_info
      - % if len(registrations) > 0: + % if registration: % else: @@ -317,12 +247,10 @@ exam_info = course.testcenter_info