Commit d2864c21 by David Ormsbee

Merge pull request #597 from MITx/feature/victor/nested-links

Feature/victor/nested links
parents e7c6404e 903161a7
...@@ -6,15 +6,14 @@ from .exceptions import (ItemNotFoundError, NoPathToItem) ...@@ -6,15 +6,14 @@ from .exceptions import (ItemNotFoundError, NoPathToItem)
from . import ModuleStore, Location from . import ModuleStore, Location
def path_to_location(modulestore, location, course_name=None): def path_to_location(modulestore, course_id, location):
''' '''
Try to find a course_id/chapter/section[/position] path to location in Try to find a course_id/chapter/section[/position] path to location in
modulestore. The courseware insists that the first level in the course is modulestore. The courseware insists that the first level in the course is
chapter, but any kind of module can be a "section". chapter, but any kind of module can be a "section".
location: something that can be passed to Location location: something that can be passed to Location
course_name: [optional]. If not None, restrict search to paths course_id: Search for paths in this course.
in that course.
raise ItemNotFoundError if the location doesn't exist. raise ItemNotFoundError if the location doesn't exist.
...@@ -27,7 +26,7 @@ def path_to_location(modulestore, location, course_name=None): ...@@ -27,7 +26,7 @@ def path_to_location(modulestore, location, course_name=None):
A location may be accessible via many paths. This method may A location may be accessible via many paths. This method may
return any valid path. return any valid path.
If the section is a sequence, position will be the position If the section is a sequential or vertical, position will be the position
of this location in that sequence. Otherwise, position will of this location in that sequence. Otherwise, position will
be None. TODO (vshnayder): Not true yet. be None. TODO (vshnayder): Not true yet.
''' '''
...@@ -41,7 +40,7 @@ def path_to_location(modulestore, location, course_name=None): ...@@ -41,7 +40,7 @@ def path_to_location(modulestore, location, course_name=None):
xs = xs[1] xs = xs[1]
return p return p
def find_path_to_course(location, course_name=None): def find_path_to_course():
'''Find a path up the location graph to a node with the '''Find a path up the location graph to a node with the
specified category. specified category.
...@@ -69,7 +68,8 @@ def path_to_location(modulestore, location, course_name=None): ...@@ -69,7 +68,8 @@ def path_to_location(modulestore, location, course_name=None):
# print 'Processing loc={0}, path={1}'.format(loc, path) # print 'Processing loc={0}, path={1}'.format(loc, path)
if loc.category == "course": if loc.category == "course":
if course_name is None or course_name == loc.name: # confirm that this is the right course
if course_id == CourseDescriptor.location_to_id(loc):
# Found it! # Found it!
path = (loc, path) path = (loc, path)
return flatten(path) return flatten(path)
...@@ -81,17 +81,34 @@ def path_to_location(modulestore, location, course_name=None): ...@@ -81,17 +81,34 @@ def path_to_location(modulestore, location, course_name=None):
# If we're here, there is no path # If we're here, there is no path
return None return None
path = find_path_to_course(location, course_name) path = find_path_to_course()
if path is None: if path is None:
raise(NoPathToItem(location)) raise NoPathToItem(location)
n = len(path) n = len(path)
course_id = CourseDescriptor.location_to_id(path[0]) course_id = CourseDescriptor.location_to_id(path[0])
# pull out the location names # pull out the location names
chapter = path[1].name if n > 1 else None chapter = path[1].name if n > 1 else None
section = path[2].name if n > 2 else None section = path[2].name if n > 2 else None
# Figure out the position
# TODO (vshnayder): not handling position at all yet...
position = None position = None
# This block of code will find the position of a module within a nested tree
# of modules. If a problem is on tab 2 of a sequence that's on tab 3 of a
# sequence, the resulting position is 3_2. However, no positional modules
# (e.g. sequential and videosequence) currently deal with this form of
# representing nested positions. This needs to happen before jumping to a
# module nested in more than one positional module will work.
if n > 3:
position_list = []
for path_index in range(2, n-1):
category = path[path_index].category
if category == 'sequential' or category == 'videosequence':
section_desc = modulestore.get_instance(course_id, path[path_index])
child_locs = [c.location for c in section_desc.get_children()]
# positions are 1-indexed, and should be strings to be consistent with
# url parsing.
position_list.append(str(child_locs.index(path[path_index+1]) + 1))
position = "_".join(position_list)
return (course_id, chapter, section, position) return (course_id, chapter, section, position)
from path import path
# from ~/mitx_all/mitx/common/lib/xmodule/xmodule/modulestore/tests/
# to ~/mitx_all/mitx/common/test
TEST_DIR = path(__file__).abspath().dirname()
for i in range(5):
TEST_DIR = TEST_DIR.dirname()
TEST_DIR = TEST_DIR / 'test'
DATA_DIR = TEST_DIR / 'data'
from nose.tools import assert_equals, assert_raises, assert_not_equals, with_setup
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from xmodule.modulestore.search import path_to_location
def check_path_to_location(modulestore):
'''Make sure that path_to_location works: should be passed a modulestore
with the toy and simple courses loaded.'''
should_work = (
("i4x://edX/toy/video/Welcome",
("edX/toy/2012_Fall", "Overview", "Welcome", None)),
("i4x://edX/toy/chapter/Overview",
("edX/toy/2012_Fall", "Overview", None, None)),
)
course_id = "edX/toy/2012_Fall"
for location, expected in should_work:
assert_equals(path_to_location(modulestore, course_id, location), expected)
not_found = (
"i4x://edX/toy/video/WelcomeX", "i4x://edX/toy/course/NotHome"
)
for location in not_found:
assert_raises(ItemNotFoundError, path_to_location, modulestore, course_id, location)
# Since our test files are valid, there shouldn't be any
# elements with no path to them. But we can look for them in
# another course.
no_path = (
"i4x://edX/simple/video/Lost_Video",
)
for location in no_path:
assert_raises(NoPathToItem, path_to_location, modulestore, course_id, location)
import pymongo import pymongo
from nose.tools import assert_equals, assert_raises, assert_not_equals, with_setup from nose.tools import assert_equals, assert_raises, assert_not_equals, with_setup
from path import path
from pprint import pprint from pprint import pprint
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
from xmodule.modulestore.mongo import MongoModuleStore from xmodule.modulestore.mongo import MongoModuleStore
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.search import path_to_location
# from ~/mitx_all/mitx/common/lib/xmodule/xmodule/modulestore/tests/ from .test_modulestore import check_path_to_location
# to ~/mitx_all/mitx/common/test from . import DATA_DIR
TEST_DIR = path(__file__).abspath().dirname()
for i in range(5):
TEST_DIR = TEST_DIR.dirname()
TEST_DIR = TEST_DIR / 'test'
DATA_DIR = TEST_DIR / 'data'
HOST = 'localhost' HOST = 'localhost'
...@@ -110,27 +101,5 @@ class TestMongoModuleStore(object): ...@@ -110,27 +101,5 @@ class TestMongoModuleStore(object):
def test_path_to_location(self): def test_path_to_location(self):
'''Make sure that path_to_location works''' '''Make sure that path_to_location works'''
should_work = ( check_path_to_location(self.store)
("i4x://edX/toy/video/Welcome",
("edX/toy/2012_Fall", "Overview", "Welcome", None)),
("i4x://edX/toy/chapter/Overview",
("edX/toy/2012_Fall", "Overview", None, None)),
)
for location, expected in should_work:
assert_equals(path_to_location(self.store, location), expected)
not_found = (
"i4x://edX/toy/video/WelcomeX", "i4x://edX/toy/course/NotHome"
)
for location in not_found:
assert_raises(ItemNotFoundError, path_to_location, self.store, location)
# Since our test files are valid, there shouldn't be any
# elements with no path to them. But we can look for them in
# another course.
no_path = (
"i4x://edX/simple/video/Lost_Video",
)
for location in no_path:
assert_raises(NoPathToItem, path_to_location, self.store, location, "toy")
from xmodule.modulestore import Location
from xmodule.modulestore.xml import XMLModuleStore
from xmodule.modulestore.xml_importer import import_from_xml
from .test_modulestore import check_path_to_location
from . import DATA_DIR
class TestXMLModuleStore(object):
def test_path_to_location(self):
"""Make sure that path_to_location works properly"""
print "Starting import"
modulestore = XMLModuleStore(DATA_DIR, course_dirs=['toy', 'simple'])
print "finished import"
check_path_to_location(modulestore)
...@@ -37,7 +37,7 @@ def clean_out_mako_templating(xml_string): ...@@ -37,7 +37,7 @@ def clean_out_mako_templating(xml_string):
class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
def __init__(self, xmlstore, course_id, course_dir, def __init__(self, xmlstore, course_id, course_dir,
policy, error_tracker, **kwargs): policy, error_tracker, parent_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
all elements have unique slugs. all elements have unique slugs.
...@@ -143,8 +143,8 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -143,8 +143,8 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
xmlstore.modules[course_id][descriptor.location] = descriptor xmlstore.modules[course_id][descriptor.location] = descriptor
if xmlstore.eager: for child in descriptor.get_children():
descriptor.get_children() parent_tracker.add_parent(child.location, descriptor.location)
return descriptor return descriptor
render_template = lambda: '' render_template = lambda: ''
...@@ -160,12 +160,51 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -160,12 +160,51 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
error_tracker, process_xml, policy, **kwargs) error_tracker, process_xml, policy, **kwargs)
class ParentTracker(object):
"""A simple class to factor out the logic for tracking location parent pointers."""
def __init__(self):
"""
Init
"""
# location -> set(parents). Not using defaultdict because we care about the empty case.
self._parents = dict()
def add_parent(self, child, parent):
"""
Add a parent of child location to the set of parents. Duplicate calls have no effect.
child and parent must be something that can be passed to Location.
"""
child = Location(child)
parent = Location(parent)
s = self._parents.setdefault(child, set())
s.add(parent)
def is_known(self, child):
"""
returns True iff child has some parents.
"""
child = Location(child)
return child in self._parents
def make_known(self, location):
"""Tell the parent tracker about an object, without registering any
parents for it. Used for the top level course descriptor locations."""
self._parents.setdefault(location, set())
def parents(self, child):
"""
Return a list of the parents of this child. If not is_known(child), will throw a KeyError
"""
child = Location(child)
return list(self._parents[child])
class XMLModuleStore(ModuleStoreBase): class XMLModuleStore(ModuleStoreBase):
""" """
An XML backed ModuleStore An XML backed ModuleStore
""" """
def __init__(self, data_dir, default_class=None, eager=False, def __init__(self, data_dir, default_class=None, course_dirs=None):
course_dirs=None):
""" """
Initialize an XMLModuleStore from data_dir Initialize an XMLModuleStore from data_dir
...@@ -174,15 +213,11 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -174,15 +213,11 @@ class XMLModuleStore(ModuleStoreBase):
default_class: dot-separated string defining the default descriptor default_class: dot-separated string defining the default descriptor
class to use if none is specified in entry_points class to use if none is specified in entry_points
eager: If true, load the modules children immediately to force the
entire course tree to be parsed
course_dirs: If specified, the list of course_dirs to load. Otherwise, course_dirs: If specified, the list of course_dirs to load. Otherwise,
load all course dirs load all course dirs
""" """
ModuleStoreBase.__init__(self) ModuleStoreBase.__init__(self)
self.eager = eager
self.data_dir = path(data_dir) self.data_dir = path(data_dir)
self.modules = defaultdict(dict) # course_id -> dict(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
...@@ -195,10 +230,7 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -195,10 +230,7 @@ class XMLModuleStore(ModuleStoreBase):
class_ = getattr(import_module(module_path), class_name) class_ = getattr(import_module(module_path), class_name)
self.default_class = class_ self.default_class = class_
# TODO (cpennington): We need a better way of selecting specific sets of self.parent_tracker = ParentTracker()
# debug messages to enable. These were drowning out important messages
#log.debug('XMLModuleStore: eager=%s, data_dir = %s' % (eager, self.data_dir))
#log.debug('default_class = %s' % self.default_class)
# If we are specifically asked for missing courses, that should # If we are specifically asked for missing courses, that should
# be an error. If we are asked for "all" courses, find the ones # be an error. If we are asked for "all" courses, find the ones
...@@ -230,6 +262,7 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -230,6 +262,7 @@ class XMLModuleStore(ModuleStoreBase):
if course_descriptor is not None: if course_descriptor is not None:
self.courses[course_dir] = course_descriptor self.courses[course_dir] = course_descriptor
self._location_errors[course_descriptor.location] = errorlog self._location_errors[course_descriptor.location] = errorlog
self.parent_tracker.make_known(course_descriptor.location)
else: else:
# Didn't load course. Instead, save the errors elsewhere. # Didn't load course. Instead, save the errors elsewhere.
self.errored_courses[course_dir] = errorlog self.errored_courses[course_dir] = errorlog
...@@ -348,7 +381,7 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -348,7 +381,7 @@ class XMLModuleStore(ModuleStoreBase):
course_id = CourseDescriptor.make_id(org, course, url_name) course_id = CourseDescriptor.make_id(org, course, url_name)
system = ImportSystem(self, course_id, course_dir, policy, tracker) system = ImportSystem(self, course_id, course_dir, policy, tracker, self.parent_tracker)
course_descriptor = system.process_xml(etree.tostring(course_data)) course_descriptor = system.process_xml(etree.tostring(course_data))
...@@ -459,3 +492,19 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -459,3 +492,19 @@ class XMLModuleStore(ModuleStoreBase):
metadata: A nested dictionary of module metadata metadata: A nested dictionary of module metadata
""" """
raise NotImplementedError("XMLModuleStores are read-only") raise NotImplementedError("XMLModuleStores are read-only")
def get_parent_locations(self, location):
'''Find all locations that are the parents of this location. Needed
for path_to_location().
If there is no data at location in this modulestore, raise
ItemNotFoundError.
returns an iterable of things that can be passed to Location. This may
be empty if there are no parents.
'''
location = Location.ensure_fully_specified(location)
if not self.parent_tracker.is_known(location):
raise ItemNotFoundError(location)
return self.parent_tracker.parents(location)
...@@ -6,7 +6,7 @@ from .exceptions import DuplicateItemError ...@@ -6,7 +6,7 @@ from .exceptions import DuplicateItemError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def import_from_xml(store, data_dir, course_dirs=None, eager=True, def import_from_xml(store, data_dir, course_dirs=None,
default_class='xmodule.raw_module.RawDescriptor'): default_class='xmodule.raw_module.RawDescriptor'):
""" """
Import the specified xml data_dir into the "store" modulestore, Import the specified xml data_dir into the "store" modulestore,
...@@ -19,7 +19,6 @@ def import_from_xml(store, data_dir, course_dirs=None, eager=True, ...@@ -19,7 +19,6 @@ def import_from_xml(store, data_dir, course_dirs=None, eager=True,
module_store = XMLModuleStore( module_store = XMLModuleStore(
data_dir, data_dir,
default_class=default_class, default_class=default_class,
eager=eager,
course_dirs=course_dirs course_dirs=course_dirs
) )
for course_id in module_store.modules.keys(): for course_id in module_store.modules.keys():
......
...@@ -49,7 +49,7 @@ class RoundTripTestCase(unittest.TestCase): ...@@ -49,7 +49,7 @@ class RoundTripTestCase(unittest.TestCase):
copytree(data_dir / course_dir, root_dir / course_dir) copytree(data_dir / course_dir, root_dir / course_dir)
print "Starting import" print "Starting import"
initial_import = XMLModuleStore(root_dir, eager=True, course_dirs=[course_dir]) initial_import = XMLModuleStore(root_dir, course_dirs=[course_dir])
courses = initial_import.get_courses() courses = initial_import.get_courses()
self.assertEquals(len(courses), 1) self.assertEquals(len(courses), 1)
...@@ -66,7 +66,7 @@ class RoundTripTestCase(unittest.TestCase): ...@@ -66,7 +66,7 @@ class RoundTripTestCase(unittest.TestCase):
course_xml.write(xml) course_xml.write(xml)
print "Starting second import" print "Starting second import"
second_import = XMLModuleStore(root_dir, eager=True, course_dirs=[course_dir]) second_import = XMLModuleStore(root_dir, course_dirs=[course_dir])
courses2 = second_import.get_courses() courses2 = second_import.get_courses()
self.assertEquals(len(courses2), 1) self.assertEquals(len(courses2), 1)
......
...@@ -193,7 +193,7 @@ class ImportTestCase(unittest.TestCase): ...@@ -193,7 +193,7 @@ class ImportTestCase(unittest.TestCase):
"""Make sure that metadata is inherited properly""" """Make sure that metadata is inherited properly"""
print "Starting import" print "Starting import"
initial_import = XMLModuleStore(DATA_DIR, eager=True, course_dirs=['toy']) initial_import = XMLModuleStore(DATA_DIR, course_dirs=['toy'])
courses = initial_import.get_courses() courses = initial_import.get_courses()
self.assertEquals(len(courses), 1) self.assertEquals(len(courses), 1)
...@@ -216,7 +216,7 @@ class ImportTestCase(unittest.TestCase): ...@@ -216,7 +216,7 @@ class ImportTestCase(unittest.TestCase):
def get_course(name): def get_course(name):
print "Importing {0}".format(name) print "Importing {0}".format(name)
modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=[name]) modulestore = XMLModuleStore(DATA_DIR, course_dirs=[name])
courses = modulestore.get_courses() courses = modulestore.get_courses()
self.assertEquals(len(courses), 1) self.assertEquals(len(courses), 1)
return courses[0] return courses[0]
...@@ -245,7 +245,7 @@ class ImportTestCase(unittest.TestCase): ...@@ -245,7 +245,7 @@ class ImportTestCase(unittest.TestCase):
happen--locations should uniquely name definitions. But in happen--locations should uniquely name definitions. But in
our imperfect XML world, it can (and likely will) happen.""" our imperfect XML world, it can (and likely will) happen."""
modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=['toy', 'two_toys']) modulestore = XMLModuleStore(DATA_DIR, course_dirs=['toy', 'two_toys'])
toy_id = "edX/toy/2012_Fall" toy_id = "edX/toy/2012_Fall"
two_toy_id = "edX/toy/TT_2012_Fall" two_toy_id = "edX/toy/TT_2012_Fall"
...@@ -261,7 +261,7 @@ class ImportTestCase(unittest.TestCase): ...@@ -261,7 +261,7 @@ class ImportTestCase(unittest.TestCase):
"""Ensure that colons in url_names convert to file paths properly""" """Ensure that colons in url_names convert to file paths properly"""
print "Starting import" print "Starting import"
modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=['toy']) modulestore = XMLModuleStore(DATA_DIR, course_dirs=['toy'])
courses = modulestore.get_courses() courses = modulestore.get_courses()
self.assertEquals(len(courses), 1) self.assertEquals(len(courses), 1)
......
...@@ -544,7 +544,13 @@ class XModuleDescriptor(Plugin, HTMLSnippet): ...@@ -544,7 +544,13 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
# Put import here to avoid circular import errors # Put import here to avoid circular import errors
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
msg = "Error loading from xml." msg = "Error loading from xml."
log.warning(msg + " " + str(err)) log.warning(msg + " " + str(err)[:200])
# Normally, we don't want lots of exception traces in our logs from common
# content problems. But if you're debugging the xml loading code itself,
# uncomment the next line.
# log.exception(msg)
system.error_tracker(msg) system.error_tracker(msg)
err_msg = msg + "\n" + exc_info_to_str(sys.exc_info()) err_msg = msg + "\n" + exc_info_to_str(sys.exc_info())
descriptor = ErrorDescriptor.from_xml(xml_data, system, org, course, descriptor = ErrorDescriptor.from_xml(xml_data, system, org, course,
......
...@@ -57,7 +57,6 @@ def import_with_checks(course_dir, verbose=True): ...@@ -57,7 +57,6 @@ def import_with_checks(course_dir, verbose=True):
# module. # module.
modulestore = XMLModuleStore(data_dir, modulestore = XMLModuleStore(data_dir,
default_class=None, default_class=None,
eager=True,
course_dirs=course_dirs) course_dirs=course_dirs)
def str_of_err(tpl): def str_of_err(tpl):
......
...@@ -23,7 +23,6 @@ def import_course(course_dir, verbose=True): ...@@ -23,7 +23,6 @@ def import_course(course_dir, verbose=True):
# module. # module.
modulestore = XMLModuleStore(data_dir, modulestore = XMLModuleStore(data_dir,
default_class=None, default_class=None,
eager=True,
course_dirs=course_dirs) course_dirs=course_dirs)
def str_of_err(tpl): def str_of_err(tpl):
......
...@@ -195,7 +195,6 @@ def _get_module(user, request, location, student_module_cache, course_id, positi ...@@ -195,7 +195,6 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
descriptor.category, descriptor.category,
shared_state_key) shared_state_key)
instance_state = instance_module.state if instance_module is not None else None instance_state = instance_module.state if instance_module is not None else None
shared_state = shared_module.state if shared_module is not None else None shared_state = shared_module.state if shared_module is not None else None
...@@ -254,7 +253,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi ...@@ -254,7 +253,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
# by the replace_static_urls code below # by the replace_static_urls code below
replace_urls=replace_urls, replace_urls=replace_urls,
node_path=settings.NODE_PATH, node_path=settings.NODE_PATH,
anonymous_student_id=anonymous_student_id anonymous_student_id=anonymous_student_id,
) )
# pass position specified in URL to module through ModuleSystem # pass position specified in URL to module through ModuleSystem
system.set('position', position) system.set('position', position)
......
...@@ -68,7 +68,6 @@ def xml_store_config(data_dir): ...@@ -68,7 +68,6 @@ def xml_store_config(data_dir):
'OPTIONS': { 'OPTIONS': {
'data_dir': data_dir, 'data_dir': data_dir,
'default_class': 'xmodule.hidden_module.HiddenDescriptor', 'default_class': 'xmodule.hidden_module.HiddenDescriptor',
'eager': True,
} }
} }
} }
...@@ -204,7 +203,8 @@ class PageLoader(ActivateLoginTestCase): ...@@ -204,7 +203,8 @@ class PageLoader(ActivateLoginTestCase):
self.assertEqual(len(courses), 1) self.assertEqual(len(courses), 1)
course = courses[0] course = courses[0]
self.enroll(course) self.enroll(course)
course_id = course.id
n = 0 n = 0
num_bad = 0 num_bad = 0
all_ok = True all_ok = True
...@@ -214,7 +214,8 @@ class PageLoader(ActivateLoginTestCase): ...@@ -214,7 +214,8 @@ class PageLoader(ActivateLoginTestCase):
print "Checking ", descriptor.location.url() print "Checking ", descriptor.location.url()
#print descriptor.__class__, descriptor.location #print descriptor.__class__, descriptor.location
resp = self.client.get(reverse('jump_to', resp = self.client.get(reverse('jump_to',
kwargs={'location': descriptor.location.url()})) kwargs={'course_id': course_id,
'location': descriptor.location.url()}))
msg = str(resp.status_code) msg = str(resp.status_code)
if resp.status_code != 200: if resp.status_code != 200:
......
...@@ -5,8 +5,6 @@ import itertools ...@@ -5,8 +5,6 @@ import itertools
from functools import partial from functools import partial
from functools import partial
from django.conf import settings from django.conf import settings
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -152,7 +150,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -152,7 +150,7 @@ def index(request, course_id, chapter=None, section=None,
course_id, request.user, section_descriptor) course_id, request.user, 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) student_module_cache, course_id, position)
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.
...@@ -196,7 +194,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -196,7 +194,7 @@ def index(request, course_id, chapter=None, section=None,
@ensure_csrf_cookie @ensure_csrf_cookie
def jump_to(request, location): def jump_to(request, course_id, location):
''' '''
Show the page that contains a specific location. Show the page that contains a specific location.
...@@ -213,15 +211,18 @@ def jump_to(request, location): ...@@ -213,15 +211,18 @@ def jump_to(request, location):
# Complain if there's not data for this location # Complain if there's not data for this location
try: try:
(course_id, chapter, section, position) = path_to_location(modulestore(), location) (course_id, chapter, section, position) = path_to_location(modulestore(), course_id, location)
except ItemNotFoundError: except ItemNotFoundError:
raise Http404("No data at this location: {0}".format(location)) raise Http404("No data at this location: {0}".format(location))
except NoPathToItem: except NoPathToItem:
raise Http404("This location is not in any class: {0}".format(location)) raise Http404("This location is not in any class: {0}".format(location))
# Rely on index to do all error handling and access control. # Rely on index to do all error handling and access control.
return index(request, course_id, chapter, section, position) return redirect('courseware_position',
course_id=course_id,
chapter=chapter,
section=section,
position=position)
@ensure_csrf_cookie @ensure_csrf_cookie
def course_info(request, course_id): def course_info(request, course_id):
""" """
......
...@@ -43,7 +43,8 @@ def get_full_modules(): ...@@ -43,7 +43,8 @@ def get_full_modules():
class_path = settings.MODULESTORE['default']['ENGINE'] class_path = settings.MODULESTORE['default']['ENGINE']
module_path, _, class_name = class_path.rpartition('.') module_path, _, class_name = class_path.rpartition('.')
class_ = getattr(import_module(module_path), class_name) class_ = getattr(import_module(module_path), class_name)
modulestore = class_(**dict(settings.MODULESTORE['default']['OPTIONS'].items() + [('eager', True)])) # TODO (vshnayder): wth is this doing???
modulestore = class_(**dict(settings.MODULESTORE['default']['OPTIONS'].items()))
_FULLMODULES = modulestore.modules _FULLMODULES = modulestore.modules
return _FULLMODULES return _FULLMODULES
...@@ -76,7 +77,7 @@ def initialize_discussion_info(request, course): ...@@ -76,7 +77,7 @@ def initialize_discussion_info(request, course):
_is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ _is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \
and x[0].dict()['course'] == course_name and x[0].dict()['course'] == course_name
_get_module_descriptor = operator.itemgetter(1) _get_module_descriptor = operator.itemgetter(1)
def _get_module(module_descriptor): def _get_module(module_descriptor):
...@@ -135,8 +136,8 @@ class HtmlResponse(HttpResponse): ...@@ -135,8 +136,8 @@ class HtmlResponse(HttpResponse):
def __init__(self, html=''): def __init__(self, html=''):
super(HtmlResponse, self).__init__(html, content_type='text/plain') super(HtmlResponse, self).__init__(html, content_type='text/plain')
class ViewNameMiddleware(object): class ViewNameMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs): def process_view(self, request, view_func, view_args, view_kwargs):
request.view_name = view_func.__name__ request.view_name = view_func.__name__
class QueryCountDebugMiddleware(object): class QueryCountDebugMiddleware(object):
......
...@@ -223,7 +223,6 @@ MODULESTORE = { ...@@ -223,7 +223,6 @@ MODULESTORE = {
'OPTIONS': { 'OPTIONS': {
'data_dir': DATA_DIR, 'data_dir': DATA_DIR,
'default_class': 'xmodule.hidden_module.HiddenDescriptor', 'default_class': 'xmodule.hidden_module.HiddenDescriptor',
'eager': True,
} }
} }
} }
......
...@@ -98,8 +98,9 @@ if settings.COURSEWARE_ENABLED: ...@@ -98,8 +98,9 @@ if settings.COURSEWARE_ENABLED:
urlpatterns += ( urlpatterns += (
# Hook django-masquerade, allowing staff to view site as other users # 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'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/jump_to/(?P<location>.*)$',
'courseware.views.jump_to', name="jump_to"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/modx/(?P<location>.*?)/(?P<dispatch>[^/]*)$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/modx/(?P<location>.*?)/(?P<dispatch>[^/]*)$',
'courseware.module_render.modx_dispatch', 'courseware.module_render.modx_dispatch',
name='modx_dispatch'), name='modx_dispatch'),
...@@ -142,6 +143,8 @@ if settings.COURSEWARE_ENABLED: ...@@ -142,6 +143,8 @@ if settings.COURSEWARE_ENABLED:
'courseware.views.index', name="courseware_chapter"), 'courseware.views.index', name="courseware_chapter"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$',
'courseware.views.index', name="courseware_section"), 'courseware.views.index', name="courseware_section"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/(?P<position>[^/]*)/?$',
'courseware.views.index', name="courseware_position"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/progress$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/progress$',
'courseware.views.progress', name="progress"), 'courseware.views.progress', name="progress"),
# Takes optional student_id for instructor use--shows profile as that student sees it. # Takes optional student_id for instructor use--shows profile as that student sees it.
...@@ -164,7 +167,7 @@ if settings.COURSEWARE_ENABLED: ...@@ -164,7 +167,7 @@ if settings.COURSEWARE_ENABLED:
if settings.MITX_FEATURES.get('ENABLE_DISCUSSION_SERVICE'): if settings.MITX_FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
urlpatterns += ( urlpatterns += (
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/news$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/news$',
'courseware.views.news', name="news"), 'courseware.views.news', name="news"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/',
include('django_comment_client.urls')) include('django_comment_client.urls'))
......
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