Resolve conflicts merging master to rc/2013-11-21
This commit is contained in:
@@ -5,7 +5,7 @@ import datetime
|
||||
from pytz import UTC
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from django.test.client import RequestFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.django import editable_modulestore
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@@ -25,6 +25,7 @@ class AnonymousIndexPageTest(ModuleStoreTestCase):
|
||||
"""
|
||||
def setUp(self):
|
||||
self.store = editable_modulestore()
|
||||
self.factory = RequestFactory()
|
||||
self.course = CourseFactory.create()
|
||||
self.course.days_early_for_beta = 5
|
||||
self.course.enrollment_start = datetime.datetime.now(UTC) + datetime.timedelta(days=3)
|
||||
@@ -32,7 +33,11 @@ class AnonymousIndexPageTest(ModuleStoreTestCase):
|
||||
|
||||
@override_settings(MITX_FEATURES=MITX_FEATURES_WITH_STARTDATE)
|
||||
def test_none_user_index_access_with_startdate_fails(self):
|
||||
with self.assertRaises(Exception):
|
||||
"""
|
||||
This was a "before" test for a bugfix. If someone fixes the bug another way in the future
|
||||
and this test begins failing (but the other two pass), then feel free to delete this test.
|
||||
"""
|
||||
with self.assertRaisesRegexp(AttributeError, "'NoneType' object has no attribute 'is_authenticated'"):
|
||||
student.views.index(self.factory.get('/'), user=None) # pylint: disable=E1101
|
||||
|
||||
@override_settings(MITX_FEATURES=MITX_FEATURES_WITH_STARTDATE)
|
||||
|
||||
@@ -93,6 +93,7 @@ class Command(BaseCommand):
|
||||
total = enrolled_students.count()
|
||||
count = 0
|
||||
start = datetime.datetime.now(UTC)
|
||||
|
||||
for student in enrolled_students:
|
||||
count += 1
|
||||
if count % STATUS_INTERVAL == 0:
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# -*- 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 'GeneratedCertificate.mode'
|
||||
db.add_column('certificates_generatedcertificate', 'mode',
|
||||
self.gf('django.db.models.fields.CharField')(default='honor', max_length=32),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'GeneratedCertificate.mode'
|
||||
db.delete_column('certificates_generatedcertificate', 'mode')
|
||||
|
||||
|
||||
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'})
|
||||
},
|
||||
'certificates.certificatewhitelist': {
|
||||
'Meta': {'object_name': 'CertificateWhitelist'},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'whitelist': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'certificates.generatedcertificate': {
|
||||
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'GeneratedCertificate'},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
|
||||
'created_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'distinction': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'download_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
|
||||
'download_uuid': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||
'error_reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '512', 'blank': 'True'}),
|
||||
'grade': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '5', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '32'}),
|
||||
'modified_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'unavailable'", 'max_length': '32'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'verify_uuid': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'})
|
||||
},
|
||||
'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'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['certificates']
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from datetime import datetime
|
||||
from model_utils import Choices
|
||||
|
||||
"""
|
||||
Certificates are created for a student and an offering of a course.
|
||||
@@ -62,7 +63,6 @@ class CertificateStatuses(object):
|
||||
restricted = 'restricted'
|
||||
unavailable = 'unavailable'
|
||||
|
||||
|
||||
class CertificateWhitelist(models.Model):
|
||||
"""
|
||||
Tracks students who are whitelisted, all users
|
||||
@@ -86,11 +86,13 @@ class GeneratedCertificate(models.Model):
|
||||
key = models.CharField(max_length=32, blank=True, default='')
|
||||
distinction = models.BooleanField(default=False)
|
||||
status = models.CharField(max_length=32, default='unavailable')
|
||||
MODES = Choices('verified', 'honor', 'audit')
|
||||
mode = models.CharField(max_length=32, choices=MODES, default=MODES.honor)
|
||||
name = models.CharField(blank=True, max_length=255)
|
||||
created_date = models.DateTimeField(
|
||||
auto_now_add=True, default=datetime.now)
|
||||
auto_now_add=True, default=datetime.now)
|
||||
modified_date = models.DateTimeField(
|
||||
auto_now=True, default=datetime.now)
|
||||
auto_now=True, default=datetime.now)
|
||||
error_reason = models.CharField(max_length=512, blank=True, default='')
|
||||
|
||||
class Meta:
|
||||
@@ -128,8 +130,9 @@ def certificate_status_for_student(student, course_id):
|
||||
|
||||
try:
|
||||
generated_certificate = GeneratedCertificate.objects.get(
|
||||
user=student, course_id=course_id)
|
||||
d = {'status': generated_certificate.status}
|
||||
user=student, course_id=course_id)
|
||||
d = {'status': generated_certificate.status,
|
||||
'mode': generated_certificate.mode}
|
||||
if generated_certificate.grade:
|
||||
d['grade'] = generated_certificate.grade
|
||||
if generated_certificate.status == CertificateStatuses.downloadable:
|
||||
@@ -138,4 +141,4 @@ def certificate_status_for_student(student, course_id):
|
||||
return d
|
||||
except GeneratedCertificate.DoesNotExist:
|
||||
pass
|
||||
return {'status': CertificateStatuses.unavailable}
|
||||
return {'status': CertificateStatuses.unavailable, 'mode': GeneratedCertificate.MODES.honor}
|
||||
|
||||
@@ -9,7 +9,8 @@ from capa.xqueue_interface import XQueueInterface
|
||||
from capa.xqueue_interface import make_xheader, make_hashkey
|
||||
from django.conf import settings
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from student.models import UserProfile
|
||||
from student.models import UserProfile, CourseEnrollment
|
||||
from verify_student.models import SoftwareSecurePhotoVerification
|
||||
|
||||
import json
|
||||
import random
|
||||
@@ -57,7 +58,7 @@ class XQueueCertInterface(object):
|
||||
|
||||
if settings.XQUEUE_INTERFACE.get('basic_auth') is not None:
|
||||
requests_auth = HTTPBasicAuth(
|
||||
*settings.XQUEUE_INTERFACE['basic_auth'])
|
||||
*settings.XQUEUE_INTERFACE['basic_auth'])
|
||||
else:
|
||||
requests_auth = None
|
||||
|
||||
@@ -68,10 +69,10 @@ class XQueueCertInterface(object):
|
||||
self.request = request
|
||||
|
||||
self.xqueue_interface = XQueueInterface(
|
||||
settings.XQUEUE_INTERFACE['url'],
|
||||
settings.XQUEUE_INTERFACE['django_auth'],
|
||||
requests_auth,
|
||||
)
|
||||
settings.XQUEUE_INTERFACE['url'],
|
||||
settings.XQUEUE_INTERFACE['django_auth'],
|
||||
requests_auth,
|
||||
)
|
||||
self.whitelist = CertificateWhitelist.objects.all()
|
||||
self.restricted = UserProfile.objects.filter(allow_certificate=False)
|
||||
self.use_https = True
|
||||
@@ -84,7 +85,7 @@ class XQueueCertInterface(object):
|
||||
course_id - courseenrollment.course_id (string)
|
||||
|
||||
WARNING: this command will leave the old certificate, if one exists,
|
||||
laying around in AWS taking up space. If this is a problem,
|
||||
laying around in AWS taking up space. If this is a problem,
|
||||
take pains to clean up storage before running this command.
|
||||
|
||||
Change the certificate status to unavailable (if it exists) and request
|
||||
@@ -92,7 +93,7 @@ class XQueueCertInterface(object):
|
||||
|
||||
Return the status object.
|
||||
"""
|
||||
# TODO: when del_cert is implemented and plumbed through certificates
|
||||
# TODO: when del_cert is implemented and plumbed through certificates
|
||||
# repo also, do a deletion followed by a creation r/t a simple
|
||||
# recreation. XXX: this leaves orphan cert files laying around in
|
||||
# AWS. See note in the docstring too.
|
||||
@@ -149,13 +150,15 @@ class XQueueCertInterface(object):
|
||||
"""
|
||||
|
||||
VALID_STATUSES = [status.generating,
|
||||
status.unavailable,
|
||||
status.deleted,
|
||||
status.unavailable,
|
||||
status.deleted,
|
||||
status.error,
|
||||
status.notpassing]
|
||||
|
||||
cert_status = certificate_status_for_student(student, course_id)['status']
|
||||
|
||||
new_status = cert_status
|
||||
|
||||
if cert_status in VALID_STATUSES:
|
||||
# grade the student
|
||||
|
||||
@@ -165,9 +168,6 @@ class XQueueCertInterface(object):
|
||||
course = courses.get_course_by_id(course_id)
|
||||
profile = UserProfile.objects.get(user=student)
|
||||
|
||||
cert, created = GeneratedCertificate.objects.get_or_create(
|
||||
user=student, course_id=course_id)
|
||||
|
||||
# Needed
|
||||
self.request.user = student
|
||||
self.request.session = {}
|
||||
@@ -175,45 +175,64 @@ class XQueueCertInterface(object):
|
||||
grade = grades.grade(student, self.request, course)
|
||||
is_whitelisted = self.whitelist.filter(
|
||||
user=student, course_id=course_id, whitelist=True).exists()
|
||||
enrollment_mode = CourseEnrollment.enrollment_mode_for_user(student, course_id)
|
||||
org = course_id.split('/')[0]
|
||||
course_num = course_id.split('/')[1]
|
||||
cert_mode = enrollment_mode
|
||||
if enrollment_mode == GeneratedCertificate.MODES.verified and SoftwareSecurePhotoVerification.user_is_verified(student):
|
||||
template_pdf = "certificate-template-{0}-{1}-verified.pdf".format(
|
||||
org, course_num)
|
||||
elif (enrollment_mode == GeneratedCertificate.MODES.verified and not
|
||||
SoftwareSecurePhotoVerification.user_is_verified(student)):
|
||||
template_pdf = "certificate-template-{0}-{1}.pdf".format(
|
||||
org, course_num)
|
||||
cert_mode = GeneratedCertificate.MODES.honor
|
||||
else:
|
||||
# honor code and audit students
|
||||
template_pdf = "certificate-template-{0}-{1}.pdf".format(
|
||||
org, course_num)
|
||||
|
||||
cert, created = GeneratedCertificate.objects.get_or_create(
|
||||
user=student, course_id=course_id)
|
||||
|
||||
cert.mode = cert_mode
|
||||
cert.user = student
|
||||
cert.grade = grade['percent']
|
||||
cert.course_id = course_id
|
||||
cert.name = profile.name
|
||||
|
||||
if is_whitelisted or grade['grade'] is not None:
|
||||
|
||||
key = make_hashkey(random.random())
|
||||
|
||||
cert.grade = grade['percent']
|
||||
cert.user = student
|
||||
cert.course_id = course_id
|
||||
cert.key = key
|
||||
cert.name = profile.name
|
||||
|
||||
# check to see whether the student is on the
|
||||
# the embargoed country restricted list
|
||||
# otherwise, put a new certificate request
|
||||
# on the queue
|
||||
|
||||
if self.restricted.filter(user=student).exists():
|
||||
cert.status = status.restricted
|
||||
new_status = status.restricted
|
||||
cert.status = new_status
|
||||
cert.save()
|
||||
else:
|
||||
key = make_hashkey(random.random())
|
||||
cert.key = key
|
||||
contents = {
|
||||
'action': 'create',
|
||||
'username': student.username,
|
||||
'course_id': course_id,
|
||||
'name': profile.name,
|
||||
'grade': grade['grade'],
|
||||
'template_pdf': template_pdf,
|
||||
}
|
||||
cert.status = status.generating
|
||||
new_status = status.generating
|
||||
cert.status = new_status
|
||||
cert.save()
|
||||
self._send_to_xqueue(contents, key)
|
||||
else:
|
||||
cert_status = status.notpassing
|
||||
cert.grade = grade['percent']
|
||||
cert.user = student
|
||||
cert.course_id = course_id
|
||||
cert.name = profile.name
|
||||
cert.status = cert_status
|
||||
new_status = status.notpassing
|
||||
cert.status = new_status
|
||||
cert.save()
|
||||
|
||||
return cert_status
|
||||
return new_status
|
||||
|
||||
def _send_to_xqueue(self, contents, key):
|
||||
|
||||
@@ -227,7 +246,7 @@ class XQueueCertInterface(object):
|
||||
proto, settings.SITE_NAME, key), key, settings.CERT_QUEUE)
|
||||
|
||||
(error, msg) = self.xqueue_interface.send_to_queue(
|
||||
header=xheader, body=json.dumps(contents))
|
||||
header=xheader, body=json.dumps(contents))
|
||||
if error:
|
||||
logger.critical('Unable to add a request to the queue: {} {}'.format(error, msg))
|
||||
raise Exception('Unable to send queue message')
|
||||
|
||||
@@ -60,5 +60,4 @@ class CodeMirror(BaseEditor):
|
||||
"js/vendor/CodeMirror/mitx_markdown.js",
|
||||
"js/wiki/accessible.js",
|
||||
"js/wiki/CodeMirror.init.js",
|
||||
"js/wiki/cheatsheet.js",
|
||||
)
|
||||
|
||||
@@ -335,7 +335,6 @@ def _progress_summary(student, request, course):
|
||||
module_creator = section_module.xmodule_runtime.get_module
|
||||
|
||||
for module_descriptor in yield_dynamic_descriptor_descendents(section_module, module_creator):
|
||||
|
||||
course_id = course.id
|
||||
(correct, total) = get_score(course_id, student, module_descriptor, module_creator)
|
||||
if correct is None and total is None:
|
||||
|
||||
@@ -134,8 +134,8 @@ class Migration(SchemaMigration):
|
||||
'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
|
||||
},
|
||||
'courseware.xmoduleuserstatesummary': {
|
||||
'Meta': {'unique_together': "(('usage_id', 'field_name'),)", 'object_name': 'XModuleUserStateSummary'},
|
||||
'courseware.xmoduleuserstatesummaryfield': {
|
||||
'Meta': {'unique_together': "(('usage_id', 'field_name'),)", 'object_name': 'XModuleUserStateSummaryField'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'usage_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'field_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
|
||||
|
||||
@@ -27,7 +27,6 @@ class StudentModule(models.Model):
|
||||
MODULE_TYPES = (('problem', 'problem'),
|
||||
('video', 'video'),
|
||||
('html', 'html'),
|
||||
('timelimit', 'timelimit'),
|
||||
)
|
||||
## These three are the key for the object
|
||||
module_type = models.CharField(max_length=32, choices=MODULE_TYPES, default='problem', db_index=True)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
"""
|
||||
Tests of the TimeLimitModule
|
||||
|
||||
TODO: This should be a test in common/lib/xmodule. However,
|
||||
actually rendering HTML templates for XModules at this point requires
|
||||
Django (which is storing the templates), so the test can't run in isolation
|
||||
"""
|
||||
from xmodule.modulestore.tests.factories import ItemFactory
|
||||
from xmodule.tests.rendering.core import assert_student_view
|
||||
|
||||
from . import XModuleRenderingTestBase
|
||||
|
||||
|
||||
class TestTimeLimitModuleRendering(XModuleRenderingTestBase):
|
||||
"""
|
||||
Tests of TimeLimitModule html rendering
|
||||
"""
|
||||
def test_with_children(self):
|
||||
block = ItemFactory.create(category='timelimit')
|
||||
block.xmodule_runtime = self.new_module_runtime()
|
||||
ItemFactory.create(category='html', data='<html>This is just text</html>', parent=block)
|
||||
|
||||
assert_student_view(block, block.render('student_view'))
|
||||
|
||||
def test_without_children(self):
|
||||
block = ItemFactory.create(category='timelimit')
|
||||
block.xmodule_runtime = self.new_module_runtime()
|
||||
|
||||
assert_student_view(block, block.render('student_view'))
|
||||
@@ -172,71 +172,6 @@ def save_child_position(seq_module, child_name):
|
||||
seq_module.save()
|
||||
|
||||
|
||||
def check_for_active_timelimit_module(request, course_id, course):
|
||||
"""
|
||||
Looks for a timing module for the given user and course that is currently active.
|
||||
If found, returns a context dict with timer-related values to enable display of time remaining.
|
||||
"""
|
||||
context = {}
|
||||
|
||||
# TODO (cpennington): Once we can query the course structure, replace this with such a query
|
||||
timelimit_student_modules = StudentModule.objects.filter(student=request.user, course_id=course_id, module_type='timelimit')
|
||||
if timelimit_student_modules:
|
||||
for timelimit_student_module in timelimit_student_modules:
|
||||
# get the corresponding section_descriptor for the given StudentModel entry:
|
||||
module_state_key = timelimit_student_module.module_state_key
|
||||
timelimit_descriptor = modulestore().get_instance(course_id, Location(module_state_key))
|
||||
timelimit_module_cache = FieldDataCache.cache_for_descriptor_descendents(course.id, request.user,
|
||||
timelimit_descriptor, depth=None)
|
||||
timelimit_module = get_module_for_descriptor(request.user, request, timelimit_descriptor,
|
||||
timelimit_module_cache, course.id, position=None)
|
||||
if timelimit_module is not None and timelimit_module.category == 'timelimit' and \
|
||||
timelimit_module.has_begun and not timelimit_module.has_ended:
|
||||
location = timelimit_module.location
|
||||
# determine where to go when the timer expires:
|
||||
if timelimit_descriptor.time_expired_redirect_url is None:
|
||||
raise Http404("no time_expired_redirect_url specified at this location: {} ".format(timelimit_module.location))
|
||||
context['time_expired_redirect_url'] = timelimit_descriptor.time_expired_redirect_url
|
||||
# Fetch the remaining time relative to the end time as stored in the module when it was started.
|
||||
# This value should be in milliseconds.
|
||||
remaining_time = timelimit_module.get_remaining_time_in_ms()
|
||||
context['timer_expiration_duration'] = remaining_time
|
||||
context['suppress_toplevel_navigation'] = timelimit_descriptor.suppress_toplevel_navigation
|
||||
return_url = reverse('jump_to', kwargs={'course_id': course_id, 'location': location})
|
||||
context['timer_navigation_return_url'] = return_url
|
||||
return context
|
||||
|
||||
|
||||
def update_timelimit_module(user, course_id, field_data_cache, timelimit_descriptor, timelimit_module):
|
||||
"""
|
||||
Updates the state of the provided timing module, starting it if it hasn't begun.
|
||||
Returns dict with timer-related values to enable display of time remaining.
|
||||
Returns 'timer_expiration_duration' in dict if timer is still active, and not if timer has expired.
|
||||
"""
|
||||
context = {}
|
||||
# determine where to go when the exam ends:
|
||||
if timelimit_descriptor.time_expired_redirect_url is None:
|
||||
raise Http404("No time_expired_redirect_url specified at this location: {} ".format(timelimit_module.location))
|
||||
context['time_expired_redirect_url'] = timelimit_descriptor.time_expired_redirect_url
|
||||
|
||||
if not timelimit_module.has_ended:
|
||||
if not timelimit_module.has_begun:
|
||||
# user has not started the exam, so start it now.
|
||||
if timelimit_descriptor.duration is None:
|
||||
raise Http404("No duration specified at this location: {} ".format(timelimit_module.location))
|
||||
# The user may have an accommodation that has been granted to them.
|
||||
# This accommodation information should already be stored in the module's state.
|
||||
timelimit_module.begin(timelimit_descriptor.duration)
|
||||
|
||||
# the exam has been started, either because the student is returning to the
|
||||
# exam page, or because they have just visited it. Fetch the remaining time relative to the
|
||||
# end time as stored in the module when it was started.
|
||||
context['timer_expiration_duration'] = timelimit_module.get_remaining_time_in_ms()
|
||||
# also use the timed module to determine whether top-level navigation is visible:
|
||||
context['suppress_toplevel_navigation'] = timelimit_descriptor.suppress_toplevel_navigation
|
||||
return context
|
||||
|
||||
|
||||
def chat_settings(course, user):
|
||||
"""
|
||||
Returns a dict containing the settings required to connect to a
|
||||
@@ -390,22 +325,8 @@ def index(request, course_id, chapter=None, section=None,
|
||||
|
||||
# Save where we are in the chapter
|
||||
save_child_position(chapter_module, section)
|
||||
|
||||
# check here if this section *is* a timed module.
|
||||
if section_module.category == 'timelimit':
|
||||
timer_context = update_timelimit_module(user, course_id, section_field_data_cache,
|
||||
section_descriptor, section_module)
|
||||
if 'timer_expiration_duration' in timer_context:
|
||||
context.update(timer_context)
|
||||
else:
|
||||
# if there is no expiration defined, then we know the timer has expired:
|
||||
return HttpResponseRedirect(timer_context['time_expired_redirect_url'])
|
||||
else:
|
||||
# check here if this page is within a course that has an active timed module running. If so, then
|
||||
# add in the appropriate timer information to the rendering context:
|
||||
context.update(check_for_active_timelimit_module(request, course_id, course))
|
||||
|
||||
context['fragment'] = section_module.render('student_view')
|
||||
|
||||
else:
|
||||
# section is none, so display a message
|
||||
prev_section = get_current_child(chapter_module)
|
||||
|
||||
7
lms/djangoapps/shoppingcart/admin.py
Normal file
7
lms/djangoapps/shoppingcart/admin.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
Allows django admin site to add PaidCourseRegistrationAnnotations
|
||||
"""
|
||||
from ratelimitbackend import admin
|
||||
from shoppingcart.models import PaidCourseRegistrationAnnotation
|
||||
|
||||
admin.site.register(PaidCourseRegistrationAnnotation)
|
||||
@@ -0,0 +1,132 @@
|
||||
# -*- 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 model 'PaidCourseRegistrationAnnotation'
|
||||
db.create_table('shoppingcart_paidcourseregistrationannotation', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('course_id', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128, db_index=True)),
|
||||
('annotation', self.gf('django.db.models.fields.TextField')(null=True)),
|
||||
))
|
||||
db.send_create_signal('shoppingcart', ['PaidCourseRegistrationAnnotation'])
|
||||
|
||||
# Adding field 'OrderItem.report_comments'
|
||||
db.add_column('shoppingcart_orderitem', 'report_comments',
|
||||
self.gf('django.db.models.fields.TextField')(default=''),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'PaidCourseRegistrationAnnotation'
|
||||
db.delete_table('shoppingcart_paidcourseregistrationannotation')
|
||||
|
||||
# Deleting field 'OrderItem.report_comments'
|
||||
db.delete_column('shoppingcart_orderitem', 'report_comments')
|
||||
|
||||
|
||||
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'})
|
||||
},
|
||||
'shoppingcart.certificateitem': {
|
||||
'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.order': {
|
||||
'Meta': {'object_name': 'Order'},
|
||||
'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
|
||||
'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
|
||||
'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
|
||||
'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
|
||||
'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.orderitem': {
|
||||
'Meta': {'object_name': 'OrderItem'},
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}),
|
||||
'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}),
|
||||
'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.paidcourseregistration': {
|
||||
'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.paidcourseregistrationannotation': {
|
||||
'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'},
|
||||
'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'student.courseenrollment': {
|
||||
'Meta': {'ordering': "('user', 'course_id')", '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'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['shoppingcart']
|
||||
@@ -0,0 +1,131 @@
|
||||
# -*- 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 'Order.refunded_time'
|
||||
db.add_column('shoppingcart_order', 'refunded_time',
|
||||
self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'OrderItem.refund_requested_time'
|
||||
db.add_column('shoppingcart_orderitem', 'refund_requested_time',
|
||||
self.gf('django.db.models.fields.DateTimeField')(null=True),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'Order.refunded_time'
|
||||
db.delete_column('shoppingcart_order', 'refunded_time')
|
||||
|
||||
# Deleting field 'OrderItem.refund_requested_time'
|
||||
db.delete_column('shoppingcart_orderitem', 'refund_requested_time')
|
||||
|
||||
|
||||
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'})
|
||||
},
|
||||
'shoppingcart.certificateitem': {
|
||||
'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.order': {
|
||||
'Meta': {'object_name': 'Order'},
|
||||
'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
|
||||
'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
|
||||
'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
|
||||
'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
|
||||
'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.orderitem': {
|
||||
'Meta': {'object_name': 'OrderItem'},
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}),
|
||||
'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}),
|
||||
'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.paidcourseregistration': {
|
||||
'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.paidcourseregistrationannotation': {
|
||||
'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'},
|
||||
'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'student.courseenrollment': {
|
||||
'Meta': {'ordering': "('user', 'course_id')", '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'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['shoppingcart']
|
||||
@@ -2,6 +2,7 @@ from datetime import datetime
|
||||
import pytz
|
||||
import logging
|
||||
import smtplib
|
||||
import unicodecsv
|
||||
|
||||
from model_utils.managers import InheritanceManager
|
||||
from collections import namedtuple
|
||||
@@ -53,6 +54,7 @@ class Order(models.Model):
|
||||
currency = models.CharField(default="usd", max_length=8) # lower case ISO currency codes
|
||||
status = models.CharField(max_length=32, default='cart', choices=ORDER_STATUSES)
|
||||
purchase_time = models.DateTimeField(null=True, blank=True)
|
||||
refunded_time = models.DateTimeField(null=True, blank=True)
|
||||
# Now we store data needed to generate a reasonable receipt
|
||||
# These fields only make sense after the purchase
|
||||
bill_to_first = models.CharField(max_length=64, blank=True)
|
||||
@@ -207,6 +209,9 @@ class OrderItem(models.Model):
|
||||
line_desc = models.CharField(default="Misc. Item", max_length=1024)
|
||||
currency = models.CharField(default="usd", max_length=8) # lower case ISO currency codes
|
||||
fulfilled_time = models.DateTimeField(null=True)
|
||||
refund_requested_time = models.DateTimeField(null=True)
|
||||
# general purpose field, not user-visible. Used for reporting
|
||||
report_comments = models.TextField(default="")
|
||||
|
||||
@property
|
||||
def line_cost(self):
|
||||
@@ -254,6 +259,66 @@ class OrderItem(models.Model):
|
||||
"""
|
||||
return self.pk_with_subclass, set([])
|
||||
|
||||
@classmethod
|
||||
def purchased_items_btw_dates(cls, start_date, end_date):
|
||||
"""
|
||||
Returns a QuerySet of the purchased items between start_date and end_date inclusive.
|
||||
"""
|
||||
return cls.objects.filter(
|
||||
status="purchased",
|
||||
fulfilled_time__gte=start_date,
|
||||
fulfilled_time__lt=end_date,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def csv_purchase_report_btw_dates(cls, filelike, start_date, end_date):
|
||||
"""
|
||||
Outputs a CSV report into "filelike" (a file-like python object, such as an actual file, an HttpRequest,
|
||||
or sys.stdout) of purchased items between start_date and end_date inclusive.
|
||||
Opening and closing filelike (if applicable) should be taken care of by the caller
|
||||
"""
|
||||
items = cls.purchased_items_btw_dates(start_date, end_date).order_by("fulfilled_time")
|
||||
|
||||
writer = unicodecsv.writer(filelike, encoding="utf-8")
|
||||
writer.writerow(OrderItem.csv_report_header_row())
|
||||
|
||||
for item in items:
|
||||
writer.writerow(item.csv_report_row)
|
||||
|
||||
@classmethod
|
||||
def csv_report_header_row(cls):
|
||||
"""
|
||||
Returns the "header" row for a csv report of purchases
|
||||
"""
|
||||
return [
|
||||
"Purchase Time",
|
||||
"Order ID",
|
||||
"Status",
|
||||
"Quantity",
|
||||
"Unit Cost",
|
||||
"Total Cost",
|
||||
"Currency",
|
||||
"Description",
|
||||
"Comments"
|
||||
]
|
||||
|
||||
@property
|
||||
def csv_report_row(self):
|
||||
"""
|
||||
Returns an array which can be fed into csv.writer to write out one csv row
|
||||
"""
|
||||
return [
|
||||
self.fulfilled_time,
|
||||
self.order_id, # pylint: disable=no-member
|
||||
self.status,
|
||||
self.qty,
|
||||
self.unit_cost,
|
||||
self.line_cost,
|
||||
self.currency,
|
||||
self.line_desc,
|
||||
self.report_comments,
|
||||
]
|
||||
|
||||
@property
|
||||
def pk_with_subclass(self):
|
||||
"""
|
||||
@@ -345,13 +410,13 @@ class PaidCourseRegistration(OrderItem):
|
||||
|
||||
item, created = cls.objects.get_or_create(order=order, user=order.user, course_id=course_id)
|
||||
item.status = order.status
|
||||
|
||||
item.mode = course_mode.slug
|
||||
item.qty = 1
|
||||
item.unit_cost = cost
|
||||
item.line_desc = 'Registration for Course: {0}'.format(course.display_name_with_default)
|
||||
item.currency = currency
|
||||
order.currency = currency
|
||||
item.report_comments = item.csv_report_comments
|
||||
order.save()
|
||||
item.save()
|
||||
log.info("User {} added course registration {} to cart: order {}"
|
||||
@@ -391,6 +456,31 @@ class PaidCourseRegistration(OrderItem):
|
||||
|
||||
return self.pk_with_subclass, set([notification])
|
||||
|
||||
@property
|
||||
def csv_report_comments(self):
|
||||
"""
|
||||
Tries to fetch an annotation associated with the course_id from the database. If not found, returns u"".
|
||||
Otherwise returns the annotation
|
||||
"""
|
||||
try:
|
||||
return PaidCourseRegistrationAnnotation.objects.get(course_id=self.course_id).annotation
|
||||
except PaidCourseRegistrationAnnotation.DoesNotExist:
|
||||
return u""
|
||||
|
||||
|
||||
class PaidCourseRegistrationAnnotation(models.Model):
|
||||
"""
|
||||
A model that maps course_id to an additional annotation. This is specifically needed because when Stanford
|
||||
generates report for the paid courses, each report item must contain the payment account associated with a course.
|
||||
And unfortunately we didn't have the concept of a "SKU" or stock item where we could keep this association,
|
||||
so this is to retrofit it.
|
||||
"""
|
||||
course_id = models.CharField(unique=True, max_length=128, db_index=True)
|
||||
annotation = models.TextField(null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"{} : {}".format(self.course_id, self.annotation)
|
||||
|
||||
|
||||
class CertificateItem(OrderItem):
|
||||
"""
|
||||
@@ -421,7 +511,10 @@ class CertificateItem(OrderItem):
|
||||
log.error("Matching CertificateItem not found while trying to refund. User %s, Course %s", course_enrollment.user, course_enrollment.course_id)
|
||||
return
|
||||
target_cert.status = 'refunded'
|
||||
target_cert.refund_requested_time = datetime.now(pytz.utc)
|
||||
target_cert.save()
|
||||
target_cert.order.status = 'refunded'
|
||||
target_cert.order.save()
|
||||
|
||||
order_number = target_cert.order_id
|
||||
# send billing an email so they can handle refunding
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
Tests for the Shopping Cart Models
|
||||
"""
|
||||
import smtplib
|
||||
import StringIO
|
||||
from textwrap import dedent
|
||||
from boto.exception import BotoServerError # this is a super-class of SESError and catches connection errors
|
||||
|
||||
from mock import patch, MagicMock
|
||||
@@ -15,7 +17,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
from shoppingcart.models import (Order, OrderItem, CertificateItem, InvalidCartItem, PaidCourseRegistration,
|
||||
OrderItemSubclassPK)
|
||||
OrderItemSubclassPK, PaidCourseRegistrationAnnotation)
|
||||
from student.tests.factories import UserFactory
|
||||
from student.models import CourseEnrollment
|
||||
from course_modes.models import CourseMode
|
||||
@@ -321,6 +323,87 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase):
|
||||
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id))
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class PurchaseReportTest(ModuleStoreTestCase):
|
||||
|
||||
FIVE_MINS = datetime.timedelta(minutes=5)
|
||||
TEST_ANNOTATION = u'Ba\xfc\u5305'
|
||||
|
||||
def setUp(self):
|
||||
self.user = UserFactory.create()
|
||||
self.course_id = "MITx/999/Robot_Super_Course"
|
||||
self.cost = 40
|
||||
self.course = CourseFactory.create(org='MITx', number='999', display_name=u'Robot Super Course')
|
||||
course_mode = CourseMode(course_id=self.course_id,
|
||||
mode_slug="honor",
|
||||
mode_display_name="honor cert",
|
||||
min_price=self.cost)
|
||||
course_mode.save()
|
||||
course_mode2 = CourseMode(course_id=self.course_id,
|
||||
mode_slug="verified",
|
||||
mode_display_name="verified cert",
|
||||
min_price=self.cost)
|
||||
course_mode2.save()
|
||||
self.annotation = PaidCourseRegistrationAnnotation(course_id=self.course_id, annotation=self.TEST_ANNOTATION)
|
||||
self.annotation.save()
|
||||
self.cart = Order.get_cart_for_user(self.user)
|
||||
self.reg = PaidCourseRegistration.add_to_order(self.cart, self.course_id)
|
||||
self.cert_item = CertificateItem.add_to_order(self.cart, self.course_id, self.cost, 'verified')
|
||||
self.cart.purchase()
|
||||
self.now = datetime.datetime.now(pytz.UTC)
|
||||
|
||||
def test_purchased_items_btw_dates(self):
|
||||
purchases = OrderItem.purchased_items_btw_dates(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
|
||||
self.assertEqual(len(purchases), 2)
|
||||
self.assertIn(self.reg.orderitem_ptr, purchases)
|
||||
self.assertIn(self.cert_item.orderitem_ptr, purchases)
|
||||
no_purchases = OrderItem.purchased_items_btw_dates(self.now + self.FIVE_MINS,
|
||||
self.now + self.FIVE_MINS + self.FIVE_MINS)
|
||||
self.assertFalse(no_purchases)
|
||||
|
||||
test_time = datetime.datetime.now(pytz.UTC)
|
||||
|
||||
CORRECT_CSV = dedent("""
|
||||
Purchase Time,Order ID,Status,Quantity,Unit Cost,Total Cost,Currency,Description,Comments
|
||||
{time_str},1,purchased,1,40,40,usd,Registration for Course: Robot Super Course,Ba\xc3\xbc\xe5\x8c\x85
|
||||
{time_str},1,purchased,1,40,40,usd,"Certificate of Achievement, verified cert for course Robot Super Course",
|
||||
""".format(time_str=str(test_time)))
|
||||
|
||||
def test_purchased_csv(self):
|
||||
"""
|
||||
Tests that a generated purchase report CSV is as we expect
|
||||
"""
|
||||
# coerce the purchase times to self.test_time so that the test can match.
|
||||
# It's pretty hard to patch datetime.datetime b/c it's a python built-in, which is immutable, so we
|
||||
# make the times match this way
|
||||
for item in OrderItem.purchased_items_btw_dates(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS):
|
||||
item.fulfilled_time = self.test_time
|
||||
item.save()
|
||||
|
||||
# add annotation to the
|
||||
csv_file = StringIO.StringIO()
|
||||
OrderItem.csv_purchase_report_btw_dates(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
|
||||
csv = csv_file.getvalue()
|
||||
csv_file.close()
|
||||
# Using excel mode csv, which automatically ends lines with \r\n, so need to convert to \n
|
||||
self.assertEqual(csv.replace('\r\n', '\n').strip(), self.CORRECT_CSV.strip())
|
||||
|
||||
def test_csv_report_no_annotation(self):
|
||||
"""
|
||||
Fill in gap in test coverage. csv_report_comments for PaidCourseRegistration instance with no
|
||||
matching annotation
|
||||
"""
|
||||
# delete the matching annotation
|
||||
self.annotation.delete()
|
||||
self.assertEqual(u"", self.reg.csv_report_comments)
|
||||
|
||||
def test_paidcourseregistrationannotation_unicode(self):
|
||||
"""
|
||||
Fill in gap in test coverage. __unicode__ method of PaidCourseRegistrationAnnotation
|
||||
"""
|
||||
self.assertEqual(unicode(self.annotation), u'{} : {}'.format(self.course_id, self.TEST_ANNOTATION))
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class CertificateItemTest(ModuleStoreTestCase):
|
||||
"""
|
||||
@@ -373,6 +456,8 @@ class CertificateItemTest(ModuleStoreTestCase):
|
||||
CourseEnrollment.unenroll(self.user, self.course_id)
|
||||
target_certs = CertificateItem.objects.filter(course_id=self.course_id, user_id=self.user, status='refunded', mode='verified')
|
||||
self.assertTrue(target_certs[0])
|
||||
self.assertTrue(target_certs[0].refund_requested_time)
|
||||
self.assertEquals(target_certs[0].order.status, 'refunded')
|
||||
|
||||
def test_refund_cert_callback_before_expiration(self):
|
||||
# If the expiration date has not yet passed on a verified mode, the user can be refunded
|
||||
@@ -395,6 +480,8 @@ class CertificateItemTest(ModuleStoreTestCase):
|
||||
CourseEnrollment.unenroll(self.user, course_id)
|
||||
target_certs = CertificateItem.objects.filter(course_id=course_id, user_id=self.user, status='refunded', mode='verified')
|
||||
self.assertTrue(target_certs[0])
|
||||
self.assertTrue(target_certs[0].refund_requested_time)
|
||||
self.assertEquals(target_certs[0].order.status, 'refunded')
|
||||
|
||||
@patch('shoppingcart.models.log.error')
|
||||
def test_refund_cert_callback_before_expiration_email_error(self, error_logger):
|
||||
|
||||
@@ -3,23 +3,23 @@ Tests for Shopping Cart views
|
||||
"""
|
||||
from urlparse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
from shoppingcart.views import add_course_to_cart
|
||||
from shoppingcart.models import Order, OrderItem, CertificateItem, InvalidCartItem, PaidCourseRegistration
|
||||
from shoppingcart.views import _can_download_report, _get_date_from_str
|
||||
from shoppingcart.models import Order, CertificateItem, PaidCourseRegistration, OrderItem
|
||||
from student.tests.factories import UserFactory
|
||||
from student.models import CourseEnrollment
|
||||
from course_modes.models import CourseMode
|
||||
from ..exceptions import PurchasedCallbackException
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from shoppingcart.processors import render_purchase_form_html, process_postpay_callback
|
||||
from shoppingcart.processors import render_purchase_form_html
|
||||
from mock import patch, Mock
|
||||
|
||||
|
||||
@@ -232,3 +232,143 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
((template, _context), _tmp) = render_mock.call_args
|
||||
self.assertEqual(template, cert_item.single_item_receipt_template)
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class CSVReportViewsTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Test suite for CSV Purchase Reporting
|
||||
"""
|
||||
def setUp(self):
|
||||
self.user = UserFactory.create()
|
||||
self.user.set_password('password')
|
||||
self.user.save()
|
||||
self.course_id = "MITx/999/Robot_Super_Course"
|
||||
self.cost = 40
|
||||
self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
|
||||
self.course_mode = CourseMode(course_id=self.course_id,
|
||||
mode_slug="honor",
|
||||
mode_display_name="honor cert",
|
||||
min_price=self.cost)
|
||||
self.course_mode.save()
|
||||
self.verified_course_id = 'org/test/Test_Course'
|
||||
CourseFactory.create(org='org', number='test', run='course1', display_name='Test Course')
|
||||
self.cart = Order.get_cart_for_user(self.user)
|
||||
self.dl_grp = Group(name=settings.PAYMENT_REPORT_GENERATOR_GROUP)
|
||||
self.dl_grp.save()
|
||||
|
||||
def login_user(self):
|
||||
"""
|
||||
Helper fn to login self.user
|
||||
"""
|
||||
self.client.login(username=self.user.username, password="password")
|
||||
|
||||
def add_to_download_group(self, user):
|
||||
"""
|
||||
Helper fn to add self.user to group that's allowed to download report CSV
|
||||
"""
|
||||
user.groups.add(self.dl_grp)
|
||||
|
||||
def test_report_csv_no_access(self):
|
||||
self.login_user()
|
||||
response = self.client.get(reverse('payment_csv_report'))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_report_csv_bad_method(self):
|
||||
self.login_user()
|
||||
self.add_to_download_group(self.user)
|
||||
response = self.client.put(reverse('payment_csv_report'))
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@patch('shoppingcart.views.render_to_response', render_mock)
|
||||
def test_report_csv_get(self):
|
||||
self.login_user()
|
||||
self.add_to_download_group(self.user)
|
||||
response = self.client.get(reverse('payment_csv_report'))
|
||||
|
||||
((template, context), unused_kwargs) = render_mock.call_args
|
||||
self.assertEqual(template, 'shoppingcart/download_report.html')
|
||||
self.assertFalse(context['total_count_error'])
|
||||
self.assertFalse(context['date_fmt_error'])
|
||||
self.assertIn(_("Download Purchase Report"), response.content)
|
||||
|
||||
@patch('shoppingcart.views.render_to_response', render_mock)
|
||||
def test_report_csv_bad_date(self):
|
||||
self.login_user()
|
||||
self.add_to_download_group(self.user)
|
||||
response = self.client.post(reverse('payment_csv_report'), {'start_date': 'BAD', 'end_date': 'BAD'})
|
||||
|
||||
((template, context), unused_kwargs) = render_mock.call_args
|
||||
self.assertEqual(template, 'shoppingcart/download_report.html')
|
||||
self.assertFalse(context['total_count_error'])
|
||||
self.assertTrue(context['date_fmt_error'])
|
||||
self.assertIn(_("There was an error in your date input. It should be formatted as YYYY-MM-DD"),
|
||||
response.content)
|
||||
|
||||
@patch('shoppingcart.views.render_to_response', render_mock)
|
||||
@override_settings(PAYMENT_REPORT_MAX_ITEMS=0)
|
||||
def test_report_csv_too_long(self):
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_id)
|
||||
self.cart.purchase()
|
||||
self.login_user()
|
||||
self.add_to_download_group(self.user)
|
||||
response = self.client.post(reverse('payment_csv_report'), {'start_date': '1970-01-01',
|
||||
'end_date': '2100-01-01'})
|
||||
|
||||
((template, context), unused_kwargs) = render_mock.call_args
|
||||
self.assertEqual(template, 'shoppingcart/download_report.html')
|
||||
self.assertTrue(context['total_count_error'])
|
||||
self.assertFalse(context['date_fmt_error'])
|
||||
self.assertIn(_("There are too many results in your report.") + " (>0)", response.content)
|
||||
|
||||
# just going to ignored the date in this test, since we already deal with date testing
|
||||
# in test_models.py
|
||||
CORRECT_CSV_NO_DATE = ",1,purchased,1,40,40,usd,Registration for Course: Robot Super Course,"
|
||||
|
||||
def test_report_csv(self):
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_id)
|
||||
self.cart.purchase()
|
||||
self.login_user()
|
||||
self.add_to_download_group(self.user)
|
||||
response = self.client.post(reverse('payment_csv_report'), {'start_date': '1970-01-01',
|
||||
'end_date': '2100-01-01'})
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
self.assertIn(",".join(OrderItem.csv_report_header_row()), response.content)
|
||||
self.assertIn(self.CORRECT_CSV_NO_DATE, response.content)
|
||||
|
||||
|
||||
class UtilFnsTest(TestCase):
|
||||
"""
|
||||
Tests for utility functions in views.py
|
||||
"""
|
||||
def setUp(self):
|
||||
self.user = UserFactory.create()
|
||||
|
||||
def test_can_download_report_no_group(self):
|
||||
"""
|
||||
Group controlling perms is not present
|
||||
"""
|
||||
self.assertFalse(_can_download_report(self.user))
|
||||
|
||||
def test_can_download_report_not_member(self):
|
||||
"""
|
||||
User is not part of group controlling perms
|
||||
"""
|
||||
Group(name=settings.PAYMENT_REPORT_GENERATOR_GROUP).save()
|
||||
self.assertFalse(_can_download_report(self.user))
|
||||
|
||||
def test_can_download_report(self):
|
||||
"""
|
||||
User is part of group controlling perms
|
||||
"""
|
||||
grp = Group(name=settings.PAYMENT_REPORT_GENERATOR_GROUP)
|
||||
grp.save()
|
||||
self.user.groups.add(grp)
|
||||
self.assertTrue(_can_download_report(self.user))
|
||||
|
||||
def test_get_date_from_str(self):
|
||||
test_str = "2013-10-01"
|
||||
date = _get_date_from_str(test_str)
|
||||
self.assertEqual(2013, date.year)
|
||||
self.assertEqual(10, date.month)
|
||||
self.assertEqual(1, date.day)
|
||||
|
||||
@@ -12,6 +12,7 @@ if settings.MITX_FEATURES['ENABLE_SHOPPING_CART']:
|
||||
url(r'^clear/$', 'clear_cart'),
|
||||
url(r'^remove_item/$', 'remove_item'),
|
||||
url(r'^add/course/(?P<course_id>[^/]+/[^/]+/[^/]+)/$', 'add_course_to_cart', name='add_course_to_cart'),
|
||||
url(r'^csv_report/$', 'csv_report', name='payment_csv_report'),
|
||||
)
|
||||
|
||||
if settings.MITX_FEATURES.get('ENABLE_PAYMENT_FAKE'):
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import logging
|
||||
import datetime
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.http import (HttpResponse, HttpResponseRedirect, HttpResponseNotFound,
|
||||
HttpResponseBadRequest, HttpResponseForbidden, Http404)
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -121,3 +125,73 @@ def show_receipt(request, ordernum):
|
||||
context.update(order_items[0].single_item_receipt_context)
|
||||
|
||||
return render_to_response(receipt_template, context)
|
||||
|
||||
|
||||
def _can_download_report(user):
|
||||
"""
|
||||
Tests if the user can download the payments report, based on membership in a group whose name is determined
|
||||
in settings. If the group does not exist, denies all access
|
||||
"""
|
||||
try:
|
||||
access_group = Group.objects.get(name=settings.PAYMENT_REPORT_GENERATOR_GROUP)
|
||||
except Group.DoesNotExist:
|
||||
return False
|
||||
return access_group in user.groups.all()
|
||||
|
||||
|
||||
def _get_date_from_str(date_input):
|
||||
"""
|
||||
Gets date from the date input string. Lets the ValueError raised by invalid strings be processed by the caller
|
||||
"""
|
||||
return datetime.datetime.strptime(date_input.strip(), "%Y-%m-%d").replace(tzinfo=pytz.UTC)
|
||||
|
||||
|
||||
def _render_report_form(start_str, end_str, total_count_error=False, date_fmt_error=False):
|
||||
"""
|
||||
Helper function that renders the purchase form. Reduces repetition
|
||||
"""
|
||||
context = {
|
||||
'total_count_error': total_count_error,
|
||||
'date_fmt_error': date_fmt_error,
|
||||
'start_date': start_str,
|
||||
'end_date': end_str,
|
||||
}
|
||||
return render_to_response('shoppingcart/download_report.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def csv_report(request):
|
||||
"""
|
||||
Downloads csv reporting of orderitems
|
||||
"""
|
||||
if not _can_download_report(request.user):
|
||||
return HttpResponseForbidden(_('You do not have permission to view this page.'))
|
||||
|
||||
if request.method == 'POST':
|
||||
start_str = request.POST.get('start_date', '')
|
||||
end_str = request.POST.get('end_date', '')
|
||||
try:
|
||||
start_date = _get_date_from_str(start_str)
|
||||
end_date = _get_date_from_str(end_str) + datetime.timedelta(days=1)
|
||||
except ValueError:
|
||||
# Error case: there was a badly formatted user-input date string
|
||||
return _render_report_form(start_str, end_str, date_fmt_error=True)
|
||||
|
||||
items = OrderItem.purchased_items_btw_dates(start_date, end_date)
|
||||
if items.count() > settings.PAYMENT_REPORT_MAX_ITEMS:
|
||||
# Error case: too many items would be generated in the report and we're at risk of timeout
|
||||
return _render_report_form(start_str, end_str, total_count_error=True)
|
||||
|
||||
response = HttpResponse(mimetype='text/csv')
|
||||
filename = "purchases_report_{}.csv".format(datetime.datetime.now(pytz.UTC).strftime("%Y-%m-%d-%H-%M-%S"))
|
||||
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
|
||||
OrderItem.csv_purchase_report_btw_dates(response, start_date, end_date)
|
||||
return response
|
||||
|
||||
elif request.method == 'GET':
|
||||
end_date = datetime.datetime.now(pytz.UTC)
|
||||
start_date = end_date - datetime.timedelta(days=30)
|
||||
return _render_report_form(start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d"))
|
||||
|
||||
else:
|
||||
return HttpResponseBadRequest("HTTP Method Not Supported")
|
||||
|
||||
@@ -166,6 +166,10 @@ PAYMENT_SUPPORT_EMAIL = ENV_TOKENS.get('PAYMENT_SUPPORT_EMAIL', PAYMENT_SUPPORT_
|
||||
PAID_COURSE_REGISTRATION_CURRENCY = ENV_TOKENS.get('PAID_COURSE_REGISTRATION_CURRENCY',
|
||||
PAID_COURSE_REGISTRATION_CURRENCY)
|
||||
|
||||
# Payment Report Settings
|
||||
PAYMENT_REPORT_GENERATOR_GROUP = ENV_TOKENS.get('PAYMENT_REPORT_GENERATOR_GROUP', PAYMENT_REPORT_GENERATOR_GROUP)
|
||||
PAYMENT_REPORT_MAX_ITEMS = ENV_TOKENS.get('PAYMENT_REPORT_MAX_ITEMS', PAYMENT_REPORT_MAX_ITEMS)
|
||||
|
||||
# Bulk Email overrides
|
||||
BULK_EMAIL_DEFAULT_FROM_EMAIL = ENV_TOKENS.get('BULK_EMAIL_DEFAULT_FROM_EMAIL', BULK_EMAIL_DEFAULT_FROM_EMAIL)
|
||||
BULK_EMAIL_EMAILS_PER_TASK = ENV_TOKENS.get('BULK_EMAIL_EMAILS_PER_TASK', BULK_EMAIL_EMAILS_PER_TASK)
|
||||
@@ -261,7 +265,13 @@ CC_PROCESSOR = AUTH_TOKENS.get('CC_PROCESSOR', CC_PROCESSOR)
|
||||
SECRET_KEY = AUTH_TOKENS['SECRET_KEY']
|
||||
|
||||
AWS_ACCESS_KEY_ID = AUTH_TOKENS["AWS_ACCESS_KEY_ID"]
|
||||
if AWS_ACCESS_KEY_ID == "":
|
||||
AWS_ACCESS_KEY_ID = None
|
||||
|
||||
AWS_SECRET_ACCESS_KEY = AUTH_TOKENS["AWS_SECRET_ACCESS_KEY"]
|
||||
if AWS_SECRET_ACCESS_KEY == "":
|
||||
AWS_SECRET_ACCESS_KEY = None
|
||||
|
||||
AWS_STORAGE_BUCKET_NAME = AUTH_TOKENS.get('AWS_STORAGE_BUCKET_NAME', 'edxuploads')
|
||||
|
||||
DATABASES = AUTH_TOKENS['DATABASES']
|
||||
@@ -280,12 +290,6 @@ OPEN_ENDED_GRADING_INTERFACE = AUTH_TOKENS.get('OPEN_ENDED_GRADING_INTERFACE',
|
||||
EMAIL_HOST_USER = AUTH_TOKENS.get('EMAIL_HOST_USER', '') # django default is ''
|
||||
EMAIL_HOST_PASSWORD = AUTH_TOKENS.get('EMAIL_HOST_PASSWORD', '') # django default is ''
|
||||
|
||||
PEARSON_TEST_USER = "pearsontest"
|
||||
PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD")
|
||||
|
||||
# Pearson hash for import/export
|
||||
PEARSON = AUTH_TOKENS.get("PEARSON")
|
||||
|
||||
# Datadog for events!
|
||||
DATADOG = AUTH_TOKENS.get("DATADOG", {})
|
||||
DATADOG.update(ENV_TOKENS.get("DATADOG", {}))
|
||||
|
||||
@@ -523,11 +523,6 @@ WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False
|
||||
WIKI_LINK_LIVE_LOOKUPS = False
|
||||
WIKI_LINK_DEFAULT_LEVEL = 2
|
||||
|
||||
################################# Pearson TestCenter config ################
|
||||
|
||||
PEARSONVUE_SIGNINPAGE_URL = "https://www1.pearsonvue.com/testtaker/signin/SignInPage/EDX"
|
||||
# TESTCENTER_ACCOMMODATION_REQUEST_EMAIL = "exam-help@example.com"
|
||||
|
||||
##### Feedback submission mechanism #####
|
||||
FEEDBACK_SUBMISSION_EMAIL = None
|
||||
|
||||
@@ -550,6 +545,12 @@ CC_PROCESSOR = {
|
||||
}
|
||||
# Setting for PAID_COURSE_REGISTRATION, DOES NOT AFFECT VERIFIED STUDENTS
|
||||
PAID_COURSE_REGISTRATION_CURRENCY = ['usd', '$']
|
||||
|
||||
# Members of this group are allowed to generate payment reports
|
||||
PAYMENT_REPORT_GENERATOR_GROUP = 'shoppingcart_report_access'
|
||||
# Maximum number of rows the report can contain
|
||||
PAYMENT_REPORT_MAX_ITEMS = 10000
|
||||
|
||||
################################# open ended grading config #####################
|
||||
|
||||
#By setting up the default settings with an incorrect user name and password,
|
||||
@@ -909,6 +910,8 @@ BULK_EMAIL_LOG_SENT_EMAILS = False
|
||||
# parallel, and what the SES rate is.
|
||||
BULK_EMAIL_RETRY_DELAY_BETWEEN_SENDS = 0.02
|
||||
|
||||
|
||||
|
||||
################################### APPS ######################################
|
||||
INSTALLED_APPS = (
|
||||
# Standard ones that are always installed...
|
||||
|
||||
@@ -254,9 +254,6 @@ MITX_FEATURES['RESTRICT_ENROLL_BY_REG_METHOD'] = True
|
||||
|
||||
PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
|
||||
|
||||
########################## PEARSON TESTING ###########################
|
||||
MITX_FEATURES['ENABLE_PEARSON_LOGIN'] = False
|
||||
|
||||
########################## ANALYTICS TESTING ########################
|
||||
|
||||
ANALYTICS_SERVER_URL = "http://127.0.0.1:9000/"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<input type="text" id="calculator_input" tabindex="-1" />
|
||||
<div class="help-wrapper">
|
||||
<a id="calculator_hint" href="#" role="button" aria-haspopup="true" aria-controls="calculator_input_help" aria-expanded="false" tabindex="-1">Hints</a>
|
||||
<dl id="calculator_input_help" class="help"></dl>
|
||||
<div id="calculator_input_help" class="help" role="tooltip" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
<input id="calculator_button" type="submit" title="Calculate" arial-label="Calculate" value="=" tabindex="-1" />
|
||||
|
||||
@@ -55,11 +55,26 @@ describe 'Calculator', ->
|
||||
it 'show the help overlay', ->
|
||||
@calculator.helpShow()
|
||||
expect($('.help')).toHaveClass('shown')
|
||||
expect($('.help')).toHaveAttr('aria-hidden', 'false')
|
||||
|
||||
describe 'helpHide', ->
|
||||
it 'show the help overlay', ->
|
||||
@calculator.helpHide()
|
||||
expect($('.help')).not.toHaveClass('shown')
|
||||
expect($('.help')).toHaveAttr('aria-hidden', 'true')
|
||||
|
||||
describe 'handleKeyDown', ->
|
||||
it 'on pressing Esc the hint becomes hidden', ->
|
||||
@calculator.helpShow()
|
||||
e = jQuery.Event('keydown', { which: 27 } );
|
||||
$(document).trigger(e);
|
||||
expect($('.help')).not.toHaveClass 'shown'
|
||||
|
||||
it 'On pressing other buttons the hint continue to show', ->
|
||||
@calculator.helpShow()
|
||||
e = jQuery.Event('keydown', { which: 32 } );
|
||||
$(document).trigger(e);
|
||||
expect($('.help')).toHaveClass 'shown'
|
||||
|
||||
describe 'calculate', ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -10,6 +10,9 @@ class @Calculator
|
||||
)
|
||||
.click (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$(document).keydown $.proxy(@handleKeyDown, @)
|
||||
|
||||
$('div.help-wrapper')
|
||||
.focusin($.proxy @helpOnFocus, @)
|
||||
.focusout($.proxy @helpOnBlur, @)
|
||||
@@ -24,14 +27,14 @@ class @Calculator
|
||||
$('div.calc-main').toggleClass 'open'
|
||||
if $calc.hasClass('closed')
|
||||
$calcWrapper
|
||||
.find('input, a, dt, dd')
|
||||
.find('input, a')
|
||||
.attr 'tabindex', -1
|
||||
else
|
||||
text = gettext('Close Calculator')
|
||||
isExpanded = true
|
||||
|
||||
$calcWrapper
|
||||
.find('input, a, dt, dd')
|
||||
.find('input, a,')
|
||||
.attr 'tabindex', 0
|
||||
# TODO: Investigate why doing this without the timeout causes it to jump
|
||||
# down to the bottom of the page. I suspect it's because it's putting the
|
||||
@@ -57,13 +60,21 @@ class @Calculator
|
||||
@helpHide()
|
||||
|
||||
helpShow: ->
|
||||
$('.help').addClass 'shown'
|
||||
$('#calculator_hint').attr 'aria-expanded', true
|
||||
$('.help')
|
||||
.addClass('shown')
|
||||
.attr('aria-hidden', false)
|
||||
|
||||
helpHide: ->
|
||||
if not @isFocusedHelp
|
||||
$('.help').removeClass 'shown'
|
||||
$('#calculator_hint').attr 'aria-expanded', false
|
||||
$('.help')
|
||||
.removeClass('shown')
|
||||
.attr('aria-hidden', true)
|
||||
|
||||
handleKeyDown: (e) ->
|
||||
ESC = 27
|
||||
if e.which is ESC and $('.help').hasClass 'shown'
|
||||
@isFocusedHelp = false
|
||||
@helpHide()
|
||||
|
||||
calculate: ->
|
||||
$.getWithPrefix '/calculate', { equation: $('#calculator_input').val() }, (data) ->
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
(function($){
|
||||
$.fn.extend({
|
||||
/*
|
||||
* leanModal prepares an element to be a modal dialog. Call it once on the
|
||||
* element that launches the dialog, when the page is ready. This function
|
||||
* will add a .click() handler that properly opens the dialog.
|
||||
*
|
||||
* The launching element must:
|
||||
* - be an <a> element, not a button,
|
||||
* - have an href= attribute identifying the id of the dialog element,
|
||||
* - have rel='leanModal'.
|
||||
*/
|
||||
leanModal: function(options) {
|
||||
var defaults = {
|
||||
top: 100,
|
||||
@@ -13,7 +23,7 @@
|
||||
$("body").append(overlay);
|
||||
}
|
||||
|
||||
options = $.extend(defaults, options);
|
||||
options = $.extend(defaults, options);
|
||||
|
||||
return this.each(function() {
|
||||
var o = options;
|
||||
@@ -23,7 +33,7 @@
|
||||
$(".modal").hide();
|
||||
|
||||
var modal_id = $(this).attr("href");
|
||||
|
||||
|
||||
if ($(modal_id).hasClass("video-modal")) {
|
||||
//Video modals need to be cloned before being presented as a modal
|
||||
//This is because actions on the video get recorded in the history.
|
||||
@@ -34,13 +44,12 @@
|
||||
modal_id = '#modal_clone';
|
||||
}
|
||||
|
||||
|
||||
$("#lean_overlay").click(function() {
|
||||
close_modal(modal_id);
|
||||
$("#lean_overlay").click(function(e) {
|
||||
close_modal(modal_id, e);
|
||||
});
|
||||
|
||||
$(o.closeButton).click(function() {
|
||||
close_modal(modal_id);
|
||||
$(o.closeButton).click(function(e) {
|
||||
close_modal(modal_id, e);
|
||||
});
|
||||
|
||||
var modal_height = $(modal_id).outerHeight();
|
||||
@@ -72,34 +81,30 @@
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
e.preventDefault();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function close_modal(modal_id){
|
||||
function close_modal(modal_id, e) {
|
||||
$("#lean_overlay").fadeOut(200);
|
||||
$('iframe', modal_id).attr('src', '');
|
||||
$(modal_id).css({ 'display' : 'none' });
|
||||
if (modal_id == '#modal_clone') {
|
||||
$(modal_id).remove();
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function($) {
|
||||
$("a[rel*=leanModal]").each(function(){
|
||||
$(this).leanModal({ top : 120, overlay: 1, closeButton: ".close-modal", position: 'absolute' });
|
||||
embed = $($(this).attr('href')).find('iframe')
|
||||
if(embed.length > 0) {
|
||||
if(embed.attr('src').indexOf("?") > 0) {
|
||||
embed.data('src', embed.attr('src') + '&autoplay=1&rel=0');
|
||||
embed.attr('src', '');
|
||||
} else {
|
||||
embed.data('src', embed.attr('src') + '?autoplay=1&rel=0');
|
||||
embed.attr('src', '');
|
||||
}
|
||||
}
|
||||
});
|
||||
$(document).ready(function ($) {
|
||||
$("a[rel*=leanModal]").each(function () {
|
||||
$(this).leanModal({ top : 120, overlay: 1, closeButton: ".close-modal", position: 'absolute' });
|
||||
embed = $($(this).attr('href')).find('iframe')
|
||||
if (embed.length > 0 && embed.attr('src')) {
|
||||
var sep = (embed.attr('src').indexOf("?") > 0) ? '&' : '?';
|
||||
embed.data('src', embed.attr('src') + sep + 'autoplay=1&rel=0');
|
||||
embed.attr('src', '');
|
||||
}
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
$(document).ready(function () {
|
||||
$('#cheatsheetLink').click(function() {
|
||||
$('#cheatsheetModal').leanModal();
|
||||
});
|
||||
accessible_modal("#cheatsheetLink", "#cheatsheetModal .close-modal", "#cheatsheetModal", ".content-wrapper");
|
||||
});
|
||||
@@ -46,7 +46,6 @@
|
||||
@import 'multicourse/home';
|
||||
@import 'multicourse/dashboard';
|
||||
@import 'multicourse/account';
|
||||
@import 'multicourse/testcenter-register';
|
||||
@import 'multicourse/courses';
|
||||
@import 'multicourse/course_about';
|
||||
@import 'multicourse/jobs';
|
||||
|
||||
@@ -120,39 +120,48 @@ div.calc-main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
dl {
|
||||
.help {
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 3px #999;
|
||||
color: #333;
|
||||
opacity: 0;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
right: -40px;
|
||||
top: -122px;
|
||||
bottom: 57px;
|
||||
@include transition(none);
|
||||
width: 600px;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
|
||||
&.shown {
|
||||
height: auto;
|
||||
opacity: 1;
|
||||
overflow: visible;
|
||||
display: block;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
dt {
|
||||
clear: both;
|
||||
float: left;
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
dd {
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
p, p+p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 10px 0;
|
||||
|
||||
td, th {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.calc-postfixes {
|
||||
margin: 10px auto;
|
||||
|
||||
td, th {
|
||||
padding: 2px 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,8 +536,12 @@ section.wiki {
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border: 1px solid $danger-red;
|
||||
padding: ($baseline/2) ($baseline/2) 0 ($baseline/2);
|
||||
margin-bottom: ($baseline/2);
|
||||
|
||||
h1, p {
|
||||
color: #fff;
|
||||
color: $base-font-color;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
@@ -523,7 +523,7 @@
|
||||
.message-copy,
|
||||
.message-copy .copy {
|
||||
@extend %t-copy-sub1;
|
||||
margin: 0;
|
||||
margin: 2px 0 0 0;
|
||||
}
|
||||
|
||||
// CASE: expandable
|
||||
@@ -532,15 +532,18 @@
|
||||
.wrapper-tip {
|
||||
|
||||
.message-title, .message-copy {
|
||||
@include transition(color 0.25s ease-in-out 0);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-title .value, .message-copy {
|
||||
@include transition(color 0.25s ease-in-out 0s);
|
||||
}
|
||||
|
||||
// STATE: hover
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
||||
.message-title, .message-copy {
|
||||
.message-title .value, .message-copy, .ui-toggle-expansion {
|
||||
color: $link-color;
|
||||
}
|
||||
}
|
||||
@@ -555,6 +558,11 @@
|
||||
// STATE: is expanded
|
||||
&.is-expanded {
|
||||
|
||||
.ui-toggle-expansion {
|
||||
@include transform(rotate(0));
|
||||
@include transform-origin(50% 50%);
|
||||
}
|
||||
|
||||
.wrapper-extended {
|
||||
display: block;
|
||||
opacity: 1.0;
|
||||
@@ -573,6 +581,16 @@
|
||||
float: left;
|
||||
}
|
||||
|
||||
.ui-toggle-expansion {
|
||||
@include transition(all 0.25s ease-in-out 0s);
|
||||
@include transform(rotate(-90deg));
|
||||
@include transform-origin(50% 50%);
|
||||
@include font-size(21);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
|
||||
.message-copy {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@@ -1,790 +0,0 @@
|
||||
// Pearson VUE Test Center Registration
|
||||
// =====
|
||||
|
||||
.testcenter-register {
|
||||
@include clearfix;
|
||||
padding: 60px 0px 120px;
|
||||
|
||||
// reset - horrible, but necessary
|
||||
p, a, h1, h2, h3, h4, h5, h6 {
|
||||
font-family: $sans-serif !important;
|
||||
}
|
||||
|
||||
// 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);
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0, 0.1);
|
||||
}
|
||||
|
||||
form {
|
||||
border: 1px solid rgb(216, 223, 230);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0, 0.2);
|
||||
|
||||
.instructions, .note {
|
||||
margin: 0;
|
||||
padding: ($baseline*1.5) ($baseline*1.5) 0 ($baseline*1.5);
|
||||
font-size: 14px;
|
||||
color: tint($base-font-color, 20%);
|
||||
|
||||
strong {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.title, .indicator {
|
||||
color: $base-font-color;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border-bottom: 1px solid rgba(216, 223, 230, 0.50);
|
||||
padding: ($baseline*1.5);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
@include clearfix();
|
||||
padding: ($baseline*1.5);
|
||||
|
||||
button[type="submit"] {
|
||||
display: block;
|
||||
@include button(simple, $blue);
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 3px;
|
||||
font: bold 15px/1.6rem $sans-serif;
|
||||
letter-spacing: 0;
|
||||
padding: ($baseline*0.75) $baseline;
|
||||
text-align: center;
|
||||
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.action-primary {
|
||||
float: left;
|
||||
width: flex-grid(5,8);
|
||||
margin-right: flex-gutter(2);
|
||||
}
|
||||
|
||||
.action-secondary {
|
||||
display: block;
|
||||
float: left;
|
||||
width: flex-grid(2,8);
|
||||
margin-top: $baseline;
|
||||
padding: ($baseline/4);
|
||||
font-size: 13px;
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.error {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.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, &.submitted {
|
||||
color: rgba(0,0,0,.25);
|
||||
|
||||
label {
|
||||
cursor: text;
|
||||
|
||||
&:after {
|
||||
margin-left: ($baseline/4);
|
||||
}
|
||||
}
|
||||
|
||||
textarea, input {
|
||||
background: rgb(255,255,255);
|
||||
color: rgba(0,0,0,.25);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
label:after {
|
||||
color: rgba(0,0,0,.35);
|
||||
content: "(Disabled Currently)";
|
||||
}
|
||||
}
|
||||
|
||||
&.submitted {
|
||||
|
||||
label:after {
|
||||
content: "(Previously Submitted and Not Editable)";
|
||||
}
|
||||
|
||||
.value {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #C8C8C8;
|
||||
padding: $baseline ($baseline*0.75);
|
||||
background: #FAFAFA;
|
||||
}
|
||||
}
|
||||
|
||||
&.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 0s);
|
||||
|
||||
&.is-focused {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: ($baseline/2);
|
||||
|
||||
&.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 ($baseline/2) 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.addresses {
|
||||
|
||||
.field {
|
||||
width: 45%;
|
||||
}
|
||||
}
|
||||
|
||||
&.postal-2 {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
}
|
||||
|
||||
&.phoneinfo {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
||||
> .instructions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.field {
|
||||
opacity: 0.6;
|
||||
|
||||
.label, label {
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// form - specifics
|
||||
.form-fields-secondary {
|
||||
display: none;
|
||||
|
||||
&.is-shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
||||
fieldset {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-fields-secondary-visibility {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: $baseline ($baseline*1.5) 0 ($baseline*1.5);
|
||||
font-size: 13px;
|
||||
|
||||
&.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// aside
|
||||
aside {
|
||||
padding-left: $baseline;
|
||||
|
||||
.message-status {
|
||||
border-radius: 3px;
|
||||
margin: 0 0 ($baseline*2) 0;
|
||||
border: 1px solid #ccc;
|
||||
padding: 0;
|
||||
background: tint($yellow,90%);
|
||||
|
||||
> * {
|
||||
padding: $baseline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 ($baseline/4) 0;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.label, .value {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h3, h4, h5 {
|
||||
font-family: $sans-serif;
|
||||
}
|
||||
|
||||
h3 {
|
||||
border-bottom: 1px solid tint(rgb(0,0,0), 90%);
|
||||
padding-bottom: ($baseline*0.75);
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: ($baseline/4);
|
||||
}
|
||||
|
||||
.status-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: $baseline;
|
||||
|
||||
> .item {
|
||||
@include clearfix();
|
||||
margin: 0 0 ($baseline*0.75) 0;
|
||||
border-bottom: 1px solid tint(rgb(0,0,0), 95%);
|
||||
padding: 0 0 ($baseline/2) 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: ($baseline/4);
|
||||
position: relative;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: $baseline;
|
||||
margin-left: $baseline;
|
||||
content: "not started";
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.details, .item, .instructions {
|
||||
@include transition(opacity 0.10s ease-in-out 0s);
|
||||
font-size: 13px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-radius: $baseline;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
display: block;
|
||||
float: left;
|
||||
width: ($baseline/2);
|
||||
height: ($baseline/2);
|
||||
margin: 0 ($baseline/2) 0 0;
|
||||
background: $dark-gray;
|
||||
content: "";
|
||||
}
|
||||
|
||||
// specific states
|
||||
&.status-processed {
|
||||
|
||||
&:before {
|
||||
background: green;
|
||||
}
|
||||
|
||||
.title:after {
|
||||
color: green;
|
||||
content: "processed";
|
||||
}
|
||||
|
||||
&.status-registration {
|
||||
.exam-link {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.status-pending {
|
||||
|
||||
&:before {
|
||||
background: transparent;
|
||||
border: 1px dotted gray;
|
||||
}
|
||||
|
||||
.title:after {
|
||||
color: gray;
|
||||
content: "pending";
|
||||
}
|
||||
}
|
||||
|
||||
&.status-rejected {
|
||||
|
||||
&:before {
|
||||
background: $red;
|
||||
}
|
||||
|
||||
.title:after {
|
||||
color: red;
|
||||
content: "rejected";
|
||||
}
|
||||
|
||||
.call-link {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&.status-initial {
|
||||
|
||||
&:before {
|
||||
background: transparent;
|
||||
border: 1px dotted gray;
|
||||
}
|
||||
|
||||
.title:after {
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
|
||||
.details, .item, .instructions {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sub menus
|
||||
.accommodations-list, .error-list {
|
||||
list-style: none;
|
||||
margin: ($baseline/2) 0;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
|
||||
.item {
|
||||
margin: 0 0 ($baseline/4) 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// actions
|
||||
.contact-link {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.actions {
|
||||
box-shadow: inset 0 1px 1px 0px rgba(0,0,0,0.2);
|
||||
border-top: 1px solid tint(rgb(0,0,0), 90%);
|
||||
padding-top: ($baseline*0.75);
|
||||
background: tint($yellow,70%);
|
||||
font-size: 14px;
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.label, .value {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.message-copy {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.exam-button {
|
||||
@include button(simple, $pink);
|
||||
display: block;
|
||||
margin: ($baseline/2) 0 0 0;
|
||||
padding: ($baseline/2) $baseline;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover, &:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.registration-number {
|
||||
|
||||
.label {
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.registration-processed {
|
||||
|
||||
.message-copy {
|
||||
margin: 0 0 ($baseline/2) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .details {
|
||||
border-bottom: 1px solid rgba(216, 223, 230, 0.5);
|
||||
margin: 0 0 $baseline 0;
|
||||
padding: 0 $baseline $baseline $baseline;
|
||||
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);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.details-course {
|
||||
|
||||
}
|
||||
|
||||
.details-registration {
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin: 0 0 ($baseline/4) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// status messages
|
||||
.message {
|
||||
border-radius: 3px;
|
||||
display: none;
|
||||
margin: $baseline 0;
|
||||
padding: ($baseline/2) $baseline;
|
||||
|
||||
&.is-shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.message-copy {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// registration status
|
||||
&.message-flash {
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
margin: 0 0 ($baseline*2) 0;
|
||||
border: 1px solid #ccc;
|
||||
padding-top: ($baseline*0.75);
|
||||
background: tint($yellow,70%);
|
||||
font-size: 14px;
|
||||
|
||||
.message-title, .message-copy {
|
||||
}
|
||||
|
||||
.message-title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
margin: 0 0 ($baseline/4) 0;
|
||||
}
|
||||
|
||||
.message-copy {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.contact-button {
|
||||
@include button(simple, $blue);
|
||||
}
|
||||
|
||||
.exam-button {
|
||||
@include button(simple, $pink);
|
||||
}
|
||||
|
||||
.button {
|
||||
position: absolute;
|
||||
top: ($baseline/4);
|
||||
right: $baseline;
|
||||
margin: ($baseline/2) 0 0 0;
|
||||
padding: ($baseline/2) $baseline;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0;
|
||||
|
||||
&:hover, &:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.message-action {
|
||||
|
||||
.message-title, .message-copy {
|
||||
width: 65%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// submission error
|
||||
&.submission-error {
|
||||
@include box-sizing(border-box);
|
||||
float: left;
|
||||
width: flex-grid(8,8);
|
||||
border: 1px solid tint($red,85%);
|
||||
background: tint($red,95%);
|
||||
font-size: 14px;
|
||||
|
||||
#submission-error-heading {
|
||||
margin-bottom: ($baseline/2);
|
||||
border-bottom: 1px solid tint($red, 85%);
|
||||
padding-bottom: ($baseline/2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.field-name, .field-error {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.field-name {
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
|
||||
.field-error {
|
||||
color: tint($red, 55%);
|
||||
}
|
||||
|
||||
p {
|
||||
color: $red;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0 0 ($baseline/2) 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin-bottom: ($baseline/2);
|
||||
padding: 0;
|
||||
|
||||
span {
|
||||
color: $red;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $red;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// submission success
|
||||
&.submission-saved {
|
||||
border: 1px solid tint($blue,85%);
|
||||
background: tint($blue,95%);
|
||||
|
||||
.message-copy {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
// specific - registration closed
|
||||
&.registration-closed {
|
||||
@include border-bottom-radius(0);
|
||||
margin-top: 0;
|
||||
border-bottom: 1px solid $light-gray;
|
||||
padding: $baseline;
|
||||
background: tint($light-gray,50%);
|
||||
|
||||
.message-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.message-copy {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// hidden
|
||||
.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@
|
||||
overflow: hidden;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 30px;
|
||||
padding-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
form {
|
||||
margin-bottom: 12px;
|
||||
padding: 0px 40px;
|
||||
padding: 0px 40px 20px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
padding: 30px 30px 0 30px;
|
||||
}
|
||||
|
||||
.error_msg {
|
||||
margin: 20px;
|
||||
padding: 5px;
|
||||
color: $red;
|
||||
border: 1px solid $red;
|
||||
|
||||
}
|
||||
|
||||
.cart-list {
|
||||
padding: 30px;
|
||||
margin-top: 40px;
|
||||
|
||||
@@ -25,7 +25,7 @@ def url_class(is_active):
|
||||
<li>
|
||||
% endif
|
||||
<a href="${tab.link | h}" class="${url_class(tab.is_active)}">
|
||||
${tab.name | h}
|
||||
${_(tab.name) | h}
|
||||
% if tab.is_active == True:
|
||||
<span class="sr">, current location</span>
|
||||
%endif
|
||||
|
||||
@@ -218,24 +218,113 @@ ${fragment.foot_html()}
|
||||
<input type="text" id="calculator_input" title="${_('Calculator Input Field')}" tabindex="-1" />
|
||||
|
||||
<div class="help-wrapper">
|
||||
<a id="calculator_hint" href="#" role="button" aria-haspopup="true" aria-controls="calculator_input_help" aria-expanded="false" tabindex="-1">${_("Hints")}</a>
|
||||
<dl id="calculator_input_help" class="help">
|
||||
<dt tabindex="-1">${_("Suffixes:")}</dt>
|
||||
<dd tabindex="-1"> %kMGTcmunp</dd>
|
||||
<dt tabindex="-1">${_("Operations:")}</dt>
|
||||
<dd tabindex="-1">^ * / + - ()</dd>
|
||||
<dt tabindex="-1">${_("Functions:")}</dt>
|
||||
<dd tabindex="-1">sin, cos, tan, sqrt, log10, log2, ln, arccos, arcsin, arctan, abs </dd>
|
||||
<dt tabindex="-1">${_("Constants")}</dt>
|
||||
<dd tabindex="-1">e, pi</dd>
|
||||
|
||||
<!-- Students won't know what parallel means at this time. Complex numbers aren't well tested in the courseware, so we would prefer to not expose them. If you read the comments in the source, feel free to use them. If you run into a bug, please let us know. But we can't officially support them right now.
|
||||
|
||||
<dt>Unsupported:</dt> <dd>||, j </dd> -->
|
||||
</dl>
|
||||
<a id="calculator_hint" href="#" role="button" aria-describedby="calculator_input_help" tabindex="-1">${_("Hints")}</a>
|
||||
<div id="calculator_input_help" class="help" role="tooltip" aria-hidden="true">
|
||||
<p><span class="bold">${_("Integers")}:</span> 2520</p>
|
||||
<p><span class="bold">${_("Decimals")}:</span> 3.14 or .98</p>
|
||||
<p><span class="bold">${_("Scientific notation")}:</span> 1.2e-2 (=0.012), -4.4e+5 = -4.4e5 (=-440,000)</p>
|
||||
<p><span class="bold">${_("Appending SI postfixes")}:</span> 2.25k (=2,250)</p>
|
||||
<p><span class="bold">${_("Supported SI postfixes")}:</span></p>
|
||||
<table class="calc-postfixes">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>%</td>
|
||||
<td>percent</td>
|
||||
<td>0.01 = 1e-2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>T</td>
|
||||
<td>tera</td>
|
||||
<td>1e12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>G</td>
|
||||
<td>giga</td>
|
||||
<td>1e9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>M</td>
|
||||
<td>mega</td>
|
||||
<td>1e6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>k</td>
|
||||
<td>kilo</td>
|
||||
<td>1000 = 1e3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>c</td>
|
||||
<td>centi</td>
|
||||
<td>0.01 = 1e-2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>m</td>
|
||||
<td>milli</td>
|
||||
<td>0.001 = 1e-3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>u</td>
|
||||
<td>micro</td>
|
||||
<td>1e-6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>n</td>
|
||||
<td>nano</td>
|
||||
<td>1e-9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>p</td>
|
||||
<td>pico</td>
|
||||
<td>1e-12</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><span class="bold">${_("Operators")}:</span> + - * / ^ and || (${_("parallel resistors function")})</p>
|
||||
<p><span class="bold">${_("Functions")}:</span> sin, cos, tan, sqrt, log10, log2, ln, arccos, arcsin, arctan, abs, fact/factorial</p>
|
||||
<p><span class="bold">${_("Constants")}:</span></p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>j</td>
|
||||
<td>=</td>
|
||||
<td>sqrt(-1)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>e</td>
|
||||
<td>=</td>
|
||||
<td>${_("Euler's number")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pi</td>
|
||||
<td>=</td>
|
||||
<td>${_("ratio of a circle's circumference to it's diameter")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>k</td>
|
||||
<td>=</td>
|
||||
<td>${_("Boltzmann constant")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>c</td>
|
||||
<td>=</td>
|
||||
<td>${_("speed of light")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>T</td>
|
||||
<td>=</td>
|
||||
<td>${_("freezing point of water in degrees Kelvin")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>q</td>
|
||||
<td>=</td>
|
||||
<td>${_("fundamental charge")}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input id="calculator_button" type="submit" title="${_('Calculate')}" value="=" arial-label="${_('Calculate')}" value="=" tabindex="-1" />
|
||||
<input id="calculator_button" type="submit" title="${_('Calculate')}" value="=" aria-label="${_('Calculate')}" value="=" tabindex="-1" />
|
||||
<input type="text" id="calculator_output" title="${_('Calculator Output Field')}" readonly tabindex="-1" />
|
||||
</form>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ else:
|
||||
% elif cert_status['status'] in ('generating', 'ready', 'notpassing', 'restricted'):
|
||||
<p class="message-copy">${_("Your final grade:")}
|
||||
<span class="grade-value">${"{0:.0f}%".format(float(cert_status['grade'])*100)}</span>.
|
||||
% if cert_status['status'] == 'notpassing':
|
||||
% if cert_status['status'] == 'notpassing' and enrollment.mode != 'audit':
|
||||
${_("Grade required for a certificate:")} <span class="grade-value">
|
||||
${"{0:.0f}%".format(float(course.lowest_passing_grade)*100)}</span>.
|
||||
% elif cert_status['status'] == 'restricted' and enrollment.mode == 'verified':
|
||||
@@ -44,6 +44,12 @@ else:
|
||||
<a class="btn" href="${cert_status['download_url']}"
|
||||
title="${_('This link will open/download a PDF document')}">
|
||||
${_("Download Your Certificate (PDF)")}</a></li>
|
||||
% elif cert_status['show_download_url'] and enrollment.mode == 'verified' and cert_status['mode'] == 'honor':
|
||||
<li class="action">
|
||||
<p>${_('Since we did not have a valid set of verification photos from you when certificates were generated, we could not grant you a verified certificate. An honor code certificate has been granted instead.')}</p>
|
||||
<a class="btn" href="${cert_status['download_url']}"
|
||||
title="${_('This link will open/download a PDF document')}">
|
||||
${_("Download Your Certificate (PDF)")}</a></li>
|
||||
% elif cert_status['show_download_url'] and enrollment.mode == 'verified':
|
||||
<li class="action">
|
||||
<a class="btn" href="${cert_status['download_url']}"
|
||||
|
||||
@@ -65,7 +65,10 @@
|
||||
<div class="message message-upsell has-actions is-expandable is-shown">
|
||||
|
||||
<div class="wrapper-tip">
|
||||
<h4 class="message-title">${_("Challenge Yourself!")}</h4>
|
||||
<h4 class="message-title">
|
||||
<i class="icon-caret-down ui-toggle-expansion"></i>
|
||||
<span class="value">${_("Challenge Yourself!")}</span>
|
||||
</h4>
|
||||
<p class="message-copy">${_("Take this course as an ID-verified student.")}</p>
|
||||
</div>
|
||||
|
||||
|
||||
29
lms/templates/shoppingcart/download_report.html
Normal file
29
lms/templates/shoppingcart/download_report.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%inherit file="../main.html" />
|
||||
|
||||
<%block name="title"><title>${_("Download Purchase Report")}</title></%block>
|
||||
|
||||
|
||||
<section class="container">
|
||||
<h2>${_("Download CSV of purchase data")}</h2>
|
||||
% if date_fmt_error:
|
||||
<section class="error_msg">
|
||||
${_("There was an error in your date input. It should be formatted as YYYY-MM-DD")}
|
||||
</section>
|
||||
% endif
|
||||
% if total_count_error:
|
||||
<section class="error_msg">
|
||||
${_("There are too many results in your report.")} (>${settings.PAYMENT_REPORT_MAX_ITEMS}).
|
||||
${_("Try making the date range smaller.")}
|
||||
</section>
|
||||
% endif
|
||||
<form method="post">
|
||||
<label for="start_date">${_("Start Date: ")}</label>
|
||||
<input id="start_date" type="text" value="${start_date}" name="start_date"/>
|
||||
<label for="end_date">${_("End Date: ")}</label>
|
||||
<input id="end_date" type="text" value="${end_date}" name="end_date"/>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
</section>
|
||||
@@ -1,482 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
<%!
|
||||
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"><title>${_('Pearson VUE Test Center Proctoring - Registration')}</title></%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
|
||||
// if form is disabled or registration is closed
|
||||
$('#testcenter_register_form.disabled').find('input, textarea, button').attr('disabled','disabled');
|
||||
|
||||
// toggling accommodations elements
|
||||
$('.form-fields-secondary-visibility').click(function(e){
|
||||
e.preventDefault();
|
||||
$(this).addClass("is-hidden");
|
||||
|
||||
$('.form-fields-secondary').addClass("is-shown");
|
||||
});
|
||||
|
||||
|
||||
$(document).on('ajax:success', '#testcenter_register_form', function(data, json, xhr) {
|
||||
if(json.success) {
|
||||
// when a form is successfully filled out, return back to the dashboard.
|
||||
location.href="${reverse('dashboard')}";
|
||||
} else {
|
||||
// This is performed by the following code that parses the errors returned as json by the
|
||||
// registration form validation.
|
||||
var field_errors = json.field_errors;
|
||||
var non_field_errors = json.non_field_errors;
|
||||
var fieldname;
|
||||
var field_errorlist;
|
||||
var field_error;
|
||||
var error_msg;
|
||||
var num_errors = 0;
|
||||
var error_html = '';
|
||||
// first get rid of any errors that are already present:
|
||||
$(".field.error", ".form-actions").removeClass('error');
|
||||
$("#submission-error-list").html(error_html);
|
||||
// start to display new errors:
|
||||
$(".form-actions").addClass("error");
|
||||
$(".submission-error").addClass("is-shown");
|
||||
for (fieldname in field_errors) {
|
||||
// to inform a user of what field the error occurred on, add a class of .error to the .field element.
|
||||
// for convenience, use the "id" attribute to identify the one matching the errant field's name.
|
||||
var field_id = "field-" + fieldname;
|
||||
var field_label = $("[id='"+field_id+"'] label").text();
|
||||
|
||||
$("[id='"+field_id+"']").addClass('error');
|
||||
|
||||
field_errorlist = field_errors[fieldname];
|
||||
for (i=0; i < field_errorlist.length; i+= 1) {
|
||||
field_error = field_errorlist[i];
|
||||
error_msg = '<span class="field-name">' + field_label + ':</span>' + '<span class="field-error">' + field_error + '</span>';
|
||||
error_html = error_html + '<li>' + '<a href="#field-' + fieldname + '">' + error_msg + '</a></li>';
|
||||
num_errors += 1;
|
||||
}
|
||||
}
|
||||
for (i=0; i < non_field_errors.length; i+= 1) {
|
||||
error_msg = non_field_errors[i];
|
||||
error_html = error_html + '<li class="to-be-determined">' + error_msg + '</li>';
|
||||
num_errors += 1;
|
||||
}
|
||||
if (num_errors == 1) {
|
||||
error_msg = 'was an error';
|
||||
} else {
|
||||
error_msg = 'were ' + num_errors + ' errors';
|
||||
}
|
||||
$('#submission-error-heading').text("We're sorry, but there " + error_msg + " in the information you provided below:")
|
||||
$("#submission-error-list").html(error_html);
|
||||
}
|
||||
});
|
||||
|
||||
$("form :input").focus(function() {
|
||||
$("label[for='" + this.id + "']").addClass("is-focused");
|
||||
}).blur(function() {
|
||||
$("label").removeClass("is-focused");
|
||||
});
|
||||
|
||||
})(this)
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
<section class="testcenter-register container">
|
||||
|
||||
<section class="introduction">
|
||||
<header>
|
||||
<hgroup>
|
||||
<h2><a href="${reverse('dashboard')}">${get_course_about_section(course, 'university')} ${course.display_number_with_default | h} ${course.display_name_with_default | h}</a></h2>
|
||||
|
||||
% if registration:
|
||||
<h1>${_('Your Pearson VUE Proctored Exam Registration')}</h1>
|
||||
% else:
|
||||
<h1>${_('Register for a Pearson VUE Proctored Exam')}</h1>
|
||||
% endif
|
||||
</hgroup>
|
||||
</header>
|
||||
</section>
|
||||
|
||||
<%
|
||||
exam_help_href = "mailto:exam-help@edx.org?subject=Pearson VUE Exam - " + get_course_about_section(course, 'university') + " - " + course.number
|
||||
%>
|
||||
|
||||
% if registration:
|
||||
<!-- select one of four registration status banners to display -->
|
||||
% if registration.is_accepted:
|
||||
<section class="status message message-flash registration-processed message-action is-shown">
|
||||
<h3 class="message-title">${_('Your registration for the Pearson exam has been processed')}</h3>
|
||||
<p class="message-copy">${_("Your registration number is <strong>{reg_number}</strong>. "
|
||||
"(Write this down! You\'ll need it to schedule your exam.)").format(reg_number=registration.client_candidate_id)}</p>
|
||||
<a href="${registration.registration_signup_url}" class="button exam-button">${_('Schedule Pearson exam')}</a>
|
||||
</section>
|
||||
% endif
|
||||
|
||||
% if registration.demographics_is_rejected:
|
||||
<section class="status message message-flash demographics-rejected message-action is-shown">
|
||||
<h3 class="message-title">${_('Your demographic information contained an error and was rejected')}</h3>
|
||||
<p class="message-copy">${_('Please check the information you provided, and correct the errors noted below.')}
|
||||
</section>
|
||||
% endif
|
||||
|
||||
% if registration.registration_is_rejected:
|
||||
<section class="status message message-flash registration-rejected message-action is-shown">
|
||||
<h3 class="message-title">${_('Your registration for the Pearson exam has been rejected')}</h3>
|
||||
<p class="message-copy">${_('Please see your <strong>registration status</strong> details for more information.')}</p>
|
||||
</section>
|
||||
% endif
|
||||
|
||||
% if registration.is_pending:
|
||||
<section class="status message message-flash registration-pending is-shown">
|
||||
<h3 class="message-title">${_('Your registration for the Pearson exam is pending')}</h3>
|
||||
<p class="message-copy">${_('Once your information is processed, it will be forwarded to Pearson and you will be able to schedule an exam.')}</p>
|
||||
</section>
|
||||
% endif
|
||||
|
||||
% endif
|
||||
|
||||
<section class="content">
|
||||
<header>
|
||||
<h3 class="is-hidden">${_('Registration Form')}</h3>
|
||||
</header>
|
||||
% if exam_info.is_registering():
|
||||
<form id="testcenter_register_form" method="post" data-remote="true" action="/create_exam_registration">
|
||||
% else:
|
||||
<form id="testcenter_register_form" method="post" data-remote="true" action="/create_exam_registration" class="disabled">
|
||||
<!-- registration closed, so tell them they can't do anything: -->
|
||||
<div class="status message registration-closed is-shown">
|
||||
<h3 class="message-title">${_('Registration for this Pearson exam is closed')}</h3>
|
||||
<p class="message-copy">${_('Your previous information is available below, however you may not edit any of the information.')}
|
||||
</div>
|
||||
% endif
|
||||
|
||||
% if registration:
|
||||
<p class="instructions">
|
||||
${_('Please use the following form if you need to update your demographic information used in your Pearson VUE Proctored Exam. Required fields are noted by <strong class="indicator">bold text and an asterisk (*)')}</strong>.
|
||||
</p>
|
||||
% else:
|
||||
<p class="instructions">
|
||||
${_('Please provide the following demographic information to register for a Pearson VUE Proctored Exam. Required fields are noted by <strong class="indicator">bold text and an asterisk (*)')}</strong>.
|
||||
</p>
|
||||
% endif
|
||||
|
||||
<!-- include these as pass-throughs -->
|
||||
<input id="id_email" type="hidden" name="email" value="${user.email}" />
|
||||
<input id="id_username" type="hidden" name="username" value="${user.username}" />
|
||||
<input id="id_course_id" type="hidden" name="course_id" value="${course.id}" />
|
||||
<input id="id_exam_series_code" type="hidden" name="exam_series_code" value="${exam_info.exam_series_code}" />
|
||||
|
||||
<div class="form-fields-primary">
|
||||
<fieldset class="group group-form group-form-personalinformation">
|
||||
<legend class="is-hidden">${_('Personal Information')}</legend>
|
||||
|
||||
<ol class="list-input">
|
||||
<li class="field" id="field-salutation">
|
||||
<label for="salutation">${_('Salutation')}</label>
|
||||
<input class="short" id="salutation" type="text" name="salutation" value="${testcenteruser.salutation}" placeholder="${_('e.g. Mr., Ms., Mrs., Dr.')}" />
|
||||
</li>
|
||||
<li class="field required" id="field-first_name">
|
||||
<label for="first_name">${_('First Name')} </label>
|
||||
<input id="first_name" type="text" name="first_name" value="${testcenteruser.first_name}" placeholder="${_('e.g. Albert')}" />
|
||||
</li>
|
||||
<li class="field" id="field-middle_name">
|
||||
<label for="middle_name">${_("Middle Name")}</label>
|
||||
<input id="middle_name" type="text" name="middle_name" value="${testcenteruser.middle_name}" placeholder="" />
|
||||
</li>
|
||||
<li class="field required" id="field-last_name">
|
||||
<label for="last_name">${_('Last Name')}</label>
|
||||
<input id="last_name" type="text" name="last_name" value="${testcenteruser.last_name}" placeholder="${_('e.g. Einstein')}" />
|
||||
</li>
|
||||
<li class="field" id="field-suffix">
|
||||
<label for="suffix">${_('Suffix')}</label>
|
||||
<input class="short" id="suffix" type="text" name="suffix" value="${testcenteruser.suffix}" placeholder="${_('e.g. Jr., Sr. ')}" />
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="group group-form group-form-mailingaddress">
|
||||
<legend class="is-hidden">${_('Mailing Address')}</legend>
|
||||
|
||||
<ol class="list-input">
|
||||
<li class="field required" id="field-address_1">
|
||||
<label class="long" for="address_1">${_('Address Line #1')}</label>
|
||||
<input id="address_1" type="text" name="address_1" value="${testcenteruser.address_1}" placeholder="${_('e.g. 112 Mercer Street')}" />
|
||||
</li>
|
||||
<li class="field-group addresses">
|
||||
<div class="field" id="field-address_2">
|
||||
<label for="address_2">${_('Address Line #2')}</label>
|
||||
<input id="address_2" class="long" type="text" name="address_2" value="${testcenteruser.address_2}" placeholder="${_('e.g. Apartment 123')}" />
|
||||
</div>
|
||||
<div class="field" id="field-address_3">
|
||||
<label for="address_3">${_('Address Line #3')}</label>
|
||||
<input id="address_3" class="long" type="text" name="address_3" value="${testcenteruser.address_3}" placeholder="${_('e.g. Attention: Albert Einstein')}" />
|
||||
</div>
|
||||
</li>
|
||||
<li class="field required postal-1" id="field-city">
|
||||
<label for="city">${_('City')}</label>
|
||||
<input id="city" class="long" type="text" name="city" value="${testcenteruser.city}" placeholder="${_('e.g. Newark')}" />
|
||||
</li>
|
||||
<li class="field-group postal-2">
|
||||
<div class="field" id="field-state">
|
||||
<label for="state">${_('State/Province')}</label>
|
||||
<input id="state" class="short" type="text" name="state" value="${testcenteruser.state}" placeholder="${_('e.g. NJ')}" />
|
||||
</div>
|
||||
<div class="field" id="field-postal_code">
|
||||
<label for="postal_code">${_('Postal Code')}</label>
|
||||
<input id="postal_code" class="short" type="text" name="postal_code" value="${testcenteruser.postal_code}" placeholder="${_('e.g. 08540')}" />
|
||||
</div>
|
||||
<div class="field required" id="field-country">
|
||||
<label class="short" for="country">${_('Country Code')}</label>
|
||||
<input id="country" class="short" type="text" name="country" value="${testcenteruser.country}" placeholder="${_('e.g. USA')}" />
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="group group-form group-form-contactinformation">
|
||||
<legend class="is-hidden">${_('Contact & Other Information')}</legend>
|
||||
|
||||
<ol class="list-input">
|
||||
<li class="field-group phoneinfo">
|
||||
<div class="field required" id="field-phone">
|
||||
<label for="phone">${_('Phone Number')}</label>
|
||||
<input id="phone" type="tel" name="phone" value="${testcenteruser.phone}" />
|
||||
</div>
|
||||
<div class="field" id="field-extension">
|
||||
<label for="extension">${_('Extension')}</label>
|
||||
<input id="extension" class="short" type="tel" name="extension" value="${testcenteruser.extension}" />
|
||||
</div>
|
||||
<div class="field required" id="field-phone_country_code">
|
||||
<label for="phone_country_code">${_('Phone Country Code')}</label>
|
||||
<input id="phone_country_code" class="short" type="text" name="phone_country_code" value="${testcenteruser.phone_country_code}" />
|
||||
</div>
|
||||
</li>
|
||||
<li class="field-group faxinfo">
|
||||
<div class="field" id="field-fax">
|
||||
<label for="fax">${_('Fax Number')}</label>
|
||||
<input id="fax" type="tel" class="short" name="fax" value="${testcenteruser.fax}" />
|
||||
</div>
|
||||
<div class="field" id="field-fax_country_code">
|
||||
<label for="fax_country_code">${_('Fax Country Code')}</label>
|
||||
<input id="fax_country_code" class="short" type="text" name="fax_country_code" value="${testcenteruser.fax_country_code}" />
|
||||
</div>
|
||||
</li>
|
||||
<li class="field" id="field-company_name">
|
||||
<label for="company_name">${_('Company')}</label>
|
||||
<input id="company_name" type="text" name="company_name" value="${testcenteruser.company_name}" placeholder="${_('e.g. American Association of University Professors')}" />
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
% if registration:
|
||||
% if registration.accommodation_request and len(registration.accommodation_request) > 0:
|
||||
<div class="form-fields-secondary is-shown disabled" id="form-fields-secondary">
|
||||
% endif
|
||||
% else:
|
||||
<div class="form-fields-secondary" id="form-fields-secondary">
|
||||
% endif
|
||||
|
||||
% if registration:
|
||||
% if registration.accommodation_request and len(registration.accommodation_request) > 0:
|
||||
<p class="note">${_('<span class="title">Note</span>: Your previous accommodation request below needs to be reviewed in detail <strong>and will add a significant delay to your registration process</strong>.')}</p>
|
||||
% endif
|
||||
% else:
|
||||
<p class="note">${_('<span class="title">Note</span>: Accommodation requests are not part of your demographic information, <strong>and cannot be changed once submitted</strong>. Accommodation requests, which are reviewed on a case-by-case basis, <strong>will add significant delay to the registration process</strong>.')}</p>
|
||||
% endif
|
||||
|
||||
<fieldset class="group group-form group-form-optional">
|
||||
<legend class="is-hidden">${_('Optional Information')}</legend>
|
||||
|
||||
<ol class="list-input">
|
||||
% if registration:
|
||||
% if registration.accommodation_request and len(registration.accommodation_request) > 0:
|
||||
<li class="field submitted" id="field-accommodation_request">
|
||||
<label for="accommodation_request">${_('Accommodations Requested')}</label>
|
||||
<p class="value" id="accommodations">${registration.accommodation_request}</p>
|
||||
</li>
|
||||
% endif
|
||||
% else:
|
||||
<li class="field" id="field-accommodation_request">
|
||||
<label for="accommodation_request">${_('Accommodations Requested')}</label>
|
||||
<textarea class="long" id="accommodation_request" name="accommodation_request" value="" placeholder=""></textarea>
|
||||
</li>
|
||||
% endif
|
||||
</ol>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
% if registration:
|
||||
<button type="submit" id="submit" class="action action-primary action-update">${_('Update Demographics')}</button>
|
||||
<a href="${reverse('dashboard')}" class="action action-secondary action-cancel">${_('Cancel Update')}</a>
|
||||
% else:
|
||||
<button type="submit" id="submit" class="action action-primary action-register">${_('Register for Pearson VUE Test')}</button>
|
||||
<a href="${reverse('dashboard')}" class="action action-secondary action-cancel">${_('Cancel Registration')}</a>
|
||||
% endif
|
||||
|
||||
<div class="message message-status submission-error">
|
||||
<p id="submission-error-heading" class="message-copy"></p>
|
||||
<ul id="submission-error-list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
% if registration:
|
||||
% if registration.accommodation_request and len(registration.accommodation_request) > 0:
|
||||
<a class="actions form-fields-secondary-visibility is-hidden" href="#form-fields-secondary">${_('Special (<abbr title="Americans with Disabilities Act">ADA</abbr>) Accommodations')}</a>
|
||||
% endif
|
||||
% else:
|
||||
<a class="actions form-fields-secondary-visibility" href="#form-fields-secondary">${_('Special (<abbr title="Americans with Disabilities Act">ADA</abbr>) Accommodations')}</a>
|
||||
% endif
|
||||
</section>
|
||||
|
||||
<aside>
|
||||
% if registration:
|
||||
|
||||
% if registration.is_accepted:
|
||||
<div class="message message-status registration-processed is-shown">
|
||||
% endif
|
||||
% if registration.is_rejected:
|
||||
<div class="message message-status registration-rejected is-shown">
|
||||
% endif
|
||||
% if registration.is_pending:
|
||||
<div class="message message-status registration-pending is-shown">
|
||||
% endif
|
||||
<h3>${_('Pearson Exam Registration Status')}</h3>
|
||||
|
||||
<ol class="status-list">
|
||||
<!-- first provide status of demographics -->
|
||||
% if registration.demographics_is_pending:
|
||||
<li class="item status status-pending status-demographics">
|
||||
<h4 class="title">${_('Demographic Information')}</h4>
|
||||
<p class="details">${_('The demographic information you most recently provided is pending. You may edit this information at any point before exam registration closes on <strong>{end_date}</strong>').format(end_date=exam_info.registration_end_date_text)}</p>
|
||||
</li>
|
||||
% endif
|
||||
% if registration.demographics_is_accepted:
|
||||
<li class="item status status-processed status-demographics">
|
||||
<h4 class="title">${_('Demographic Information')}</h4>
|
||||
<p class="details">${_('The demographic information you most recently provided has been processed. You may edit this information at any point before exam registration closes on <strong>{end_date}</strong>').format(end_date=exam_info.registration_end_date_text)}</p>
|
||||
</li>
|
||||
% endif
|
||||
% if registration.demographics_is_rejected:
|
||||
<li class="item status status-rejected status-demographics">
|
||||
<h4 class="title">${_('Demographic Information')}</h4>
|
||||
<p class="details">${_('The demographic information you most recently provided has been rejected by Pearson. You can correct and submit it again before the exam registration closes on <strong>{end_date}</strong>. The error message is:').format(end_date=exam_info.registration_end_date_text)}</p>
|
||||
|
||||
<ul class="error-list">
|
||||
<li class="item">${registration.testcenter_user.upload_error_message}.</li>
|
||||
</ul>
|
||||
|
||||
<p class="action">${_('If the error is not correctable by revising your demographic information, please {contact_link_start}contact edX at exam-help@edx.org{contact_link_end}.').format(contact_link_start='<a class="contact-link" href="{}"'.format(exam_help_href), contact_link_end='</a>')}</p>
|
||||
</li>
|
||||
% endif
|
||||
|
||||
<!-- then provide status of accommodations, if any -->
|
||||
% if registration.accommodation_is_pending:
|
||||
<li class="item status status-pending status-accommodations">
|
||||
<h4 class="title">${_('Accommodations Request')}</h4>
|
||||
<p class="details">${_('Your requested accommodations are pending. Within a few days, you should see confirmation here of granted accommodations.')}</p>
|
||||
</li>
|
||||
% endif
|
||||
% if registration.accommodation_is_accepted:
|
||||
<li class="item status status-processed status-accommodations">
|
||||
<h4 class="title">${_('Accommodations Request')}</h4>
|
||||
<p class="details">${_('Your requested accommodations have been reviewed and processed. You are allowed:')}</p>
|
||||
|
||||
<ul class="accommodations-list">
|
||||
% for accommodation_name in registration.get_accommodation_names():
|
||||
<li class="item">${accommodation_name}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</li>
|
||||
% endif
|
||||
% if registration.accommodation_is_rejected:
|
||||
<li class="item status status-processed status-accommodations">
|
||||
<h4 class="title">${_('Accommodations Request')}</h4>
|
||||
<p class="details">${_('Your requested accommodations have been reviewed and processed. You are allowed no accommodations.')}</p>
|
||||
|
||||
<p class="action">${_('Please {contact_link_start}contact {edX} at ${exam_help}{contact_link_end}.').format(contact_link_start='<a class="contact-link" href="{}">'.format(exam_help_href), contact_link_end='</a>', edX="edX", exam_help="exam-help@edx.org")}</p>
|
||||
</li>
|
||||
% endif
|
||||
|
||||
<!-- finally provide status of registration -->
|
||||
% if registration.registration_is_pending:
|
||||
<li class="item status status-pending status-registration">
|
||||
<h4 class="title">${_('Registration Request')}</h4>
|
||||
<p class="details">${_('Your exam registration is pending. Once your information is processed, it will be forwarded to Pearson and you will be able to schedule an exam.')}</p>
|
||||
</li>
|
||||
% endif
|
||||
% if registration.registration_is_accepted:
|
||||
<li class="item status status-processed status-registration">
|
||||
<h4 class="title">${_('Registration Request')}</h4>
|
||||
<p class="details">${_('Your exam registration has been processed and has been forwarded to Pearson. <strong>You are now able to {exam_link_start}schedule a Pearson exam{exam_link_end}</strong>.').format(exam_link_start='<a href="{}" class="exam-link">'.format(registration.registration_signup_url), exam_link_end='</a>')}</p>
|
||||
</li>
|
||||
% endif
|
||||
% if registration.registration_is_rejected:
|
||||
<li class="item status status-rejected status-registration">
|
||||
<h4 class="title">${_('Registration Request')}</h4>
|
||||
<p class="details">${_('Your exam registration has been rejected by Pearson. <strong>You currently cannot schedule an exam</strong>. The errors found include:')}</p>
|
||||
|
||||
<ul class="error-list">
|
||||
<li class="item">${registration.upload_error_message}</li>
|
||||
</ul>
|
||||
|
||||
<p class="action">${_('Please {contact_link_start}contact edX at exam-help@edx.org{contact_link_end}.').format(contact_link_start='<a class="contact-link" href="{}"'.format(exam_help_href), contact_link_end='</a>')}</p>
|
||||
</li>
|
||||
% endif
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
% endif
|
||||
|
||||
<div class="details details-course">
|
||||
<h4>${_("About {university} {course_number}").format(university=get_course_about_section(course, 'university'), course_number=course.course.display_number_with_default) | h}</h4>
|
||||
<p>
|
||||
% if course.has_ended():
|
||||
<span class="label">${_('Course Completed:')}</span> <span class="value">${course.end_date_text}</span>
|
||||
% elif course.has_started():
|
||||
<span class="label">${_('Course Started:')}</span> <span class="value">${course.start_date_text}</span>
|
||||
% else: # hasn't started yet
|
||||
<span class="label">${_('Course Starts:')}</span> <span class="value">${course.start_date_text}</span>
|
||||
% endif
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="details details-registration">
|
||||
<h4>${_('Pearson VUE Test Details')}</h4>
|
||||
% if exam_info is not None:
|
||||
<ul>
|
||||
<li>
|
||||
<span class="label">${_('Exam Name:')}</span> <span class="value">${exam_info.display_name_with_default}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">${_('First Eligible Appointment Date:')}</span> <span class="value">${exam_info.first_eligible_appointment_date_text}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">${_('Last Eligible Appointment Date:')}</span> <span class="value">${exam_info.last_eligible_appointment_date_text}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">${_('Registration Ends:')}</span> <span class="value">${exam_info.registration_end_date_text}</span>
|
||||
</li>
|
||||
</ul>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
<div class="details details-contact">
|
||||
<h4>${_('Questions')}</h4>
|
||||
<p>${_('If you have a specific question pertaining to your registration, you may {contact_link_start}contact edX at exam-help@edx.org{contact_link_end}.').format(contact_link_start='<a class="contact-link" href="{}"'.format(exam_help_href), contact_link_end='</a>')}</p>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
@@ -1,30 +1,57 @@
|
||||
{% extends "wiki/article.html" %}
|
||||
{% load wiki_tags i18n %}
|
||||
{% load wiki_tags i18n sekizai_tags %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block pagetitle %}{% trans "Edit" %}: {{ article.current_revision.title }}{% endblock %}
|
||||
|
||||
{% block wiki_contents_tab %}
|
||||
|
||||
<form method="POST" class="form-horizontal" id="article_edit_form" enctype="multipart/form-data">
|
||||
{% addtoblock "js" %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(
|
||||
function ($) {
|
||||
accessible_modal("#cheatsheetLink", "#cheatsheetModal .close-modal", "#cheatsheetModal", ".content-wrapper");
|
||||
accessible_modal("#previewButton", "#previewModal .close-modal", "#previewModal", ".content-wrapper");
|
||||
$("#previewModalBackToEditor").click(function (e) {
|
||||
$("#previewModal .close-modal").click();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
);
|
||||
</script>
|
||||
{% endaddtoblock %}
|
||||
|
||||
<form method="POST" class="form-horizontal" id="article_edit_form" name="article_edit_form" enctype="multipart/form-data">
|
||||
{% include "wiki/includes/editor.html" %}
|
||||
<div class="form-actions">
|
||||
<button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="this.form.target=''; this.form.action='{% url 'wiki:edit' path=urlpath.path article_id=article.id %}'">
|
||||
<span class="icon-ok"></span>
|
||||
{% trans "Save changes" %}
|
||||
</button>
|
||||
<button type="submit" name="preview" value="1" class="btn btn-large" onclick="$('#previewModal').modal('show'); this.form.target = 'previewWindow'; this.form.action = '{% url 'wiki:preview' path=urlpath.path article_id=article.id %}';">
|
||||
<a class="btn btn-large" id="previewButton" href="#previewModal" rel="leanModal"
|
||||
onclick="
|
||||
document.article_edit_form.target='previewWindow';
|
||||
document.article_edit_form.action='{% url 'wiki:preview' path=urlpath.path article_id=article.id %}';
|
||||
document.article_edit_form.submit();">
|
||||
<span class="icon-eye-open"></span>
|
||||
{% trans "Preview" %}
|
||||
</button>
|
||||
|
||||
</a>
|
||||
|
||||
<a href="{% url 'wiki:delete' path=urlpath.path article_id=article.id %}" class="pull-right btn btn-danger">
|
||||
<span class="icon-trash"></span>
|
||||
{% trans "Delete article" %}
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="modal hide fade" id="previewModal">
|
||||
<section id="previewModal" class="modal" aria-hidden="true">
|
||||
<div class="inner-wrapper" role="dialog" aria-labelledby="preview-title">
|
||||
<button class="close-modal">✕ <span class="sr">{% trans 'Close Modal' %}</span></button>
|
||||
|
||||
<header>
|
||||
<h2 id="preview-title">{% trans "Wiki Preview" %}<span class="sr">, {% trans "modal open" %}</span></h2>
|
||||
<hr/>
|
||||
</header>
|
||||
|
||||
<div class="modal-body">
|
||||
<iframe name="previewWindow" frameborder="0"></iframe>
|
||||
</div>
|
||||
@@ -34,16 +61,13 @@
|
||||
{% trans "Save changes" %}
|
||||
</button>
|
||||
|
||||
<a href="#" class="btn btn-large" data-dismiss="modal">
|
||||
<a id="previewModalBackToEditor" href="#" class="btn btn-large">
|
||||
<span class="icon-circle-arrow-left"></span>
|
||||
{% trans "Back to editor" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% include "wiki/includes/cheatsheet.html" %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}wiki/js/diffview.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
$( document ).ready(function() {
|
||||
$(document).ready(function() {
|
||||
$('.accordion input[disabled!="disabled"][type="radio"]').first().attr('checked', 'true');
|
||||
|
||||
$('a.accordion-toggle').click(function(event) {
|
||||
@@ -41,6 +41,20 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".previewRevisionButton").each(function () {
|
||||
accessible_modal("#"+this.id, "#previewRevisionModal .close-modal", "#previewRevisionModal", ".content-wrapper");
|
||||
});
|
||||
$("#previewRevisionModalBackToHistory").click(function (e) {
|
||||
$("#previewRevisionModal .close-modal").click();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
accessible_modal("#mergeButton", "#mergeModal .close-modal", "#mergeModal", ".content-wrapper");
|
||||
$("#mergeModalBackToHistory").click(function (e) {
|
||||
$("#mergeModal .close-modal").click();
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endaddtoblock %}
|
||||
@@ -81,7 +95,7 @@
|
||||
{% trans "Click each revision to see a list of edited lines. Click the Preview button to see how the article looked at this stage. At the bottom of this page, you can change to a particular revision or merge an old revision with the current one." %}
|
||||
</p>
|
||||
|
||||
<form method="GET">
|
||||
<form method="GET" name="revisions_form">
|
||||
<div class="tab-content" style="overflow: visible;">
|
||||
{% for revision in revisions %}
|
||||
<div class="accordion" id="accordion{{ revision.revision_number }}">
|
||||
@@ -107,16 +121,29 @@
|
||||
</div>
|
||||
<div class="pull-right" style="vertical-align: middle; margin: 8px 3px;">
|
||||
{% if not revision == article.current_revision %}
|
||||
<button type="submit" class="btn" onclick="$('#previewModal').modal('show'); this.form.target='previewWindow'; this.form.r.value='{{ revision.id }}'; this.form.action='{% url 'wiki:preview_revision' article.id %}'; $('#previewModal .switch-to-revision').attr('href', '{% url 'wiki:change_revision' path=urlpath.path article_id=article.id revision_id=revision.id %}')">
|
||||
<span class="icon-eye-open"></span>
|
||||
{% trans "Preview this revision" %}
|
||||
</button>
|
||||
<a class="btn previewRevisionButton"
|
||||
id="previewRevisionButton{{ revision.revision_number }}"
|
||||
href="#previewRevisionModal" rel="leanModal"
|
||||
onclick="
|
||||
document.revisions_form.target='previewWindow';
|
||||
document.revisions_form.r.value='{{ revision.id }}';
|
||||
document.revisions_form.action='{% url 'wiki:preview_revision' article.id %}';
|
||||
$('#previewRevisionModal .switch-to-revision').attr('href', '{% url 'wiki:change_revision' path=urlpath.path article_id=article.id revision_id=revision.id %}');
|
||||
document.revisions_form.submit();">
|
||||
<span class="icon-eye-open"></span>
|
||||
{% trans "Preview this revision" %}
|
||||
</a>
|
||||
|
||||
{% if article|can_write:user %}
|
||||
<input type="radio" style="margin: 0 10px;" value="{{ revision.id }}" name="revision_id"
|
||||
switch-button-href="{% url 'wiki:change_revision' path=urlpath.path revision_id=revision.id %}"
|
||||
merge-button-href="{% url 'wiki:merge_revision_preview' article_id=article.id revision_id=revision.id %}"
|
||||
merge-button-commit-href="{% url 'wiki:merge_revision' path=urlpath.path article_id=article.id revision_id=revision.id %}"
|
||||
/>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if article|can_write:user %}
|
||||
<input type="radio"{% if revision == article.current_revision %} disabled="true"{% endif %} style="margin: 0 10px;" value="{{ revision.id }}" name="revision_id" switch-button-href="{% url 'wiki:change_revision' path=urlpath.path revision_id=revision.id %}" merge-button-href="{% url 'wiki:merge_revision_preview' article_id=article.id revision_id=revision.id %}" merge-button-commit-href="{% url 'wiki:merge_revision' path=urlpath.path article_id=article.id revision_id=revision.id %}" />
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
<div style="clear: both"></div>
|
||||
</div>
|
||||
@@ -140,84 +167,103 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% include "wiki/includes/pagination.html" %}
|
||||
|
||||
|
||||
{% if revisions.count > 1 %}
|
||||
<div class="form-actions">
|
||||
<div class="pull-right">
|
||||
{% if article|can_write:user %}
|
||||
<button type="submit" name="preview" value="1" class="btn btn-large" onclick="$('#mergeModal').modal('show'); this.form.target='mergeWindow'; this.form.action=$('input[type=radio]:checked').attr('merge-button-href'); $('.merge-revision-commit').attr('href', $('input[type=radio]:checked').attr('merge-button-commit-href'))">
|
||||
<a class="btn btn-large" id="mergeButton" href="#mergeModal" rel="leanModal"
|
||||
onclick="
|
||||
document.revisions_form.target='mergeWindow';
|
||||
document.revisions_form.action=$('input[type=radio]:checked').attr('merge-button-href');
|
||||
$('.merge-revision-commit').attr('href', $('input[type=radio]:checked').attr('merge-button-commit-href'));
|
||||
document.revisions_form.submit();">
|
||||
<span class="icon-random"></span>
|
||||
{% trans "Merge selected with current..." %}
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="submit" disabled="true" name="preview" value="1" class="btn btn-large">
|
||||
<span class="icon-lock"></span>
|
||||
{% trans "Merge selected with current..." %}
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="$('#previewModal').modal('show'); this.form.target='previewWindow'; this.form.action=$('input[type=radio]:checked').attr('switch-button-href')">
|
||||
<button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="this.form.action=$('input[type=radio]:checked').attr('switch-button-href')">
|
||||
<span class="icon-flag"></span>
|
||||
{% trans "Switch to selected version" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
<input type="hidden" name="r" value="" />
|
||||
<div class="modal hide fade" id="previewModal">
|
||||
<div class="modal-body">
|
||||
<iframe name="previewWindow"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#" class="btn btn-large" data-dismiss="modal">
|
||||
<section id="previewRevisionModal" class="modal" aria-hidden="true">
|
||||
<div class="inner-wrapper" role="dialog" aria-labelledby="preview-title">
|
||||
<button class="close-modal">✕ <span class="sr">{% trans 'Close Modal' %}</span></button>
|
||||
|
||||
<header>
|
||||
<h2 id="preview-title">{% trans "Wiki Revision Preview" %}<span class="sr">, {% trans "modal open" %}</span></h2>
|
||||
<hr/>
|
||||
</header>
|
||||
<div class="modal-body">
|
||||
<iframe name="previewWindow"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a id="previewRevisionModalBackToHistory" href="#" class="btn btn-large" data-dismiss="modal">
|
||||
<span class="icon-circle-arrow-left"></span>
|
||||
{% trans "Back to history view" %}
|
||||
</a>
|
||||
{% if article|can_write:user %}
|
||||
<a href="#" class="btn btn-large btn-primary switch-to-revision">
|
||||
<span class="icon-flag"></span>
|
||||
{% trans "Switch to this version" %}
|
||||
</a>
|
||||
<a href="#" class="btn btn-large btn-primary switch-to-revision">
|
||||
<span class="icon-flag"></span>
|
||||
{% trans "Switch to this version" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="btn btn-large btn-primary disabled">
|
||||
<span class="icon-lock"></span>
|
||||
{% trans "Switch to this version" %}
|
||||
</a>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal hide fade" id="mergeModal">
|
||||
<div class="modal-header">
|
||||
<h1>{% trans "Merge with current" %}</h1>
|
||||
<p class="lead"><span class="icon-info-sign"></span> {% trans "When you merge a revision with the current, all data will be retained from both versions and merged at its approximate location from each revision." %} <strong>{% trans "After this, it's important to do a manual review." %}</strong></p>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<iframe name="mergeWindow"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#" class="btn btn-large" data-dismiss="modal">
|
||||
<span class="icon-circle-arrow-left"></span>
|
||||
{% trans "Back to history view" %}
|
||||
</a>
|
||||
{% if article|can_write:user %}
|
||||
<a href="#" class="btn btn-large btn-primary merge-revision-commit">
|
||||
<span class="icon-file"></span>
|
||||
{% trans "Create new merged version" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="btn btn-large btn-primary disabled">
|
||||
<span class="icon-lock"></span>
|
||||
<section id="mergeModal" class="modal" aria-hidden="true">
|
||||
<div class="inner-wrapper" role="dialog" aria-labelledby="merge-title">
|
||||
<button class="close-modal">✕ <span class="sr">{% trans 'Close Modal' %}</span></button>
|
||||
|
||||
<header>
|
||||
<h2 id="merge-title">{% trans "Merge Revision" %}<span class="sr">, {% trans "modal open" %}</span></h2>
|
||||
<hr/>
|
||||
</header>
|
||||
<div class="modal-header">
|
||||
<h1>{% trans "Merge with current" %}</h1>
|
||||
<p class="lead"><span class="icon-info-sign"></span> {% trans "When you merge a revision with the current, all data will be retained from both versions and merged at its approximate location from each revision." %} <strong>{% trans "After this, it's important to do a manual review." %}</strong></p>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<iframe name="mergeWindow"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a id="mergeModalBackToHistory" href="#" class="btn btn-large" data-dismiss="modal">
|
||||
<span class="icon-circle-arrow-left"></span>
|
||||
{% trans "Back to history view" %}
|
||||
</a>
|
||||
{% if article|can_write:user %}
|
||||
<a href="#" class="btn btn-large btn-primary merge-revision-commit">
|
||||
<span class="icon-file"></span>
|
||||
{% trans "Create new merged version" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="btn btn-large btn-primary disabled">
|
||||
<span class="icon-lock"></span>
|
||||
{% trans "Create new merged version" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
1. {% trans "Ordered" %}
|
||||
2. {% trans "List" %}</pre>
|
||||
<pre>
|
||||
> {% trans "Quotes" %}</pre>
|
||||
> {% trans "Quotes" %}</pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -41,9 +41,6 @@ urlpatterns = ('', # nopep8
|
||||
url(r'^create_account$', 'student.views.create_account', name='create_account'),
|
||||
url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account', name="activate"),
|
||||
|
||||
url(r'^begin_exam_registration/(?P<course_id>[^/]+/[^/]+/[^/]+)$', 'student.views.begin_exam_registration', name="begin_exam_registration"),
|
||||
url(r'^create_exam_registration$', 'student.views.create_exam_registration'),
|
||||
|
||||
url(r'^password_reset/$', 'student.views.password_reset', name='password_reset'),
|
||||
## Obsolete Django views for password resets
|
||||
## TODO: Replace with Mako-ized views
|
||||
@@ -404,9 +401,6 @@ if settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'):
|
||||
url(r'^openid/provider/xrds/$', 'external_auth.views.provider_xrds', name='openid-provider-xrds')
|
||||
)
|
||||
|
||||
if settings.MITX_FEATURES.get('ENABLE_PEARSON_LOGIN', False):
|
||||
urlpatterns += url(r'^testcenter/login$', 'external_auth.views.test_center_login'),
|
||||
|
||||
if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
|
||||
urlpatterns += (
|
||||
url(r'^migrate/modules$', 'lms_migration.migrate.manage_modulestores'),
|
||||
|
||||
Reference in New Issue
Block a user