diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py
index 690e3dbea0..3e0ccdd5e8 100644
--- a/cms/djangoapps/contentstore/management/commands/import.py
+++ b/cms/djangoapps/contentstore/management/commands/import.py
@@ -82,7 +82,7 @@ class Command(BaseCommand):
def handle_list(e):
if e.attrib.get("class", None) == "tutorials":
return
- children = [{'url':le.attrib['url']} for le in e.getchildren()]
+ children = [le.attrib['url'] for le in e.getchildren()]
results[e.attrib['url']] = {'children':children}
def handle_video(e):
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 429fb6c26b..64bde14869 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -3,11 +3,10 @@ from keystore.django import keystore
from django.contrib.auth.decorators import login_required
-@login_required
-def calendar(request, org, course):
- weeks = keystore.get_children_for_item(['i4x', org, course, 'Course', None])
- return render_to_response('calendar.html', {'weeks': weeks})
-
-
def index(request):
- return render_to_response('index.html', {})
+ # FIXME (cpennington): These need to be read in from the active user
+ org = 'mit.edu'
+ course = '6002xs12'
+ course = keystore.get_item(['i4x', org, course, 'Course', None])
+ weeks = course.get_children()
+ return render_to_response('index.html', {'weeks': weeks})
diff --git a/cms/lib/keystore/__init__.py b/cms/lib/keystore/__init__.py
index d0a24be797..61c797241d 100644
--- a/cms/lib/keystore/__init__.py
+++ b/cms/lib/keystore/__init__.py
@@ -37,7 +37,7 @@ class Location(object):
self.update(location.list())
def url(self):
- return "i4x://{org}/{course}/{category}/{name}".format(**self.dict())
+ return "{tag}://{org}/{course}/{category}/{name}".format(**self.dict())
def list(self):
return [self.tag, self.org, self.course, self.category, self.name]
@@ -54,10 +54,9 @@ class Location(object):
class KeyStore(object):
- def get_children_for_item(self, location):
+ def get_item(self, location):
"""
- Returns the children for the most recent revision of the object
- with the specified location.
+ Returns an XModuleDescriptor instance for the item at location
If no object is found at that location, raises keystore.exceptions.ItemNotFoundError
diff --git a/cms/lib/keystore/mongo.py b/cms/lib/keystore/mongo.py
index d29afb4bd2..d9760909c9 100644
--- a/cms/lib/keystore/mongo.py
+++ b/cms/lib/keystore/mongo.py
@@ -1,6 +1,7 @@
import pymongo
from . import KeyStore, Location
from .exceptions import ItemNotFoundError, InsufficientSpecificationError
+from xmodule.x_module import XModuleDescriptor
class MongoKeyStore(KeyStore):
@@ -12,11 +13,22 @@ class MongoKeyStore(KeyStore):
host=host,
port=port
)[db][collection]
-
+
# Force mongo to report errors, at the expense of performance
self.collection.safe = True
- def get_children_for_item(self, location):
+ def get_item(self, location):
+ """
+ Returns an XModuleDescriptor instance for the item at location
+
+ If no object is found at that location, raises keystore.exceptions.ItemNotFoundError
+
+ Searches for all matches of a partially specifed location, but raises an
+ keystore.exceptions.InsufficientSpecificationError if more
+ than a single object matches the query.
+
+ location: Something that can be passed to Location
+ """
query = dict(
('location.{key}'.format(key=key), val)
for (key, val)
@@ -25,7 +37,6 @@ class MongoKeyStore(KeyStore):
)
items = self.collection.find(
query,
- fields={'children': True},
sort=[('revision', pymongo.ASCENDING)],
limit=1,
)
@@ -35,7 +46,7 @@ class MongoKeyStore(KeyStore):
if items.count() == 0:
raise ItemNotFoundError(location)
- return items[0]['children']
+ return XModuleDescriptor.load_from_json(items[0], self.get_item)
def create_item(self, location, editor):
"""
diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html
index 28b92979d9..75d581dd73 100644
--- a/cms/templates/widgets/navigation.html
+++ b/cms/templates/widgets/navigation.html
@@ -35,9 +35,10 @@
+ % for week in weeks:
-
-
+
- Goal title: This is a goal that will be in the header of the week
- Goal title two: This is another goal for this week so that students have two things to learn
@@ -64,125 +65,7 @@
<%include file="module-dropdown.html"/>
- -
-
-
-
- - Another title This is the goal for the week
-
-
-
-
-
- -
-
-
-
- - Another title This is the goal for the week
-
-
-
-
-
- -
-
-
-
- - Another title This is the goal for the week
- - Goal title two: This is another fgoal for this week so that students have two things to learn
-
-
-
-
-
-
- -
-
-
-
- - Please create a learning goal for this week
-
-
-
-
- <%include file="module-dropdown.html"/>
-
-
- -
-
-
-
- - Please create a learning goal for this week
-
-
-
-
- <%include file="module-dropdown.html"/>
-
-
- -
-
-
-
- - Please create a learning goal for this week
-
-
-
-
- <%include file="module-dropdown.html"/>
-
-
+ %endfor
-
Course Scratch Pad
diff --git a/common/lib/xmodule/seq_module.py b/common/lib/xmodule/seq_module.py
index f643eaab4a..4ae4fb3813 100644
--- a/common/lib/xmodule/seq_module.py
+++ b/common/lib/xmodule/seq_module.py
@@ -95,3 +95,11 @@ class Module(XModule):
self.position = int(system.get('position'))
self.rendered = False
+
+
+class CourseModuleDescriptor(XModuleDescriptor):
+ pass
+
+
+class ChapterModuleDescriptor(XModuleDescriptor):
+ pass
diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py
new file mode 100644
index 0000000000..6a659b6852
--- /dev/null
+++ b/common/lib/xmodule/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup, find_packages
+
+setup(
+ name="XModule",
+ version="0.1",
+ packages=find_packages(),
+ install_requires=['distribute'],
+ entry_points={
+ 'xmodule.v1': [
+ "Course = seq_module:CourseModuleDescriptor",
+ "Chapter = seq_module:ChapterModuleDescriptor",
+ ]
+ }
+)
diff --git a/common/lib/xmodule/x_module.py b/common/lib/xmodule/x_module.py
index d783694fee..23025df50b 100644
--- a/common/lib/xmodule/x_module.py
+++ b/common/lib/xmodule/x_module.py
@@ -1,8 +1,34 @@
from lxml import etree
+import pkg_resources
+import logging
+from keystore import Location
+
+log = logging.getLogger('mitx.' + __name__)
def dummy_track(event_type, event):
pass
+
+class ModuleMissingError(Exception):
+ pass
+
+
+class Plugin(object):
+ @classmethod
+ def load_class(cls, identifier):
+ classes = list(pkg_resources.iter_entry_points(cls.entry_point, name=identifier))
+ if len(classes) > 1:
+ log.warning("Found multiple classes for {entry_point} with identifier {id}: {classes}. Returning the first one.".format(
+ entry_point=cls.entry_point,
+ id=identifier,
+ classes=", ".join([class_.module_name for class_ in classes])))
+
+ if len(classes) == 0:
+ raise ModuleMissingError(identifier)
+
+ return classes[0].load()
+
+
class XModule(object):
''' Implements a generic learning module.
Initialized on access with __init__, first time with state=None, and
@@ -24,8 +50,8 @@ class XModule(object):
or a CAPA input type '''
return ['xmodule']
- def get_name():
- name = self.__xmltree.get(name)
+ def get_name(self):
+ name = self.__xmltree.get('name')
if name:
return name
else:
@@ -98,15 +124,42 @@ class XModule(object):
return ""
-class XModuleDescriptor(object):
- def __init__(self, xml = None, json = None):
- if not xml and not json:
- raise "XModuleDescriptor must be initalized with XML or JSON"
- if not xml:
- raise NotImplementedError("Code does not have support for JSON yet")
-
- self.xml = xml
- self.json = json
+class XModuleDescriptor(Plugin):
+
+ entry_point = "xmodule.v1"
+
+ @staticmethod
+ def load_from_json(json_data, load_item):
+ class_ = XModuleDescriptor.load_class(json_data['location']['category'])
+ return class_.from_json(json_data, load_item)
+
+ @classmethod
+ def from_json(cls, json_data, load_item):
+ """
+ Creates an instance of this descriptor from the supplied json_data.
+
+ json_data: Json data specifying the data, children, and metadata for the descriptor
+ load_item: A function that takes an i4x url and returns a module descriptor
+ """
+ return cls(load_item=load_item, **json_data)
+
+ def __init__(self,
+ load_item,
+ data=None,
+ children=None,
+ **kwargs):
+ self.load_item = load_item
+ self.data = data if data is not None else {}
+ self.children = children if children is not None else []
+ self.name = Location(kwargs.get('location')).name
+ self._child_instances = None
+
+ def get_children(self):
+ """Returns a list of XModuleDescriptor instances for the children of this module"""
+ if self._child_instances is None:
+ self._child_instances = [self.load_item(child) for child in self.children]
+ return self._child_instances
+
def get_xml(self):
''' For conversions between JSON and legacy XML representations.
diff --git a/requirements.txt b/requirements.txt
index 122276ea39..37a30b6cef 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -27,3 +27,4 @@ pymongo
django_nose
nosexcover
rednose
+-e common/lib/xmodule