Commit 259e2136 by Calen Pennington

Merge pull request #461 from MITx/feature/victor/multi-run-policy

Keep each course's descriptors separate
parents 9bde5e7a 55b1a59c
...@@ -102,7 +102,7 @@ def main_index(request, extra_context={}, user=None): ...@@ -102,7 +102,7 @@ def main_index(request, extra_context={}, user=None):
def course_from_id(course_id): def course_from_id(course_id):
"""Return the CourseDescriptor corresponding to this course_id""" """Return the CourseDescriptor corresponding to this course_id"""
course_loc = CourseDescriptor.id_to_location(course_id) course_loc = CourseDescriptor.id_to_location(course_id)
return modulestore().get_item(course_loc) return modulestore().get_instance(course_id, course_loc)
def press(request): def press(request):
......
...@@ -135,6 +135,10 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -135,6 +135,10 @@ class CourseDescriptor(SequenceDescriptor):
@staticmethod @staticmethod
def make_id(org, course, url_name):
return '/'.join([org, course, url_name])
@staticmethod
def id_to_location(course_id): def id_to_location(course_id):
'''Convert the given course_id (org/course/name) to a location object. '''Convert the given course_id (org/course/name) to a location object.
Throws ValueError if course_id is of the wrong format. Throws ValueError if course_id is of the wrong format.
......
...@@ -223,6 +223,13 @@ class ModuleStore(object): ...@@ -223,6 +223,13 @@ class ModuleStore(object):
""" """
raise NotImplementedError raise NotImplementedError
def get_instance(self, course_id, location):
"""
Get an instance of this location, with policy for course_id applied.
TODO (vshnayder): this may want to live outside the modulestore eventually
"""
raise NotImplementedError
def get_item_errors(self, location): def get_item_errors(self, location):
""" """
Return a list of (msg, exception-or-None) errors that the modulestore Return a list of (msg, exception-or-None) errors that the modulestore
...@@ -331,7 +338,8 @@ class ModuleStoreBase(ModuleStore): ...@@ -331,7 +338,8 @@ class ModuleStoreBase(ModuleStore):
and datastores. and datastores.
""" """
# check that item is present and raise the promised exceptions if needed # check that item is present and raise the promised exceptions if needed
self.get_item(location) # TODO (vshnayder): post-launch, make errors properties of items
#self.get_item(location)
errorlog = self._get_errorlog(location) errorlog = self._get_errorlog(location)
return errorlog.errors return errorlog.errors
...@@ -217,6 +217,13 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -217,6 +217,13 @@ class MongoModuleStore(ModuleStoreBase):
item = self._find_one(location) item = self._find_one(location)
return self._load_items([item], depth)[0] return self._load_items([item], depth)[0]
def get_instance(self, course_id, location):
"""
TODO (vshnayder): implement policy tracking in mongo.
For now, just delegate to get_item and ignore policy.
"""
return self.get_item(location)
def get_items(self, location, depth=0): def get_items(self, location, depth=0):
items = self.collection.find( items = self.collection.find(
location_to_query(location), location_to_query(location),
......
...@@ -3,6 +3,7 @@ import logging ...@@ -3,6 +3,7 @@ import logging
import os import os
import re import re
from collections import defaultdict
from fs.osfs import OSFS from fs.osfs import OSFS
from importlib import import_module from importlib import import_module
from lxml import etree from lxml import etree
...@@ -33,7 +34,7 @@ def clean_out_mako_templating(xml_string): ...@@ -33,7 +34,7 @@ def clean_out_mako_templating(xml_string):
return xml_string return xml_string
class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
def __init__(self, xmlstore, org, course, course_dir, def __init__(self, xmlstore, course_id, course_dir,
policy, error_tracker, **kwargs): policy, error_tracker, **kwargs):
""" """
A class that handles loading from xml. Does some munging to ensure that A class that handles loading from xml. Does some munging to ensure that
...@@ -43,6 +44,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -43,6 +44,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
""" """
self.unnamed_modules = 0 self.unnamed_modules = 0
self.used_slugs = set() self.used_slugs = set()
self.org, self.course, self.url_name = course_id.split('/')
def process_xml(xml): def process_xml(xml):
"""Takes an xml string, and returns a XModuleDescriptor created from """Takes an xml string, and returns a XModuleDescriptor created from
...@@ -80,21 +82,24 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -80,21 +82,24 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
xml_data.set('url_name', slug) xml_data.set('url_name', slug)
descriptor = XModuleDescriptor.load_from_xml( descriptor = XModuleDescriptor.load_from_xml(
etree.tostring(xml_data), self, org, etree.tostring(xml_data), self, self.org,
course, xmlstore.default_class) self.course, xmlstore.default_class)
#log.debug('==> importing descriptor location %s' % #log.debug('==> importing descriptor location %s' %
# repr(descriptor.location)) # repr(descriptor.location))
descriptor.metadata['data_dir'] = course_dir descriptor.metadata['data_dir'] = course_dir
xmlstore.modules[descriptor.location] = descriptor xmlstore.modules[course_id][descriptor.location] = descriptor
if xmlstore.eager: if xmlstore.eager:
descriptor.get_children() descriptor.get_children()
return descriptor return descriptor
render_template = lambda: '' render_template = lambda: ''
load_item = xmlstore.get_item # TODO (vshnayder): we are somewhat architecturally confused in the loading code:
# load_item should actually be get_instance, because it expects the course-specific
# policy to be loaded. For now, just add the course_id here...
load_item = lambda location: xmlstore.get_instance(course_id, location)
resources_fs = OSFS(xmlstore.data_dir / course_dir) resources_fs = OSFS(xmlstore.data_dir / course_dir)
MakoDescriptorSystem.__init__(self, load_item, resources_fs, MakoDescriptorSystem.__init__(self, load_item, resources_fs,
...@@ -127,7 +132,7 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -127,7 +132,7 @@ class XMLModuleStore(ModuleStoreBase):
self.eager = eager self.eager = eager
self.data_dir = path(data_dir) self.data_dir = path(data_dir)
self.modules = {} # location -> XModuleDescriptor self.modules = defaultdict(dict) # course_id -> dict(location -> XModuleDescriptor)
self.courses = {} # course_dir -> XModuleDescriptor for the course self.courses = {} # course_dir -> XModuleDescriptor for the course
if default_class is None: if default_class is None:
...@@ -236,14 +241,24 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -236,14 +241,24 @@ class XMLModuleStore(ModuleStoreBase):
tracker(msg) tracker(msg)
course = course_dir course = course_dir
url_name = course_data.get('url_name') url_name = course_data.get('url_name', course_data.get('slug'))
if url_name: if url_name:
policy_path = self.data_dir / course_dir / 'policies' / '{0}.json'.format(url_name) policy_path = self.data_dir / course_dir / 'policies' / '{0}.json'.format(url_name)
policy = self.load_policy(policy_path, tracker) policy = self.load_policy(policy_path, tracker)
else: else:
policy = {} policy = {}
# VS[compat] : 'name' is deprecated, but support it for now...
if course_data.get('name'):
url_name = Location.clean(course_data.get('name'))
tracker("'name' is deprecated for module xml. Please use "
"display_name and url_name.")
else:
raise ValueError("Can't load a course without a 'url_name' "
"(or 'name') set. Set url_name.")
system = ImportSystem(self, org, course, course_dir, policy, tracker) course_id = CourseDescriptor.make_id(org, course, url_name)
system = ImportSystem(self, course_id, course_dir, policy, tracker)
course_descriptor = system.process_xml(etree.tostring(course_data)) course_descriptor = system.process_xml(etree.tostring(course_data))
...@@ -257,11 +272,12 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -257,11 +272,12 @@ class XMLModuleStore(ModuleStoreBase):
return course_descriptor return course_descriptor
def get_item(self, location, depth=0): def get_instance(self, course_id, location, depth=0):
""" """
Returns an XModuleDescriptor instance for the item at location. Returns an XModuleDescriptor instance for the item at
If location.revision is None, returns the most item with the most location, with the policy for course_id. (In case two xml
recent revision dirs have different content at the same location, return the
one for this course_id.)
If any segment of the location is None except revision, raises If any segment of the location is None except revision, raises
xmodule.modulestore.exceptions.InsufficientSpecificationError xmodule.modulestore.exceptions.InsufficientSpecificationError
...@@ -273,10 +289,27 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -273,10 +289,27 @@ class XMLModuleStore(ModuleStoreBase):
""" """
location = Location(location) location = Location(location)
try: try:
return self.modules[location] return self.modules[course_id][location]
except KeyError: except KeyError:
raise ItemNotFoundError(location) raise ItemNotFoundError(location)
def get_item(self, location, depth=0):
"""
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
xmodule.modulestore.exceptions.InsufficientSpecificationError
If no object is found at that location, raises
xmodule.modulestore.exceptions.ItemNotFoundError
location: Something that can be passed to Location
"""
raise NotImplementedError("XMLModuleStores can't guarantee that definitions"
" are unique. Use get_instance.")
def get_courses(self, depth=0): def get_courses(self, depth=0):
""" """
......
...@@ -22,21 +22,22 @@ def import_from_xml(store, data_dir, course_dirs=None, eager=True, ...@@ -22,21 +22,22 @@ def import_from_xml(store, data_dir, course_dirs=None, eager=True,
eager=eager, eager=eager,
course_dirs=course_dirs course_dirs=course_dirs
) )
for module in module_store.modules.itervalues(): for course_id in module_store.modules.keys():
for module in module_store.modules[course_id].itervalues():
# TODO (cpennington): This forces import to overrite the same items.
# This should in the future create new revisions of the items on import # TODO (cpennington): This forces import to overrite the same items.
try: # This should in the future create new revisions of the items on import
store.create_item(module.location) try:
except DuplicateItemError: store.create_item(module.location)
log.exception('Item already exists at %s' % module.location.url()) except DuplicateItemError:
pass log.exception('Item already exists at %s' % module.location.url())
if 'data' in module.definition: pass
store.update_item(module.location, module.definition['data']) if 'data' in module.definition:
if 'children' in module.definition: store.update_item(module.location, module.definition['data'])
store.update_children(module.location, module.definition['children']) if 'children' in module.definition:
# NOTE: It's important to use own_metadata here to avoid writing store.update_children(module.location, module.definition['children'])
# inherited metadata everywhere. # NOTE: It's important to use own_metadata here to avoid writing
store.update_metadata(module.location, dict(module.own_metadata)) # inherited metadata everywhere.
store.update_metadata(module.location, dict(module.own_metadata))
return module_store return module_store
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import unittest import unittest
import os import os
import fs import fs
import fs.osfs
import json import json
import json import json
......
...@@ -84,20 +84,22 @@ class RoundTripTestCase(unittest.TestCase): ...@@ -84,20 +84,22 @@ class RoundTripTestCase(unittest.TestCase):
strip_filenames(exported_course) strip_filenames(exported_course)
self.assertEquals(initial_course, exported_course) self.assertEquals(initial_course, exported_course)
self.assertEquals(initial_course.id, exported_course.id)
course_id = initial_course.id
print "Checking key equality" print "Checking key equality"
self.assertEquals(sorted(initial_import.modules.keys()), self.assertEquals(sorted(initial_import.modules[course_id].keys()),
sorted(second_import.modules.keys())) sorted(second_import.modules[course_id].keys()))
print "Checking module equality" print "Checking module equality"
for location in initial_import.modules.keys(): for location in initial_import.modules[course_id].keys():
print "Checking", location print "Checking", location
if location.category == 'html': if location.category == 'html':
print ("Skipping html modules--they can't import in" print ("Skipping html modules--they can't import in"
" final form without writing files...") " final form without writing files...")
continue continue
self.assertEquals(initial_import.modules[location], self.assertEquals(initial_import.modules[course_id][location],
second_import.modules[location]) second_import.modules[course_id][location])
def setUp(self): def setUp(self):
......
...@@ -207,3 +207,48 @@ class ImportTestCase(unittest.TestCase): ...@@ -207,3 +207,48 @@ class ImportTestCase(unittest.TestCase):
check_for_key(key, c) check_for_key(key, c)
check_for_key('graceperiod', course) check_for_key('graceperiod', course)
def test_policy_loading(self):
"""Make sure that when two courses share content with the same
org and course names, policy applies to the right one."""
def get_course(name):
print "Importing {0}".format(name)
modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=[name])
courses = modulestore.get_courses()
self.assertEquals(len(courses), 1)
return courses[0]
toy = get_course('toy')
two_toys = get_course('two_toys')
self.assertEqual(toy.url_name, "2012_Fall")
self.assertEqual(two_toys.url_name, "TT_2012_Fall")
toy_ch = toy.get_children()[0]
two_toys_ch = two_toys.get_children()[0]
self.assertEqual(toy_ch.display_name, "Overview")
self.assertEqual(two_toys_ch.display_name, "Two Toy Overview")
def test_definition_loading(self):
"""When two courses share the same org and course name and
both have a module with the same url_name, the definitions shouldn't clash.
TODO (vshnayder): once we have a CMS, this shouldn't
happen--locations should uniquely name definitions. But in
our imperfect XML world, it can (and likely will) happen."""
modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=['toy', 'two_toys'])
toy_id = "edX/toy/2012_Fall"
two_toy_id = "edX/toy/TT_2012_Fall"
location = Location(["i4x", "edX", "toy", "video", "Welcome"])
toy_video = modulestore.get_instance(toy_id, location)
two_toy_video = modulestore.get_instance(two_toy_id, location)
self.assertEqual(toy_video.metadata['youtube'], "1.0:p2Q6BrNhdh8")
self.assertEqual(two_toy_video.metadata['youtube'], "1.0:p2Q6BrNhdh9")
A copy of the toy course, with different metadata. Used to test policy loading for course with identical org course fields and shared content.
<chapter>
<videosequence url_name="Toy_Videos"/>
<video url_name="Welcome"/>
</chapter>
<course url_name="TT_2012_Fall" org="edX" course="toy"/>
<course display_name="Toy Course" graceperiod="2 days 5 hours 59 minutes 59 seconds" start="2015-07-17T12:00">
<chapter url_name="Overview"/>
</course>
{
"course/TT_2012_Fall": {
"graceperiod": "2 days 5 hours 59 minutes 59 seconds",
"start": "2015-07-17T12:00",
"display_name": "Two Toys Course"
},
"chapter/Overview": {
"display_name": "Two Toy Overview"
},
"videosequence/Toy_Videos": {
"display_name": "Toy Videos",
"format": "Lecture Sequence"
},
"html/toylab": {
"display_name": "Toy lab"
},
"video/Video_Resources": {
"display_name": "Video Resources"
},
"video/Welcome": {
"display_name": "Welcome"
}
}
<video youtube="1.0:1bK-WdDi6Qw" display_name="Video Resources"/>
<video youtube="1.0:p2Q6BrNhdh9" display_name="Welcome"/>
<videosequence display_name="Toy Videos" format="Lecture Sequence">
<video url_name="Video_Resources"/>
</videosequence>
...@@ -25,7 +25,7 @@ def get_course_by_id(course_id): ...@@ -25,7 +25,7 @@ def get_course_by_id(course_id):
""" """
try: try:
course_loc = CourseDescriptor.id_to_location(course_id) course_loc = CourseDescriptor.id_to_location(course_id)
return modulestore().get_item(course_loc) return modulestore().get_instance(course_id, course_loc)
except (KeyError, ItemNotFoundError): except (KeyError, ItemNotFoundError):
raise Http404("Course not found.") raise Http404("Course not found.")
......
...@@ -9,6 +9,7 @@ from django.conf import settings ...@@ -9,6 +9,7 @@ from django.conf import settings
from models import StudentModuleCache from models import StudentModuleCache
from module_render import get_module, get_instance_module from module_render import get_module, get_instance_module
from xmodule import graders from xmodule import graders
from xmodule.course_module import CourseDescriptor
from xmodule.graders import Score from xmodule.graders import Score
from models import StudentModule from models import StudentModule
...@@ -63,8 +64,10 @@ def grade(student, request, course, student_module_cache=None): ...@@ -63,8 +64,10 @@ def grade(student, request, course, student_module_cache=None):
scores = [] scores = []
# TODO: We need the request to pass into here. If we could forgo that, our arguments # TODO: We need the request to pass into here. If we could forgo that, our arguments
# would be simpler # would be simpler
course_id = CourseDescriptor.location_to_id(course.location)
section_module = get_module(student, request, section_module = get_module(student, request,
section_descriptor.location, student_module_cache) section_descriptor.location, student_module_cache,
course_id)
if section_module is None: if section_module is None:
# student doesn't have access to this module, or something else # student doesn't have access to this module, or something else
# went wrong. # went wrong.
......
import os.path import os.path
# THIS COMMAND IS OUT OF DATE
from lxml import etree from lxml import etree
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
...@@ -72,13 +74,17 @@ class Command(BaseCommand): ...@@ -72,13 +74,17 @@ class Command(BaseCommand):
# TODO: use args as list of files to check. Fix loading to work for other files. # TODO: use args as list of files to check. Fix loading to work for other files.
print "This command needs updating before use"
return
"""
sample_user = User.objects.all()[0] sample_user = User.objects.all()[0]
print "Attempting to load courseware" print "Attempting to load courseware"
# TODO (cpennington): Get coursename in a legitimate way # TODO (cpennington): Get coursename in a legitimate way
course_location = 'i4x://edx/6002xs12/course/6.002_Spring_2012' course_location = 'i4x://edx/6002xs12/course/6.002_Spring_2012'
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(sample_user, modulestore().get_item(course_location)) student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
sample_user, modulestore().get_item(course_location))
course = get_module(sample_user, None, course_location, student_module_cache) course = get_module(sample_user, None, course_location, student_module_cache)
to_run = [ to_run = [
...@@ -96,3 +102,4 @@ class Command(BaseCommand): ...@@ -96,3 +102,4 @@ class Command(BaseCommand):
print 'Courseware passes all checks!' print 'Courseware passes all checks!'
else: else:
print "Courseware fails some checks" print "Courseware fails some checks"
"""
...@@ -124,9 +124,11 @@ def check_roundtrip(course_dir): ...@@ -124,9 +124,11 @@ def check_roundtrip(course_dir):
print "======== ideally there is no diff above this =======" print "======== ideally there is no diff above this ======="
def clean_xml(course_dir, export_dir): def clean_xml(course_dir, export_dir, force):
(ok, course) = import_with_checks(course_dir) (ok, course) = import_with_checks(course_dir)
if ok: if ok or force:
if not ok:
print "WARNING: Exporting despite errors"
export(course, export_dir) export(course, export_dir)
check_roundtrip(export_dir) check_roundtrip(export_dir)
else: else:
...@@ -138,11 +140,18 @@ class Command(BaseCommand): ...@@ -138,11 +140,18 @@ class Command(BaseCommand):
help = """Imports specified course.xml, validate it, then exports in help = """Imports specified course.xml, validate it, then exports in
a canonical format. a canonical format.
Usage: clean_xml PATH-TO-COURSE-DIR PATH-TO-OUTPUT-DIR Usage: clean_xml PATH-TO-COURSE-DIR PATH-TO-OUTPUT-DIR [force]
If 'force' is specified as the last argument, exports even if there
were import errors.
""" """
def handle(self, *args, **options): def handle(self, *args, **options):
if len(args) != 2: n = len(args)
if n < 2 or n > 3:
print Command.help print Command.help
return return
clean_xml(args[0], args[1]) force = False
if n == 3 and args[2] == 'force':
force = True
clean_xml(args[0], args[1], force)
...@@ -71,13 +71,13 @@ def toc_for_course(user, request, course, active_chapter, active_section, course ...@@ -71,13 +71,13 @@ def toc_for_course(user, request, course, active_chapter, active_section, course
''' '''
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(user, course, depth=2) student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(user, course, depth=2)
course = get_module(user, request, course.location, student_module_cache, course_id=course_id) course = get_module(user, request, course.location, student_module_cache, course_id)
chapters = list() chapters = list()
for chapter in course.get_display_items(): for chapter in course.get_display_items():
hide_from_toc = chapter.metadata.get('hide_from_toc','false').lower() == 'true' hide_from_toc = chapter.metadata.get('hide_from_toc','false').lower() == 'true'
if hide_from_toc: if hide_from_toc:
continue continue
sections = list() sections = list()
for section in chapter.get_display_items(): for section in chapter.get_display_items():
...@@ -131,7 +131,7 @@ def get_section(course_module, chapter, section): ...@@ -131,7 +131,7 @@ def get_section(course_module, chapter, section):
return section_module return section_module
def get_module(user, request, location, student_module_cache, position=None, course_id=None): def get_module(user, request, location, student_module_cache, course_id, position=None):
''' Get an instance of the xmodule class identified by location, ''' Get an instance of the xmodule class identified by location,
setting the state based on an existing StudentModule, or creating one if none setting the state based on an existing StudentModule, or creating one if none
exists. exists.
...@@ -141,21 +141,14 @@ def get_module(user, request, location, student_module_cache, position=None, cou ...@@ -141,21 +141,14 @@ def get_module(user, request, location, student_module_cache, position=None, cou
- request : current django HTTPrequest - request : current django HTTPrequest
- location : A Location-like object identifying the module to load - location : A Location-like object identifying the module to load
- student_module_cache : a StudentModuleCache - student_module_cache : a StudentModuleCache
- course_id : the course_id in the context of which to load module
- position : extra information from URL for user-specified - position : extra information from URL for user-specified
position within module position within module
Returns: xmodule instance Returns: xmodule instance
''' '''
descriptor = modulestore().get_item(location) descriptor = modulestore().get_instance(course_id, location)
# NOTE:
# A 'course_id' is understood to be the triplet (org, course, run), for example
# (MITx, 6.002x, 2012_Spring).
# At the moment generic XModule does not contain enough information to replicate
# the triplet (it is missing 'run'), so we must pass down course_id
if course_id is None:
course_id = descriptor.location.course_id # Will NOT produce (org, course, run) for non-CourseModule's
# Short circuit--if the user shouldn't have access, bail without doing any work # Short circuit--if the user shouldn't have access, bail without doing any work
if not has_access(user, descriptor, 'load'): if not has_access(user, descriptor, 'load'):
...@@ -207,7 +200,7 @@ def get_module(user, request, location, student_module_cache, position=None, cou ...@@ -207,7 +200,7 @@ def get_module(user, request, location, student_module_cache, position=None, cou
Delegate to get_module. It does an access check, so may return None Delegate to get_module. It does an access check, so may return None
""" """
return get_module(user, request, location, return get_module(user, request, location,
student_module_cache, position, course_id=course_id) student_module_cache, course_id, position)
# TODO (cpennington): When modules are shared between courses, the static # TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory # prefix is going to have to be specific to the module, not the directory
...@@ -325,7 +318,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch): ...@@ -325,7 +318,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
user, modulestore().get_item(id), depth=0, select_for_update=True) user, modulestore().get_item(id), depth=0, select_for_update=True)
instance = get_module(user, request, id, student_module_cache) instance = get_module(user, request, id, student_module_cache, course_id)
if instance is None: if instance is None:
log.debug("No module {0} for user {1}--access denied?".format(id, user)) log.debug("No module {0} for user {1}--access denied?".format(id, user))
raise Http404 raise Http404
...@@ -361,7 +354,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch): ...@@ -361,7 +354,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
return HttpResponse("") return HttpResponse("")
def modx_dispatch(request, dispatch=None, id=None, course_id=None): def modx_dispatch(request, dispatch, location, course_id):
''' Generic view for extensions. This is where AJAX calls go. ''' Generic view for extensions. This is where AJAX calls go.
Arguments: Arguments:
...@@ -370,7 +363,8 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None): ...@@ -370,7 +363,8 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
- dispatch -- the command string to pass through to the module's handle_ajax call - dispatch -- the command string to pass through to the module's handle_ajax call
(e.g. 'problem_reset'). If this string contains '?', only pass (e.g. 'problem_reset'). If this string contains '?', only pass
through the part before the first '?'. through the part before the first '?'.
- id -- the module id. Used to look up the XModule instance - location -- the module location. Used to look up the XModule instance
- course_id -- defines the course context for this request.
''' '''
# ''' (fix emacs broken parsing) # ''' (fix emacs broken parsing)
...@@ -392,12 +386,14 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None): ...@@ -392,12 +386,14 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
return HttpResponse(json.dumps({'success': file_too_big_msg})) return HttpResponse(json.dumps({'success': file_too_big_msg}))
p[fileinput_id] = inputfiles p[fileinput_id] = inputfiles
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, modulestore().get_item(id)) student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
instance = get_module(request.user, request, id, student_module_cache, course_id=course_id) request.user, modulestore().get_item(course_id, location))
instance = get_module(request.user, request, location, student_module_cache, course_id)
if instance is None: if instance is None:
# Either permissions just changed, or someone is trying to be clever # Either permissions just changed, or someone is trying to be clever
# and load something they shouldn't have access to. # and load something they shouldn't have access to.
log.debug("No module {0} for user {1}--access denied?".format(id, user)) log.debug("No module {0} for user {1}--access denied?".format(location, user))
raise Http404 raise Http404
instance_module = get_instance_module(request.user, instance, student_module_cache) instance_module = get_instance_module(request.user, instance, student_module_cache)
......
...@@ -64,7 +64,7 @@ def courses(request): ...@@ -64,7 +64,7 @@ def courses(request):
''' '''
Render "find courses" page. The course selection work is done in courseware.courses. Render "find courses" page. The course selection work is done in courseware.courses.
''' '''
universities = get_courses_by_university(request.user, universities = get_courses_by_university(request.user,
domain=request.META.get('HTTP_HOST')) domain=request.META.get('HTTP_HOST'))
return render_to_response("courses.html", {'universities': universities}) return render_to_response("courses.html", {'universities': universities})
...@@ -139,7 +139,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -139,7 +139,7 @@ def index(request, course_id, chapter=None, section=None,
section_descriptor) section_descriptor)
module = get_module(request.user, request, module = get_module(request.user, request,
section_descriptor.location, section_descriptor.location,
student_module_cache, course_id=course_id) student_module_cache, course_id)
if module is None: if module is None:
# User is probably being clever and trying to access something # User is probably being clever and trying to access something
# they don't have access to. # they don't have access to.
...@@ -277,9 +277,11 @@ def progress(request, course_id, student_id=None): ...@@ -277,9 +277,11 @@ def progress(request, course_id, student_id=None):
student = User.objects.get(id=int(student_id)) student = User.objects.get(id=int(student_id))
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, course) student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, course)
course_module = get_module(request.user, request, course.location, student_module_cache, course_id=course_id) course_module = get_module(request.user, request, course.location,
student_module_cache, course_id)
courseware_summary = grades.progress_summary(student, course_module, course.grader, student_module_cache) courseware_summary = grades.progress_summary(student, course_module,
course.grader, student_module_cache)
grade_summary = grades.grade(request.user, request, course, student_module_cache) grade_summary = grades.grade(request.user, request, course, student_module_cache)
context = {'course': course, context = {'course': course,
......
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