Commit 1c3e43de by Calen Pennington

WIP: Merging multicourse and xmodule descriptor changes

The basic form has been fleshed out. Things still to do:
1) Extend get_course interface to the rest of modulestore
2) Make courses actually load
3) Make org/course/maybe others metadata attributes for course_module (see xml_module), so
that they are loaded/saved on import/export to xml
parent 3b28a17f
"""
This module provides an abstraction for working with XModuleDescriptors
that are stored in a database an accessible using their Location as an identifier
"""
import re
from .exceptions import InvalidLocationError
URL_RE = re.compile("""
(?P<tag>[^:]+)://
(?P<org>[^/]+)/
(?P<course>[^/]+)/
(?P<category>[^/]+)/
(?P<name>[^/]+)
(/(?P<revision>[^/]+))?
""", re.VERBOSE)
class Location(object):
'''
Encodes a location.
Locations representations of URLs of the
form {tag}://{org}/{course}/{category}/{name}[/{revision}]
However, they can also be represented a dictionaries (specifying each component),
tuples or list (specified in order), or as strings of the url
'''
def __init__(self, location):
"""
Create a new location that is a clone of the specifed one.
location - Can be any of the following types:
string: should be of the form {tag}://{org}/{course}/{category}/{name}[/{revision}]
list: should be of the form [tag, org, course, category, name, revision]
dict: should be of the form {
'tag': tag,
'org': org,
'course': course,
'category': category,
'name': name,
'revision': revision,
}
Location: another Location object
In both the dict and list forms, the revision is optional, and can be ommitted.
None of the components of a location may contain the '/' character
Components may be set to None, which may be interpreted by some contexts to mean
wildcard selection
"""
self.update(location)
def update(self, location):
"""
Update this instance with data from another Location object.
location: can take the same forms as specified by `__init__`
"""
self.tag = self.org = self.course = self.category = self.name = self.revision = None
if isinstance(location, basestring):
match = URL_RE.match(location)
if match is None:
raise InvalidLocationError(location)
else:
self.update(match.groupdict())
elif isinstance(location, list):
if len(location) not in (5, 6):
raise InvalidLocationError(location)
(self.tag, self.org, self.course, self.category, self.name) = location[0:5]
self.revision = location[5] if len(location) == 6 else None
elif isinstance(location, dict):
try:
self.tag = location['tag']
self.org = location['org']
self.course = location['course']
self.category = location['category']
self.name = location['name']
except KeyError:
raise InvalidLocationError(location)
self.revision = location.get('revision')
elif isinstance(location, Location):
self.update(location.list())
else:
raise InvalidLocationError(location)
for val in self.list():
if val is not None and '/' in val:
raise InvalidLocationError(location)
def __str__(self):
return self.url()
def url(self):
"""
Return a string containing the URL for this location
"""
url = "{tag}://{org}/{course}/{category}/{name}".format(**self.dict())
if self.revision:
url += "/" + self.revision
return url
def list(self):
"""
Return a list representing this location
"""
return [self.tag, self.org, self.course, self.category, self.name, self.revision]
def dict(self):
"""
Return a dictionary representing this location
"""
return {'tag': self.tag,
'org': self.org,
'course': self.course,
'category': self.category,
'name': self.name,
'revision': self.revision}
class ModuleStore(object):
"""
An abstract interface for a database backend that stores XModuleDescriptor instances
"""
def get_item(self, location):
"""
Returns an XModuleDescriptor instance for the item at location.
If location.revision is None, returns the item with the most
recent revision
If any segment of the location is None except revision, raises
keystore.exceptions.InsufficientSpecificationError
If no object is found at that location, raises keystore.exceptions.ItemNotFoundError
location: Something that can be passed to Location
"""
raise NotImplementedError
# TODO (cpennington): Replace with clone_item
def create_item(self, location, editor):
raise NotImplementedError
def update_item(self, location, data):
"""
Set the data in the item specified by the location to
data
location: Something that can be passed to Location
data: A nested dictionary of problem data
"""
raise NotImplementedError
def update_children(self, location, children):
"""
Set the children for the item specified by the location to
data
location: Something that can be passed to Location
children: A list of child item identifiers
"""
raise NotImplementedError
"""
Module that provides a connection to the keystore specified in the django settings.
Passes settings.KEYSTORE as kwargs to MongoModuleStore
"""
from __future__ import absolute_import
from django.conf import settings
from .mongo import MongoModuleStore
_KEYSTORES = {}
def keystore(name='default'):
global _KEYSTORES
if name not in _KEYSTORES:
_KEYSTORES[name] = MongoModuleStore(**settings.KEYSTORE[name])
return _KEYSTORES[name]
"""
Exceptions thrown by KeyStore objects
"""
class ItemNotFoundError(Exception):
pass
class InsufficientSpecificationError(Exception):
pass
class InvalidLocationError(Exception):
pass
import pymongo
from . import ModuleStore, Location
from .exceptions import ItemNotFoundError, InsufficientSpecificationError
from xmodule.x_module import XModuleDescriptor, DescriptorSystem
class MongoModuleStore(ModuleStore):
"""
A Mongodb backed ModuleStore
"""
def __init__(self, host, db, collection, port=27017):
self.collection = pymongo.connection.Connection(
host=host,
port=port
)[db][collection]
# Force mongo to report errors, at the expense of performance
self.collection.safe = True
def get_item(self, location):
"""
Returns an XModuleDescriptor instance for the item at location.
If location.revision is None, returns the most item with the most
recent revision
If any segment of the location is None except revision, raises
keystore.exceptions.InsufficientSpecificationError
If no object is found at that location, raises keystore.exceptions.ItemNotFoundError
location: Something that can be passed to Location
"""
query = {}
for key, val in Location(location).dict().iteritems():
if key != 'revision' and val is None:
raise InsufficientSpecificationError(location)
if val is not None:
query['location.{key}'.format(key=key)] = val
item = self.collection.find_one(
query,
sort=[('revision', pymongo.ASCENDING)],
)
if item is None:
raise ItemNotFoundError(location)
return XModuleDescriptor.load_from_json(item, DescriptorSystem(self.get_item))
def create_item(self, location, editor):
"""
Create an empty item at the specified location with the supplied editor
location: Something that can be passed to Location
"""
self.collection.insert({
'location': Location(location).dict(),
'editor': editor
})
def update_item(self, location, data):
"""
Set the data in the item specified by the location to
data
location: Something that can be passed to Location
data: A nested dictionary of problem data
"""
# See http://www.mongodb.org/display/DOCS/Updating for
# atomic update syntax
self.collection.update(
{'location': Location(location).dict()},
{'$set': {'definition.data': data}}
)
def update_children(self, location, children):
"""
Set the children for the item specified by the location to
data
location: Something that can be passed to Location
children: A list of child item identifiers
"""
# See http://www.mongodb.org/display/DOCS/Updating for
# atomic update syntax
self.collection.update(
{'location': Location(location).dict()},
{'$set': {'definition.children': children}}
)
from nose.tools import assert_equals, assert_raises
from keystore import Location
from keystore.exceptions import InvalidLocationError
def check_string_roundtrip(url):
assert_equals(url, Location(url).url())
assert_equals(url, str(Location(url)))
def test_string_roundtrip():
check_string_roundtrip("tag://org/course/category/name")
check_string_roundtrip("tag://org/course/category/name/revision")
check_string_roundtrip("tag://org/course/category/name with spaces/revision")
def test_dict():
input_dict = {
'tag': 'tag',
'course': 'course',
'category': 'category',
'name': 'name',
'org': 'org'
}
assert_equals("tag://org/course/category/name", Location(input_dict).url())
assert_equals(dict(revision=None, **input_dict), Location(input_dict).dict())
input_dict['revision'] = 'revision'
assert_equals("tag://org/course/category/name/revision", Location(input_dict).url())
assert_equals(input_dict, Location(input_dict).dict())
def test_list():
input_list = ['tag', 'org', 'course', 'category', 'name']
assert_equals("tag://org/course/category/name", Location(input_list).url())
assert_equals(input_list + [None], Location(input_list).list())
input_list.append('revision')
assert_equals("tag://org/course/category/name/revision", Location(input_list).url())
assert_equals(input_list, Location(input_list).list())
def test_location():
input_list = ['tag', 'org', 'course', 'category', 'name']
assert_equals("tag://org/course/category/name", Location(Location(input_list)).url())
def test_invalid_locations():
assert_raises(InvalidLocationError, Location, "foo")
assert_raises(InvalidLocationError, Location, ["foo", "bar"])
assert_raises(InvalidLocationError, Location, ["foo", "bar", "baz", "blat", "foo/bar"])
assert_raises(InvalidLocationError, Location, None)
......@@ -16,7 +16,7 @@ setup(
"abtest = xmodule.abtest_module:ABTestDescriptor",
"book = xmodule.translation_module:TranslateCustomTagDescriptor",
"chapter = xmodule.seq_module:SequenceDescriptor",
"course = xmodule.seq_module:SequenceDescriptor",
"course = xmodule.course_module:CourseDescriptor",
"customtag = xmodule.template_module:CustomTagDescriptor",
"discuss = xmodule.translation_module:TranslateCustomTagDescriptor",
"html = xmodule.html_module:HtmlDescriptor",
......
from path import path
from xmodule.modulestore import Location
from xmodule.seq_module import SequenceDescriptor, SequenceModule
class CourseDescriptor(SequenceDescriptor):
module_class = SequenceModule
@classmethod
def id_to_location(cls, course_id):
org, course, name = course_id.split('/')
return Location('i4x', org, course, 'course', name)
@property
def id(self):
return "/".join([self.location.org, self.location.course, self.location.name])
@property
def name(self):
self.metadata['display_name']
def get_about_section(self, section_key):
"""
This returns the snippet of html to be rendered on the course about page, given the key for the section.
Valid keys:
- title
- university
- number
- short_description
- description
- key_dates (includes start, end, exams, etc)
- video
- course_staff_short
- course_staff_extended
- requirements
- syllabus
- textbook
- faq
- more_info
"""
# Many of these are stored as html files instead of some semantic markup. This can change without effecting
# this interface when we find a good format for defining so many snippets of text/html.
if section_key in ['short_description', 'description', 'key_dates', 'video', 'course_staff_short', 'course_staff_extended',
'requirements', 'syllabus', 'textbook', 'faq', 'more_info']:
try:
with self.system.resources_fs.open(path("about") / section_key + ".html") as htmlFile:
return htmlFile.read()
except IOError:
return "! About section missing !"
elif section_key == "title":
return self.name
elif section_key == "university":
return self.org
elif section_key == "number":
return self.number
raise KeyError("Invalid about key " + str(section_key))
......@@ -5,6 +5,7 @@ from lxml import etree
from path import path
from xmodule.x_module import XModuleDescriptor, XMLParsingSystem
from xmodule.mako_module import MakoDescriptorSystem
import os
from . import ModuleStore, Location
from .exceptions import ItemNotFoundError
......@@ -19,17 +20,19 @@ class XMLModuleStore(ModuleStore):
"""
An XML backed ModuleStore
"""
def __init__(self, org, course, data_dir, default_class=None, eager=False):
def __init__(self, data_dir, default_class=None, eager=False):
"""
Initialize an XMLModuleStore from data_dir
org, course: Strings to be used in module keys
data_dir: path to data directory containing course.xml
data_dir: path to data directory containing the course directories
default_class: dot-separated string defining the default descriptor class to use if non is specified in entry_points
eager: If true, load the modules children immediately to force the entire course tree to be parsed
"""
self.eager = eager
self.data_dir = path(data_dir)
self.modules = {}
self.courses = []
if default_class is None:
self.default_class = None
......@@ -38,7 +41,25 @@ class XMLModuleStore(ModuleStore):
class_ = getattr(import_module(module_path), class_name)
self.default_class = class_
with open(self.data_dir / "course.xml") as course_file:
for course_dir in os.listdir(self.data_dir):
if not os.path.isdir(course_dir):
continue
self.load_course(course_dir)
def load_course(self, course_dir):
"""
Load a course into this module store
course_path: Course directory name
"""
with open(self.data_dir / course_dir / "course.xml") as course_file:
course_data = etree.parse(course_file).getroot()
org = course_data.get('org')
course = course_data.get('course')
class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
def __init__(self, modulestore):
"""
......@@ -70,20 +91,20 @@ class XMLModuleStore(ModuleStore):
module = XModuleDescriptor.load_from_xml(etree.tostring(xml_data), self, org, course, modulestore.default_class)
modulestore.modules[module.location] = module
if eager:
if self.eager:
module.get_children()
return module
system_kwargs = dict(
render_template=lambda: '',
load_item=modulestore.get_item,
resources_fs=OSFS(data_dir),
resources_fs=OSFS(self.data_dir / course_dir),
process_xml=process_xml
)
MakoDescriptorSystem.__init__(self, **system_kwargs)
XMLParsingSystem.__init__(self, **system_kwargs)
self.course = ImportSystem(self).process_xml(course_file.read())
return ImportSystem(self).process_xml(etree.tostring(course_data))
def get_item(self, location):
"""
......@@ -103,6 +124,12 @@ class XMLModuleStore(ModuleStore):
except KeyError:
raise ItemNotFoundError(location)
def get_courses(self):
"""
Returns a list of course descriptors
"""
return self.courses
def create_item(self, location):
raise NotImplementedError("XMLModuleStores are read-only")
......
......@@ -27,68 +27,6 @@ class Course(namedtuple('Course', _FIELDS)):
"""Course objects encapsulate general information about a given run of a
course. This includes things like name, grading policy, etc.
"""
@property
def id(self):
return "{0.institution},{0.number},{0.run_id}".format(self).replace(" ", "_")
@property
def name(self):
return "{0.number} {0.run_id}".format(self)
@classmethod
def load_from_path(cls, course_path):
course_path = path(course_path) # convert it from string if necessary
try:
with open(course_path / "course_info.yaml") as course_info_file:
course_info = yaml.load(course_info_file)
summary = course_info['course']
summary.update(path=course_path, grader=None)
return cls(**summary)
except Exception as ex:
log.exception(ex)
raise CourseInfoLoadError("Could not read course info: {0}:{1}"
.format(type(ex).__name__, ex))
def get_about_section(self, section_key):
"""
This returns the snippet of html to be rendered on the course about page, given the key for the section.
Valid keys:
- title
- university
- number
- short_description
- description
- key_dates (includes start, end, exams, etc)
- video
- course_staff_short
- course_staff_extended
- requirements
- syllabus
- textbook
- faq
- more_info
"""
# Many of these are stored as html files instead of some semantic markup. This can change without effecting
# this interface when we find a good format for defining so many snippets of text/html.
if section_key in ['short_description', 'description', 'key_dates', 'video', 'course_staff_short', 'course_staff_extended',
'requirements', 'syllabus', 'textbook', 'faq', 'more_info']:
try:
with open(self.path / "about" / section_key + ".html") as htmlFile:
return htmlFile.read()
except IOError:
return "! About section missing !"
elif section_key == "title":
return self.title
elif section_key == "university":
return self.institution
elif section_key == "number":
return self.number
raise KeyError("Invalid about key " + str(section_key))
def load_courses(courses_path):
"""Given a directory of courses, returns a list of Course objects. For the
......
......@@ -17,6 +17,7 @@ from models import StudentModuleCache
from student.models import UserProfile
from multicourse import multicourse_settings
from xmodule.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor
from util.cache import cache
from student.models import UserTestGroup
......@@ -51,24 +52,24 @@ def format_url_params(params):
@ensure_csrf_cookie
def courses(request):
csrf_token = csrf(request)['csrf_token']
# TODO: Clean up how 'error' is done.
context = {'courses' : settings.COURSES,
'csrf' : csrf_token}
return render_to_response("courses.html", context)
csrf_token = csrf(request)['csrf_token']
# TODO: Clean up how 'error' is done.
context = {'courses': modulestore().get_courses(),
'csrf': csrf_token}
return render_to_response("courses.html", context)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def gradebook(request):
def gradebook(request, course_id):
if 'course_admin' not in user_groups(request.user):
raise Http404
course = settings.COURSES_BY_ID[course_id]
course_location = CourseDescriptor.id_to_location(course_id)
student_objects = User.objects.all()[:100]
student_info = []
for student in student_objects:
# TODO (cpennington): do the right thing with courses
student_module_cache = StudentModuleCache(student, modulestore().get_item(course_location))
course, _, _, _ = get_module(request.user, request, course_location, student_module_cache)
student_info.append({
......@@ -84,10 +85,11 @@ def gradebook(request):
@login_required
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def profile(request, student_id=None):
def profile(request, course_id, student_id=None):
''' User profile. Show username, location, etc, as well as grades .
We need to allow the user to change some of these settings .'''
course = settings.COURSES_BY_ID[course_id]
course_location = CourseDescriptor.id_to_location(course_id)
if student_id is None:
student = request.user
else:
......@@ -97,7 +99,6 @@ def profile(request, student_id=None):
user_info = UserProfile.objects.get(user=student)
# TODO (cpennington): do the right thing with courses
student_module_cache = StudentModuleCache(request.user, modulestore().get_item(course_location))
course, _, _, _ = get_module(request.user, request, course_location, student_module_cache)
......@@ -107,7 +108,7 @@ def profile(request, student_id=None):
'language': user_info.language,
'email': student.email,
'course': course,
'format_url_params': content_parser.format_url_params,
'format_url_params': format_url_params,
'csrf': csrf(request)['csrf_token']
}
context.update(grades.grade_sheet(student, course, student_module_cache))
......@@ -124,7 +125,7 @@ def render_accordion(request, course, chapter, section):
Returns (initialization_javascript, content)'''
# TODO (cpennington): do the right thing with courses
toc = toc_for_course(request.user, request, course_location, chapter, section)
toc = toc_for_course(request.user, request, course.location, chapter, section)
active_chapter = 1
for i in range(len(toc)):
......@@ -140,40 +141,10 @@ def render_accordion(request, course, chapter, section):
return render_to_string('accordion.html', context)
def get_module_xml(user, course, chapter, section):
''' Look up the module xml for the given course/chapter/section path.
Takes the user to look up the course file.
Returns None if there was a problem, or the lxml etree for the module.
'''
try:
# this is the course.xml etree
dom = content_parser.course_file(user, course)
except:
log.exception("Unable to parse courseware xml")
return None
# this is the module's parent's etree
path = "//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]"
dom_module = dom.xpath(path, course=course.name, chapter=chapter, section=section)
module_wrapper = dom_module[0] if len(dom_module) > 0 else None
if module_wrapper is None:
module = None
elif module_wrapper.get("src"):
module = content_parser.section_file(
user=user, section=module_wrapper.get("src"), coursename=course)
else:
# Copy the element out of the module's etree
module = etree.XML(etree.tostring(module_wrapper[0]))
return module
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def index(request, course=None, chapter=None, section=None,
position=None, course_id=None):
def index(request, course_id=None, chapter=None, section=None,
position=None):
''' Displays courseware accordion, and any associated content.
If course, chapter, and section aren't all specified, just returns
the accordion. If they are specified, returns an error if they don't
......@@ -198,6 +169,9 @@ def index(request, course=None, chapter=None, section=None,
'''
return s.replace('_', ' ') if s is not None else None
course_location = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_location)
if not settings.COURSEWARE_ENABLED:
return redirect('/')
......@@ -278,8 +252,9 @@ def course_info(request, course_id):
csrf_token = csrf(request)['csrf_token']
try:
course = settings.COURSES_BY_ID[course_id]
course_location = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_location)
except KeyError:
raise Http404("Course not found")
return render_to_response('info.html', {'csrf': csrf_token, 'course' : course})
return render_to_response('info.html', {'csrf': csrf_token, 'course': course})
from django.contrib.auth.decorators import login_required
from mitxmako.shortcuts import render_to_response
from django.conf import settings
@login_required
def index(request, course_id=None, page=0):
course = settings.COURSES_BY_ID[course_id]
course_location = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_location)
return render_to_response('staticbook.html',{'page':int(page), 'course': course})
def index_shifted(request, page):
......
......@@ -20,12 +20,15 @@ from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string
from django.core.urlresolvers import reverse
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
from django_future.csrf import ensure_csrf_cookie
from models import Registration, UserProfile, PendingNameChange, PendingEmailChange, CourseEnrollment
log = logging.getLogger("mitx.student")
def csrf_token(context):
''' A csrf token that can be included in a form.
'''
......@@ -34,6 +37,7 @@ def csrf_token(context):
return ''
return u'<div style="display:none"><input type="hidden" name="csrfmiddlewaretoken" value="%s" /></div>' % (csrf_token)
@ensure_csrf_cookie
def index(request):
''' Redirects to main page -- info page if user authenticated, or marketing if not
......@@ -43,26 +47,33 @@ def index(request):
else:
csrf_token = csrf(request)['csrf_token']
# TODO: Clean up how 'error' is done.
return render_to_response('index.html', {'courses' : settings.COURSES,
'csrf': csrf_token })
return render_to_response('index.html', {'courses': modulestore().get_courses(),
'csrf': csrf_token})
@ensure_csrf_cookie
def dashboard(request):
csrf_token = csrf(request)['csrf_token']
user = request.user
enrollments = CourseEnrollment.objects.filter(user=user)
courses = [settings.COURSES_BY_ID[enrollment.course_id] for enrollment in enrollments]
context = { 'csrf': csrf_token, 'courses': courses }
def course_from_id(id):
course_loc = CourseDescriptor.id_to_location(id)
return modulestore().get_item(course_loc)
courses = [course_from_id(enrollment.course_id) for enrollment in enrollments]
context = {'csrf': csrf_token, 'courses': courses}
return render_to_response('dashboard.html', context)
# Need different levels of logging
@ensure_csrf_cookie
def login_user(request, error=""):
''' AJAX request to log in the user. '''
if 'email' not in request.POST or 'password' not in request.POST:
return HttpResponse(json.dumps({'success':False,
'error': 'Invalid login'})) # TODO: User error message
return HttpResponse(json.dumps({'success': False,
'error': 'Invalid login'})) # TODO: User error message
email = request.POST['email']
password = request.POST['password']
......@@ -70,14 +81,14 @@ def login_user(request, error=""):
user = User.objects.get(email=email)
except User.DoesNotExist:
log.warning("Login failed - Unknown user email: {0}".format(email))
return HttpResponse(json.dumps({'success':False,
'error': 'Invalid login'})) # TODO: User error message
return HttpResponse(json.dumps({'success': False,
'error': 'Invalid login'})) # TODO: User error message
username = user.username
user = authenticate(username=username, password=password)
if user is None:
log.warning("Login failed - password for {0} is invalid".format(email))
return HttpResponse(json.dumps({'success':False,
return HttpResponse(json.dumps({'success': False,
'error': 'Invalid login'}))
if user is not None and user.is_active:
......@@ -416,6 +427,7 @@ def change_name_request(request):
pnc.save()
return HttpResponse(json.dumps({'success':True}))
@ensure_csrf_cookie
def pending_name_changes(request):
''' Web page which allows staff to approve or reject name changes. '''
......@@ -423,14 +435,15 @@ def pending_name_changes(request):
raise Http404
changes = list(PendingNameChange.objects.all())
js = {'students' : [{'new_name': c.new_name,
'rationale':c.rationale,
'old_name':UserProfile.objects.get(user=c.user).name,
'email':c.user.email,
'uid':c.user.id,
'cid':c.id} for c in changes]}
js = {'students': [{'new_name': c.new_name,
'rationale':c.rationale,
'old_name':UserProfile.objects.get(user=c.user).name,
'email':c.user.email,
'uid':c.user.id,
'cid':c.id} for c in changes]}
return render_to_response('name_changes.html', js)
@ensure_csrf_cookie
def reject_name_change(request):
''' JSON: Name change process. Course staff clicks 'reject' on a given name change '''
......@@ -438,12 +451,13 @@ def reject_name_change(request):
raise Http404
try:
pnc = PendingNameChange.objects.get(id = int(request.POST['id']))
pnc = PendingNameChange.objects.get(id=int(request.POST['id']))
except PendingNameChange.DoesNotExist:
return HttpResponse(json.dumps({'success':False, 'error':'Invalid ID'}))
return HttpResponse(json.dumps({'success': False, 'error': 'Invalid ID'}))
pnc.delete()
return HttpResponse(json.dumps({'success':True}))
return HttpResponse(json.dumps({'success': True}))
@ensure_csrf_cookie
def accept_name_change(request):
......@@ -452,9 +466,9 @@ def accept_name_change(request):
raise Http404
try:
pnc = PendingNameChange.objects.get(id = int(request.POST['id']))
pnc = PendingNameChange.objects.get(id=int(request.POST['id']))
except PendingNameChange.DoesNotExist:
return HttpResponse(json.dumps({'success':False, 'error':'Invalid ID'}))
return HttpResponse(json.dumps({'success': False, 'error': 'Invalid ID'}))
u = pnc.user
up = UserProfile.objects.get(user=u)
......@@ -470,30 +484,35 @@ def accept_name_change(request):
up.save()
pnc.delete()
return HttpResponse(json.dumps({'success':True}))
return HttpResponse(json.dumps({'success': True}))
@ensure_csrf_cookie
def course_info(request):
def course_info(request, course_id):
# This is the advertising page for a student to look at the course before signing up
csrf_token = csrf(request)['csrf_token']
course = settings.COURSES_BY_ID[course_id]
# TODO: Couse should be a model
return render_to_response('portal/course_about.html', {'csrf': csrf_token, 'course': course })
csrf_token = csrf(request)['csrf_token']
course_loc = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_loc)
# TODO: Couse should be a model
return render_to_response('portal/course_about.html', {'csrf': csrf_token, 'course': course})
def about(request):
return render_to_response('about.html', None)
return render_to_response('about.html', None)
def jobs(request):
return render_to_response('jobs.html', None)
return render_to_response('jobs.html', None)
def help(request):
return render_to_response('help.html', None)
return render_to_response('help.html', None)
@ensure_csrf_cookie
def enroll(request, course_id):
course = settings.COURSES_BY_ID[course_id]
user = request.user
enrollment = CourseEnrollment(user=user,
course_id=course_id)
enrollment.save()
return redirect(reverse('dashboard'))
user = request.user
enrollment = CourseEnrollment(user=user,
course_id=course_id)
enrollment.save()
return redirect(reverse('dashboard'))
......@@ -64,12 +64,6 @@ sys.path.append(PROJECT_ROOT / 'lib')
sys.path.append(COMMON_ROOT / 'djangoapps')
sys.path.append(COMMON_ROOT / 'lib')
######### EDX dormsbee/portal changes #################
from courseware.courses import create_lookup_table, load_courses
COURSES = load_courses(ENV_ROOT / "data")
COURSES_BY_ID = create_lookup_table(COURSES)
#######################################################
################################## MITXWEB #####################################
# This is where we stick our compiled template files. Most of the app uses Mako
# templates
......@@ -148,8 +142,6 @@ MODULESTORE = {
'default': {
'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
'OPTIONS': {
'org': 'edx',
'course': '6002xs12',
'data_dir': DATA_DIR,
'default_class': 'xmodule.hidden_module.HiddenDescriptor',
}
......
......@@ -53,10 +53,6 @@ if settings.COURSEWARE_ENABLED:
urlpatterns += (
url(r'^wiki/', include('simplewiki.urls')),
url(r'^masquerade/', include('masquerade.urls')),
url(r'^courseware/(?P<course>[^/]*)/(?P<chapter>[^/]*)/(?P<section>[^/]*)/(?P<position>[^/]*)$', 'courseware.views.index'),
# url(r'^courseware/(?P<course>[^/]*)/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', 'courseware.views.index', name="courseware_section"),
url(r'^courseware/(?P<course>[^/]*)/(?P<chapter>[^/]*)/$', 'courseware.views.index', name="courseware_chapter"),
url(r'^courseware/(?P<course>[^/]*)/$', 'courseware.views.index', name="courseware_course"),
url(r'^jumpto/(?P<probname>[^/]+)/$', 'courseware.views.jump_to'),
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
url(r'^change_setting$', 'student.views.change_setting'),
......@@ -73,15 +69,15 @@ if settings.COURSEWARE_ENABLED:
# Multicourse related:
url(r'^courses/?$', 'courseware.views.courses', name="courses"),
url(r'^courses/(?P<course_id>[^/]*)/info$', 'courseware.views.course_info', name="info"),
url(r'^courses/(?P<course_id>[^/]*)/book$', 'staticbook.views.index', name="book"),
url(r'^courses/(?P<course_id>[^/]*)/enroll$', 'student.views.enroll', name="enroll"),
url(r'^courses/(?P<course_id>[^/]*)/courseware/?$', 'courseware.views.index', name="courseware"),
url(r'^courses/(?P<course_id>[^/]*)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', 'courseware.views.index', name="courseware_section"),
url(r'^courses/(?P<course_id>[^/]*)/profile$', 'courseware.views.profile', name="profile"),
url(r'^courses/(?P<course_id>[^/]*)/profile/(?P<student_id>[^/]*)/$', 'courseware.views.profile'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/])/info$', 'courseware.views.course_info', name="info"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/])/book$', 'staticbook.views.index', name="book"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/])/enroll$', 'student.views.enroll', name="enroll"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/])/courseware/?$', 'courseware.views.index', name="courseware"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/])/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', 'courseware.views.index', name="courseware_section"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/])/profile$', 'courseware.views.profile', name="profile"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/])/profile/(?P<student_id>[^/]*)/$', 'courseware.views.profile'),
url(r'^courses/(?P<course_id>[^/]*)/about$', 'courseware.views.course_info', name="about_course"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/])/about$', 'courseware.views.course_info', name="about_course"),
)
if settings.ENABLE_MULTICOURSE:
......
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