Commit 03d04c1f by Calen Pennington

Merge pull request #392 from MITx/feature/victor/dark-launch

Feature/victor/dark launch
parents 9433eafb b0dc60ee
from staticfiles.storage import staticfiles_storage
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from pipeline.conf import settings from pipeline.conf import settings
from pipeline.packager import Packager from pipeline.packager import Packager
from pipeline.utils import guess_type from pipeline.utils import guess_type
from static_replace import try_staticfiles_lookup
def compressed_css(package_name): def compressed_css(package_name):
...@@ -25,9 +24,11 @@ def compressed_css(package_name): ...@@ -25,9 +24,11 @@ def compressed_css(package_name):
def render_css(package, path): def render_css(package, path):
template_name = package.template_name or "mako/css.html" template_name = package.template_name or "mako/css.html"
context = package.extra_context context = package.extra_context
url = try_staticfiles_lookup(path)
context.update({ context.update({
'type': guess_type(path, 'text/css'), 'type': guess_type(path, 'text/css'),
'url': staticfiles_storage.url(path) 'url': url,
}) })
return render_to_string(template_name, context) return render_to_string(template_name, context)
...@@ -58,7 +59,7 @@ def render_js(package, path): ...@@ -58,7 +59,7 @@ def render_js(package, path):
context = package.extra_context context = package.extra_context
context.update({ context.update({
'type': guess_type(path, 'text/javascript'), 'type': guess_type(path, 'text/javascript'),
'url': staticfiles_storage.url(path) 'url': try_staticfiles_lookup(path)
}) })
return render_to_string(template_name, context) return render_to_string(template_name, context)
......
import logging
import re
from staticfiles.storage import staticfiles_storage from staticfiles.storage import staticfiles_storage
from staticfiles import finders from staticfiles import finders
from django.conf import settings from django.conf import settings
import re
log = logging.getLogger(__name__)
def try_staticfiles_lookup(path):
"""
Try to lookup a path in staticfiles_storage. If it fails, return
a dead link instead of raising an exception.
"""
try:
url = staticfiles_storage.url(path)
except Exception as err:
log.warning("staticfiles_storage couldn't find path {}: {}".format(
path, str(err)))
# Just return a dead link--don't kill everything.
url = "file_not_found"
return url
def replace(static_url, prefix=None): def replace(static_url, prefix=None):
...@@ -22,7 +40,8 @@ def replace(static_url, prefix=None): ...@@ -22,7 +40,8 @@ def replace(static_url, prefix=None):
if servable: if servable:
return static_url.group(0) return static_url.group(0)
else: else:
url = staticfiles_storage.url(prefix + static_url.group('rest')) # don't error if file can't be found
url = try_staticfiles_lookup(prefix + static_url.group('rest'))
return "".join([quote, url, quote]) return "".join([quote, url, quote])
......
...@@ -10,17 +10,16 @@ from django.http import Http404 ...@@ -10,17 +10,16 @@ from django.http import Http404
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from static_replace import replace_urls from static_replace import replace_urls, try_staticfiles_lookup
from staticfiles.storage import staticfiles_storage
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def check_course(course_id, course_must_be_open=True, course_required=True): def check_course(user, course_id, course_must_be_open=True, course_required=True):
""" """
Given a course_id, this returns the course object. By default, Given a django user and a course_id, this returns the course
if the course is not found or the course is not open yet, this object. By default, if the course is not found or the course is
method will raise a 404. not open yet, this method will raise a 404.
If course_must_be_open is False, the course will be returned If course_must_be_open is False, the course will be returned
without a 404 even if it is not open. without a 404 even if it is not open.
...@@ -28,6 +27,10 @@ def check_course(course_id, course_must_be_open=True, course_required=True): ...@@ -28,6 +27,10 @@ def check_course(course_id, course_must_be_open=True, course_required=True):
If course_required is False, a course_id of None is acceptable. The If course_required is False, a course_id of None is acceptable. The
course returned will be None. Even if the course is not required, course returned will be None. Even if the course is not required,
if a course_id is given that does not exist a 404 will be raised. if a course_id is given that does not exist a 404 will be raised.
This behavior is modified by MITX_FEATURES['DARK_LAUNCH']:
if dark launch is enabled, course_must_be_open is ignored for
users that have staff access.
""" """
course = None course = None
if course_required or course_id: if course_required or course_id:
...@@ -39,16 +42,23 @@ def check_course(course_id, course_must_be_open=True, course_required=True): ...@@ -39,16 +42,23 @@ def check_course(course_id, course_must_be_open=True, course_required=True):
raise Http404("Course not found.") raise Http404("Course not found.")
started = course.has_started() or settings.MITX_FEATURES['DISABLE_START_DATES'] started = course.has_started() or settings.MITX_FEATURES['DISABLE_START_DATES']
if course_must_be_open and not started:
must_be_open = course_must_be_open
if (settings.MITX_FEATURES['DARK_LAUNCH'] and
has_staff_access_to_course(user, course)):
must_be_open = False
if must_be_open and not started:
raise Http404("This course has not yet started.") raise Http404("This course has not yet started.")
return course return course
def course_image_url(course): def course_image_url(course):
return staticfiles_storage.url(course.metadata['data_dir'] + """Try to look up the image url for the course. If it's not found,
"/images/course_image.jpg") log an error and return the dead link"""
path = course.metadata['data_dir'] + "/images/course_image.jpg"
return try_staticfiles_lookup(path)
def get_course_about_section(course, section_key): def get_course_about_section(course, section_key):
""" """
......
import copy import copy
import json import json
from path import path
import os import os
import sys
import time
from pprint import pprint
from nose import SkipTest from nose import SkipTest
from path import path
from pprint import pprint
from django.contrib.auth.models import User, Group
from django.test import TestCase from django.test import TestCase
from django.test.client import Client from django.test.client import Client
from django.conf import settings from django.conf import settings
...@@ -13,12 +16,11 @@ from django.core.urlresolvers import reverse ...@@ -13,12 +16,11 @@ from django.core.urlresolvers import reverse
from mock import patch, Mock from mock import patch, Mock
from override_settings import override_settings from override_settings import override_settings
from django.contrib.auth.models import User, Group import xmodule.modulestore.django
from student.models import Registration from student.models import Registration
from courseware.courses import course_staff_group_name from courseware.courses import course_staff_group_name
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
import xmodule.modulestore.django
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
...@@ -206,13 +208,14 @@ class TestCoursesLoadTestCase(PageLoader): ...@@ -206,13 +208,14 @@ class TestCoursesLoadTestCase(PageLoader):
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
class TestInstructorAuth(PageLoader): class TestViewAuth(PageLoader):
"""Check that authentication works properly""" """Check that view authentication works properly"""
# NOTE: setUpClass() runs before override_settings takes effect, so # NOTE: setUpClass() runs before override_settings takes effect, so
# can't do imports there without manually hacking settings. # can't do imports there without manually hacking settings.
def setUp(self): def setUp(self):
print "sys.path: {}".format(sys.path)
xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django._MODULESTORES = {}
modulestore().collection.drop() modulestore().collection.drop()
import_from_xml(modulestore(), TEST_DATA_DIR, ['toy']) import_from_xml(modulestore(), TEST_DATA_DIR, ['toy'])
...@@ -237,12 +240,16 @@ class TestInstructorAuth(PageLoader): ...@@ -237,12 +240,16 @@ class TestInstructorAuth(PageLoader):
# TODO (vshnayder): once we're returning 404s, get rid of this if. # TODO (vshnayder): once we're returning 404s, get rid of this if.
if code != 404: if code != 404:
self.assertEqual(resp.status_code, code) self.assertEqual(resp.status_code, code)
# And 'page not found' shouldn't be in the returned page
self.assertTrue(resp.content.lower().find('page not found') == -1)
else: else:
# look for "page not found" instead of the status code # look for "page not found" instead of the status code
#print resp.content
self.assertTrue(resp.content.lower().find('page not found') != -1) self.assertTrue(resp.content.lower().find('page not found') != -1)
def test_instructor_page(self): def test_instructor_pages(self):
"Make sure only instructors can load it" """Make sure only instructors for the course or staff can load the instructor
dashboard, the grade views, and student profile pages"""
# First, try with an enrolled student # First, try with an enrolled student
self.login(self.student, self.password) self.login(self.student, self.password)
...@@ -297,7 +304,125 @@ class TestInstructorAuth(PageLoader): ...@@ -297,7 +304,125 @@ class TestInstructorAuth(PageLoader):
self.check_for_get_code(200, url) self.check_for_get_code(200, url)
def test_dark_launch(self):
"""Make sure that when dark launch is on, students can't access course
pages, but instructors can"""
# test.py turns off start dates, enable them and set them correctly.
# Because settings is global, be careful not to mess it up for other tests
# (Can't use override_settings because we're only changing part of the
# MITX_FEATURES dict)
oldDSD = settings.MITX_FEATURES['DISABLE_START_DATES']
oldDL = settings.MITX_FEATURES['DARK_LAUNCH']
try:
settings.MITX_FEATURES['DISABLE_START_DATES'] = False
settings.MITX_FEATURES['DARK_LAUNCH'] = True
self._do_test_dark_launch()
finally:
settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD
settings.MITX_FEATURES['DARK_LAUNCH'] = oldDL
def _do_test_dark_launch(self):
"""Actually do the test, relying on settings to be right."""
# Make courses start in the future
tomorrow = time.time() + 24*3600
self.toy.start = self.toy.metadata['start'] = time.gmtime(tomorrow)
self.full.start = self.full.metadata['start'] = time.gmtime(tomorrow)
self.assertFalse(self.toy.has_started())
self.assertFalse(self.full.has_started())
self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES'])
self.assertTrue(settings.MITX_FEATURES['DARK_LAUNCH'])
def reverse_urls(names, course):
return [reverse(name, kwargs={'course_id': course.id}) for name in names]
def dark_student_urls(course):
"""
list of urls that students should be able to see only
after launch, but staff should see before
"""
urls = reverse_urls(['info', 'book', 'courseware', 'profile'], course)
return urls
def light_student_urls(course):
"""
list of urls that students should be able to see before
launch.
"""
urls = reverse_urls(['about_course'], course)
urls.append(reverse('courses'))
# Need separate test for change_enrollment, since it's a POST view
#urls.append(reverse('change_enrollment'))
return urls
def instructor_urls(course):
"""list of urls that only instructors/staff should be able to see"""
urls = reverse_urls(['instructor_dashboard','gradebook','grade_summary'],
course)
urls.append(reverse('student_profile', kwargs={'course_id': course.id,
'student_id': user(self.student).id}))
return urls
def check_non_staff(course):
"""Check that access is right for non-staff in course"""
print '=== Checking non-staff access for {}'.format(course.id)
for url in instructor_urls(course) + dark_student_urls(course):
print 'checking for 404 on {}'.format(url)
self.check_for_get_code(404, url)
for url in light_student_urls(course):
print 'checking for 200 on {}'.format(url)
self.check_for_get_code(200, url)
def check_staff(course):
"""Check that access is right for staff in course"""
print '=== Checking staff access for {}'.format(course.id)
for url in (instructor_urls(course) +
dark_student_urls(course) +
light_student_urls(course)):
print 'checking for 200 on {}'.format(url)
self.check_for_get_code(200, url)
# First, try with an enrolled student
print '=== Testing student access....'
self.login(self.student, self.password)
self.enroll(self.toy)
self.enroll(self.full)
# shouldn't be able to get to anything except the light pages
check_non_staff(self.toy)
check_non_staff(self.full)
print '=== Testing course instructor access....'
# Make the instructor staff in the toy course
group_name = course_staff_group_name(self.toy)
g = Group.objects.create(name=group_name)
g.user_set.add(user(self.instructor))
self.logout()
self.login(self.instructor, self.password)
# Enroll in the classes---can't see courseware otherwise.
self.enroll(self.toy)
self.enroll(self.full)
# should now be able to get to everything for toy course
check_non_staff(self.full)
check_staff(self.toy)
print '=== Testing staff access....'
# now also make the instructor staff
u = user(self.instructor)
u.is_staff = True
u.save()
# and now should be able to load both
check_staff(self.toy)
check_staff(self.full)
@override_settings(MODULESTORE=REAL_DATA_MODULESTORE) @override_settings(MODULESTORE=REAL_DATA_MODULESTORE)
......
...@@ -110,9 +110,10 @@ def index(request, course_id, chapter=None, section=None, ...@@ -110,9 +110,10 @@ def index(request, course_id, chapter=None, section=None,
- HTTPresponse - HTTPresponse
''' '''
course = check_course(course_id) course = check_course(request.user, course_id)
registered = registered_for_course(course, request.user) registered = registered_for_course(course, request.user)
if not registered: if not registered:
# TODO (vshnayder): do course instructors need to be registered to see course?
log.debug('User %s tried to view course %s but is not enrolled' % (request.user,course.location.url())) log.debug('User %s tried to view course %s but is not enrolled' % (request.user,course.location.url()))
return redirect(reverse('about_course', args=[course.id])) return redirect(reverse('about_course', args=[course.id]))
...@@ -203,7 +204,7 @@ def course_info(request, course_id): ...@@ -203,7 +204,7 @@ def course_info(request, course_id):
Assumes the course_id is in a valid format. Assumes the course_id is in a valid format.
""" """
course = check_course(course_id) course = check_course(request.user, course_id)
return render_to_response('info.html', {'course': course}) return render_to_response('info.html', {'course': course})
...@@ -220,7 +221,7 @@ def registered_for_course(course, user): ...@@ -220,7 +221,7 @@ def registered_for_course(course, user):
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_if_anonymous @cache_if_anonymous
def course_about(request, course_id): def course_about(request, course_id):
course = check_course(course_id, course_must_be_open=False) course = check_course(request.user, course_id, course_must_be_open=False)
registered = registered_for_course(course, request.user) registered = registered_for_course(course, request.user)
return render_to_response('portal/course_about.html', {'course': course, 'registered': registered}) return render_to_response('portal/course_about.html', {'course': course, 'registered': registered})
...@@ -252,7 +253,7 @@ def profile(request, course_id, student_id=None): ...@@ -252,7 +253,7 @@ def profile(request, course_id, student_id=None):
Course staff are allowed to see the profiles of students in their class. Course staff are allowed to see the profiles of students in their class.
""" """
course = check_course(course_id) course = check_course(request.user, course_id)
if student_id is None or student_id == request.user.id: if student_id is None or student_id == request.user.id:
# always allowed to see your own profile # always allowed to see your own profile
...@@ -299,7 +300,7 @@ def gradebook(request, course_id): ...@@ -299,7 +300,7 @@ def gradebook(request, course_id):
if not has_staff_access_to_course_id(request.user, course_id): if not has_staff_access_to_course_id(request.user, course_id):
raise Http404 raise Http404
course = check_course(course_id) course = check_course(request.user, course_id)
enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).order_by('username') enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).order_by('username')
...@@ -324,7 +325,7 @@ def grade_summary(request, course_id): ...@@ -324,7 +325,7 @@ def grade_summary(request, course_id):
if not has_staff_access_to_course_id(request.user, course_id): if not has_staff_access_to_course_id(request.user, course_id):
raise Http404 raise Http404
course = check_course(course_id) course = check_course(request.user, course_id)
# For now, just a static page # For now, just a static page
context = {'course': course } context = {'course': course }
...@@ -337,7 +338,7 @@ def instructor_dashboard(request, course_id): ...@@ -337,7 +338,7 @@ def instructor_dashboard(request, course_id):
if not has_staff_access_to_course_id(request.user, course_id): if not has_staff_access_to_course_id(request.user, course_id):
raise Http404 raise Http404
course = check_course(course_id) course = check_course(request.user, course_id)
# For now, just a static page # For now, just a static page
context = {'course': course } context = {'course': course }
......
...@@ -51,7 +51,7 @@ def update_template_dictionary(dictionary, request=None, course=None, article=No ...@@ -51,7 +51,7 @@ def update_template_dictionary(dictionary, request=None, course=None, article=No
def view(request, article_path, course_id=None): def view(request, article_path, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
(article, err) = get_article(request, article_path, course) (article, err) = get_article(request, article_path, course)
if err: if err:
...@@ -67,7 +67,7 @@ def view(request, article_path, course_id=None): ...@@ -67,7 +67,7 @@ def view(request, article_path, course_id=None):
def view_revision(request, revision_number, article_path, course_id=None): def view_revision(request, revision_number, article_path, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
(article, err) = get_article(request, article_path, course) (article, err) = get_article(request, article_path, course)
if err: if err:
...@@ -91,7 +91,7 @@ def view_revision(request, revision_number, article_path, course_id=None): ...@@ -91,7 +91,7 @@ def view_revision(request, revision_number, article_path, course_id=None):
def root_redirect(request, course_id=None): def root_redirect(request, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
#TODO: Add a default namespace to settings. #TODO: Add a default namespace to settings.
namespace = course.wiki_namespace if course else "edX" namespace = course.wiki_namespace if course else "edX"
...@@ -109,7 +109,7 @@ def root_redirect(request, course_id=None): ...@@ -109,7 +109,7 @@ def root_redirect(request, course_id=None):
def create(request, article_path, course_id=None): def create(request, article_path, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
article_path_components = article_path.split('/') article_path_components = article_path.split('/')
...@@ -170,7 +170,7 @@ def create(request, article_path, course_id=None): ...@@ -170,7 +170,7 @@ def create(request, article_path, course_id=None):
def edit(request, article_path, course_id=None): def edit(request, article_path, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
(article, err) = get_article(request, article_path, course) (article, err) = get_article(request, article_path, course)
if err: if err:
...@@ -218,7 +218,7 @@ def edit(request, article_path, course_id=None): ...@@ -218,7 +218,7 @@ def edit(request, article_path, course_id=None):
def history(request, article_path, page=1, course_id=None): def history(request, article_path, page=1, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
(article, err) = get_article(request, article_path, course) (article, err) = get_article(request, article_path, course)
if err: if err:
...@@ -300,7 +300,7 @@ def history(request, article_path, page=1, course_id=None): ...@@ -300,7 +300,7 @@ def history(request, article_path, page=1, course_id=None):
def revision_feed(request, page=1, namespace=None, course_id=None): def revision_feed(request, page=1, namespace=None, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
page_size = 10 page_size = 10
...@@ -333,7 +333,7 @@ def revision_feed(request, page=1, namespace=None, course_id=None): ...@@ -333,7 +333,7 @@ def revision_feed(request, page=1, namespace=None, course_id=None):
def search_articles(request, namespace=None, course_id=None): def search_articles(request, namespace=None, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
# blampe: We should check for the presence of other popular django search # blampe: We should check for the presence of other popular django search
# apps and use those if possible. Only fall back on this as a last resort. # apps and use those if possible. Only fall back on this as a last resort.
...@@ -382,7 +382,7 @@ def search_articles(request, namespace=None, course_id=None): ...@@ -382,7 +382,7 @@ def search_articles(request, namespace=None, course_id=None):
def search_add_related(request, course_id, slug, namespace): def search_add_related(request, course_id, slug, namespace):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
(article, err) = get_article(request, slug, namespace if namespace else course_id) (article, err) = get_article(request, slug, namespace if namespace else course_id)
if err: if err:
...@@ -415,7 +415,7 @@ def search_add_related(request, course_id, slug, namespace): ...@@ -415,7 +415,7 @@ def search_add_related(request, course_id, slug, namespace):
def add_related(request, course_id, slug, namespace): def add_related(request, course_id, slug, namespace):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
(article, err) = get_article(request, slug, namespace if namespace else course_id) (article, err) = get_article(request, slug, namespace if namespace else course_id)
if err: if err:
...@@ -439,7 +439,7 @@ def add_related(request, course_id, slug, namespace): ...@@ -439,7 +439,7 @@ def add_related(request, course_id, slug, namespace):
def remove_related(request, course_id, namespace, slug, related_id): def remove_related(request, course_id, namespace, slug, related_id):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
(article, err) = get_article(request, slug, namespace if namespace else course_id) (article, err) = get_article(request, slug, namespace if namespace else course_id)
...@@ -462,7 +462,7 @@ def remove_related(request, course_id, namespace, slug, related_id): ...@@ -462,7 +462,7 @@ def remove_related(request, course_id, namespace, slug, related_id):
def random_article(request, course_id=None): def random_article(request, course_id=None):
course = check_course(course_id, course_required=False) course = check_course(request.user, course_id, course_required=False)
from random import randint from random import randint
num_arts = Article.objects.count() num_arts = Article.objects.count()
......
...@@ -6,7 +6,7 @@ from lxml import etree ...@@ -6,7 +6,7 @@ from lxml import etree
@login_required @login_required
def index(request, course_id, page=0): def index(request, course_id, page=0):
course = check_course(course_id) course = check_course(request.user, course_id)
raw_table_of_contents = open('lms/templates/book_toc.xml', 'r') # TODO: This will need to come from S3 raw_table_of_contents = open('lms/templates/book_toc.xml', 'r') # TODO: This will need to come from S3
table_of_contents = etree.parse(raw_table_of_contents).getroot() table_of_contents = etree.parse(raw_table_of_contents).getroot()
return render_to_response('staticbook.html', {'page': int(page), 'course': course, 'table_of_contents': table_of_contents}) return render_to_response('staticbook.html', {'page': int(page), 'course': course, 'table_of_contents': table_of_contents})
......
...@@ -48,6 +48,7 @@ MITX_FEATURES = { ...@@ -48,6 +48,7 @@ MITX_FEATURES = {
## DO NOT SET TO True IN THIS FILE ## DO NOT SET TO True IN THIS FILE
## Doing so will cause all courses to be released on production ## Doing so will cause all courses to be released on production
'DISABLE_START_DATES': False, # When True, all courses will be active, regardless of start date 'DISABLE_START_DATES': False, # When True, all courses will be active, regardless of start date
'DARK_LAUNCH': False, # When True, courses will be active for staff only
'ENABLE_TEXTBOOK' : True, 'ENABLE_TEXTBOOK' : True,
'ENABLE_DISCUSSION' : True, 'ENABLE_DISCUSSION' : True,
......
...@@ -97,12 +97,16 @@ if settings.PERFSTATS: ...@@ -97,12 +97,16 @@ if settings.PERFSTATS:
if settings.COURSEWARE_ENABLED: if settings.COURSEWARE_ENABLED:
urlpatterns += ( urlpatterns += (
# Hook django-masquerade, allowing staff to view site as other users
url(r'^masquerade/', include('masquerade.urls')), url(r'^masquerade/', include('masquerade.urls')),
url(r'^jump_to/(?P<location>.*)$', 'courseware.views.jump_to', name="jump_to"), url(r'^jump_to/(?P<location>.*)$', 'courseware.views.jump_to', name="jump_to"),
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'), url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$',
url(r'^xqueue/(?P<userid>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.xqueue_callback'), 'courseware.module_render.modx_dispatch', name='modx_dispatch'),
url(r'^change_setting$', 'student.views.change_setting'), url(r'^xqueue/(?P<userid>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$',
'courseware.module_render.xqueue_callback', name='xqueue_callback'),
url(r'^change_setting$', 'student.views.change_setting',
name='change_setting'),
# TODO: These views need to be updated before they work # TODO: These views need to be updated before they work
# url(r'^calculate$', 'util.views.calculate'), # url(r'^calculate$', 'util.views.calculate'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment