Merge pull request #122 from MITx/cpennington/cms-review-cleanup
Cleanup from https://github.com/MITx/mitx/pull/120
This commit is contained in:
@@ -4,9 +4,10 @@ from django.contrib.auth.decorators import login_required
|
||||
|
||||
|
||||
def index(request):
|
||||
# FIXME (cpennington): These need to be read in from the active user
|
||||
# TODO (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])
|
||||
name = '6.002 Spring 2012'
|
||||
course = keystore().get_item(['i4x', org, course, 'Course', name])
|
||||
weeks = course.get_children()
|
||||
return render_to_response('index.html', {'weeks': weeks})
|
||||
|
||||
@@ -27,15 +27,12 @@ from path import path
|
||||
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms
|
||||
COMMON_ROOT = PROJECT_ROOT.dirname() / "common"
|
||||
ENV_ROOT = PROJECT_ROOT.dirname().dirname() # virtualenv dir /mitx is in
|
||||
ASKBOT_ROOT = ENV_ROOT / "askbot-devel"
|
||||
COURSES_ROOT = ENV_ROOT / "data"
|
||||
|
||||
# FIXME: To support multiple courses, we should walk the courses dir at startup
|
||||
DATA_DIR = COURSES_ROOT
|
||||
|
||||
sys.path.append(ENV_ROOT)
|
||||
sys.path.append(ASKBOT_ROOT)
|
||||
sys.path.append(ASKBOT_ROOT / "askbot" / "deps")
|
||||
sys.path.append(PROJECT_ROOT / 'djangoapps')
|
||||
sys.path.append(PROJECT_ROOT / 'lib')
|
||||
sys.path.append(COMMON_ROOT / 'djangoapps')
|
||||
@@ -145,5 +142,4 @@ INSTALLED_APPS = (
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'contentstore',
|
||||
'instructor',
|
||||
)
|
||||
|
||||
@@ -7,9 +7,11 @@ DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
KEYSTORE = {
|
||||
'host': 'localhost',
|
||||
'db': 'mongo_base',
|
||||
'collection': 'key_store',
|
||||
'default': {
|
||||
'host': 'localhost',
|
||||
'db': 'mongo_base',
|
||||
'collection': 'key_store',
|
||||
}
|
||||
}
|
||||
|
||||
DATABASES = {
|
||||
|
||||
@@ -5,7 +5,9 @@ try:
|
||||
imp.find_module('settings') # Assumed to be in the same directory.
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
|
||||
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. "
|
||||
"It appears you've customized things.\nYou'll have to run django-admin.py, "
|
||||
"passing it your settings module.\n" % __file__)
|
||||
sys.exit(1)
|
||||
|
||||
import settings
|
||||
|
||||
@@ -40,14 +40,18 @@
|
||||
<header>
|
||||
<h1><a href="#">${week.name}</a></h1>
|
||||
<ul>
|
||||
% for goal in week.get_goals():
|
||||
<li class="goal editable"><strong>${goal.name}:</strong>${goal.data}</li>
|
||||
% endfor
|
||||
% if week.goals:
|
||||
% for goal in week.goals:
|
||||
<li class="goal editable">${goal}</li>
|
||||
% endfor
|
||||
% else:
|
||||
<li class="goal editable">Please create a learning goal for this week</li>
|
||||
% endif
|
||||
</ul>
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
% for module in week.get_non_goals():
|
||||
% for module in week.get_children():
|
||||
<li class="${module.type}">
|
||||
<a href="#" class="${module.type}-edit">${module.name}</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
|
||||
@@ -5,7 +5,5 @@ from django.conf.urls.defaults import patterns, url
|
||||
# admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/calendar/', 'contentstore.views.calendar', name='calendar'),
|
||||
url(r'^accounts/login/', 'instructor.views.do_login', name='login'),
|
||||
url(r'^$', 'contentstore.views.index', name='index'),
|
||||
)
|
||||
|
||||
@@ -1,79 +1,146 @@
|
||||
"""
|
||||
This module provides an abstraction for working objects that conceptually have
|
||||
the following attributes:
|
||||
This module provides an abstraction for working with XModuleDescriptors
|
||||
that are stored in a database an accessible using their Location as an identifier
|
||||
"""
|
||||
|
||||
location: An identifier for an item, of which there might be many revisions
|
||||
children: A list of urls for other items required to fully define this object
|
||||
data: A set of nested data needed to define this object
|
||||
editor: The editor/owner of the object
|
||||
parents: Url pointers for objects that this object was derived from
|
||||
revision: What revision of the item this is
|
||||
"""
|
||||
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.
|
||||
Can be:
|
||||
* String (url)
|
||||
* Tuple
|
||||
* Dictionary
|
||||
'''
|
||||
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):
|
||||
self.tag = location.split('/')[0][:-1]
|
||||
(self.org, self.course, self.category, self.name) = location.split('/')[2:]
|
||||
match = URL_RE.match(location)
|
||||
if match is None:
|
||||
raise InvalidLocationError(location)
|
||||
else:
|
||||
self.update(match.groupdict())
|
||||
elif isinstance(location, list):
|
||||
(self.tag, self.org, self.course, self.category, self.name) = location
|
||||
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):
|
||||
self.tag = location['tag']
|
||||
self.org = location['org']
|
||||
self.course = location['course']
|
||||
self.category = location['category']
|
||||
self.name = location['name']
|
||||
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 "{tag}://{org}/{course}/{category}/{name}".format(**self.dict())
|
||||
"""
|
||||
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 [self.tag, self.org, self.course, self.category, self.name]
|
||||
"""
|
||||
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}
|
||||
|
||||
def to_json(self):
|
||||
return self.dict()
|
||||
'name': self.name,
|
||||
'revision': self.revision}
|
||||
|
||||
|
||||
class KeyStore(object):
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
# TODO (cpennington): Replace with clone_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
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update_item(self, location, data):
|
||||
@@ -95,19 +162,3 @@ class KeyStore(object):
|
||||
children: A list of child item identifiers
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class KeyStoreItem(object):
|
||||
"""
|
||||
An object from a KeyStore, which can be saved back to that keystore
|
||||
"""
|
||||
def __init__(self, location, children, data, editor, parents, revision):
|
||||
self.location = location
|
||||
self.children = children
|
||||
self.data = data
|
||||
self.editor = editor
|
||||
self.parents = parents
|
||||
self.revision = revision
|
||||
|
||||
def save(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
"""
|
||||
Module that provides a connection to the keystore specified in the django settings.
|
||||
|
||||
Passes settings.KEYSTORE as kwargs to MongoKeyStore
|
||||
Passes settings.KEYSTORE as kwargs to MongoModuleStore
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf import settings
|
||||
from .mongo import MongoKeyStore
|
||||
from .mongo import MongoModuleStore
|
||||
|
||||
_KEYSTORE = None
|
||||
_KEYSTORES = {}
|
||||
|
||||
|
||||
def keystore():
|
||||
global _KEYSTORE
|
||||
def keystore(name='default'):
|
||||
global _KEYSTORES
|
||||
|
||||
if _KEYSTORE is None:
|
||||
_KEYSTORE = MongoKeyStore(**settings.KEYSTORE)
|
||||
if name not in _KEYSTORES:
|
||||
_KEYSTORES[name] = MongoModuleStore(**settings.KEYSTORE[name])
|
||||
|
||||
return _KEYSTORE
|
||||
return _KEYSTORES[name]
|
||||
|
||||
@@ -9,3 +9,6 @@ class ItemNotFoundError(Exception):
|
||||
|
||||
class InsufficientSpecificationError(Exception):
|
||||
pass
|
||||
|
||||
class InvalidLocationError(Exception):
|
||||
pass
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import pymongo
|
||||
from . import KeyStore, Location
|
||||
from . import ModuleStore, Location
|
||||
from .exceptions import ItemNotFoundError, InsufficientSpecificationError
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
from xmodule.x_module import XModuleDescriptor, DescriptorSystem
|
||||
|
||||
|
||||
class MongoKeyStore(KeyStore):
|
||||
class MongoModuleStore(ModuleStore):
|
||||
"""
|
||||
A Mongodb backed KeyStore
|
||||
A Mongodb backed ModuleStore
|
||||
"""
|
||||
def __init__(self, host, db, collection, port=27017):
|
||||
self.collection = pymongo.connection.Connection(
|
||||
@@ -19,34 +19,33 @@ class MongoKeyStore(KeyStore):
|
||||
|
||||
def get_item(self, location):
|
||||
"""
|
||||
Returns an XModuleDescriptor instance for the item at 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
|
||||
|
||||
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)
|
||||
in Location(location).dict().items()
|
||||
if val is not None
|
||||
)
|
||||
items = self.collection.find(
|
||||
|
||||
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)],
|
||||
limit=1,
|
||||
)
|
||||
if items.count() > 1:
|
||||
raise InsufficientSpecificationError(location)
|
||||
|
||||
if items.count() == 0:
|
||||
if item is None:
|
||||
raise ItemNotFoundError(location)
|
||||
|
||||
return XModuleDescriptor.load_from_json(items[0], self.get_item)
|
||||
return XModuleDescriptor.load_from_json(item, DescriptorSystem(self.get_item))
|
||||
|
||||
def create_item(self, location, editor):
|
||||
"""
|
||||
@@ -67,9 +66,12 @@ class MongoKeyStore(KeyStore):
|
||||
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': {'data': data}}
|
||||
{'$set': {'definition.data': data}}
|
||||
)
|
||||
|
||||
def update_children(self, location, children):
|
||||
@@ -80,7 +82,10 @@ class MongoKeyStore(KeyStore):
|
||||
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': {'children': children}}
|
||||
{'$set': {'definition.children': children}}
|
||||
)
|
||||
|
||||
52
common/lib/keystore/test/test_location.py
Normal file
52
common/lib/keystore/test/test_location.py
Normal file
@@ -0,0 +1,52 @@
|
||||
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)
|
||||
@@ -97,22 +97,5 @@ class Module(XModule):
|
||||
self.rendered = False
|
||||
|
||||
|
||||
class WeekDescriptor(XModuleDescriptor):
|
||||
|
||||
def get_goals(self):
|
||||
"""
|
||||
Return a list of Goal XModuleDescriptors that are children
|
||||
of this Week
|
||||
"""
|
||||
return [child for child in self.get_children() if child.type == 'Goal']
|
||||
|
||||
def get_non_goals(self):
|
||||
"""
|
||||
Return a list of non-Goal XModuleDescriptors that are children of
|
||||
this Week
|
||||
"""
|
||||
return [child for child in self.get_children() if child.type != 'Goal']
|
||||
|
||||
|
||||
class SectionDescriptor(XModuleDescriptor):
|
||||
pass
|
||||
|
||||
@@ -5,10 +5,13 @@ setup(
|
||||
version="0.1",
|
||||
packages=find_packages(),
|
||||
install_requires=['distribute'],
|
||||
|
||||
# See http://guide.python-distribute.org/creation.html#entry-points
|
||||
# for a description of entry_points
|
||||
entry_points={
|
||||
'xmodule.v1': [
|
||||
"Course = seq_module:SectionDescriptor",
|
||||
"Week = seq_module:WeekDescriptor",
|
||||
"Week = seq_module:SectionDescriptor",
|
||||
"Section = seq_module:SectionDescriptor",
|
||||
"LectureSequence = seq_module:SectionDescriptor",
|
||||
"Lab = seq_module:SectionDescriptor",
|
||||
|
||||
@@ -21,7 +21,7 @@ class Plugin(object):
|
||||
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])))
|
||||
classes=", ".join(class_.module_name for class_ in classes)))
|
||||
|
||||
if len(classes) == 0:
|
||||
raise ModuleMissingError(identifier)
|
||||
@@ -125,47 +125,82 @@ class XModule(object):
|
||||
|
||||
|
||||
class XModuleDescriptor(Plugin):
|
||||
"""
|
||||
An XModuleDescriptor is a specification for an element of a course. This could
|
||||
be a problem, an organizational element (a group of content), or a segment of video,
|
||||
for example.
|
||||
|
||||
XModuleDescriptors are independent and agnostic to the current student state on a
|
||||
problem. They handle the editing interface used by instructors to create a problem,
|
||||
and can generate XModules (which do know about student state).
|
||||
"""
|
||||
entry_point = "xmodule.v1"
|
||||
|
||||
@staticmethod
|
||||
def load_from_json(json_data, load_item):
|
||||
def load_from_json(json_data, system):
|
||||
"""
|
||||
This method instantiates the correct subclass of XModuleDescriptor based
|
||||
on the contents of json_data.
|
||||
|
||||
json_data must contain a 'location' element, and must be suitable to be
|
||||
passed into the subclasses `from_json` method.
|
||||
"""
|
||||
class_ = XModuleDescriptor.load_class(json_data['location']['category'])
|
||||
return class_.from_json(json_data, load_item)
|
||||
return class_.from_json(json_data, system)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_data, load_item):
|
||||
def from_json(cls, json_data, system):
|
||||
"""
|
||||
Creates an instance of this descriptor from the supplied json_data.
|
||||
This may be overridden by subclasses
|
||||
|
||||
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
|
||||
system: An XModuleSystem for interacting with external resources
|
||||
"""
|
||||
return cls(load_item=load_item, **json_data)
|
||||
return cls(system=system, **json_data)
|
||||
|
||||
def __init__(self,
|
||||
load_item,
|
||||
data=None,
|
||||
children=None,
|
||||
system,
|
||||
definition=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 []
|
||||
"""
|
||||
Construct a new XModuleDescriptor. The only required arguments are the
|
||||
system, used for interaction with external resources, and the definition,
|
||||
which specifies all the data needed to edit and display the problem (but none
|
||||
of the associated metadata that handles recordkeeping around the problem).
|
||||
|
||||
This allows for maximal flexibility to add to the interface while preserving
|
||||
backwards compatibility.
|
||||
|
||||
system: An XModuleSystem for interacting with external resources
|
||||
definition: A dict containing `data` and `children` representing the problem definition
|
||||
|
||||
Current arguments passed in kwargs:
|
||||
location: A keystore.Location object indicating the name and ownership of this problem
|
||||
goals: A list of strings of learning goals associated with this module
|
||||
"""
|
||||
self.system = system
|
||||
self.definition = definition if definition is not None else {}
|
||||
self.name = Location(kwargs.get('location')).name
|
||||
self.type = Location(kwargs.get('location')).category
|
||||
|
||||
# For now, we represent goals as a list of strings, but this
|
||||
# is one of the things that we are going to be iterating on heavily
|
||||
# to find the best teaching method
|
||||
self.goals = kwargs.get('goals', [])
|
||||
|
||||
self._child_instances = None
|
||||
|
||||
def get_children(self, categories=None):
|
||||
"""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]
|
||||
self._child_instances = [self.system.load_item(child) for child in self.definition['children']]
|
||||
|
||||
if categories is None:
|
||||
return self._child_instances
|
||||
else:
|
||||
return [child for child in self._child_instances if child.type in categories]
|
||||
|
||||
|
||||
def get_xml(self):
|
||||
''' For conversions between JSON and legacy XML representations.
|
||||
'''
|
||||
@@ -192,3 +227,12 @@ class XModuleDescriptor(Plugin):
|
||||
# Full ==> what we edit
|
||||
# '''
|
||||
# raise NotImplementedError
|
||||
|
||||
|
||||
class DescriptorSystem(object):
|
||||
def __init__(self, load_item):
|
||||
"""
|
||||
load_item: Takes a Location and returns and XModuleDescriptor
|
||||
"""
|
||||
|
||||
self.load_item = load_item
|
||||
|
||||
@@ -107,3 +107,6 @@ If code qualifies:
|
||||
* When impossible, it should live in the github repo.
|
||||
* Discussion should live on github, Basecamp or Pivotal, depending on
|
||||
context.
|
||||
* Notes for later fixes should in general be put into Pivotal as stories.
|
||||
If they are left in the code, they should be prefixed by
|
||||
# TODO (<name>)
|
||||
|
||||
@@ -44,10 +44,27 @@ You should be familiar with the following. If you're not, go read some docs...
|
||||
|
||||
### Common libraries
|
||||
|
||||
- x_modules -- generic learning modules. *x* can be sequence, video, template, html, vertical, capa, etc. These are the things that one puts inside sections in the course structure. Modules know how to render themselves to html, how to score themselves, and handle ajax calls from the front end.
|
||||
- x_modules take a 'system context' parameter, which helps isolate xmodules from any particular application, so they can be used in many places. The modules should make no references to Django (though there are still a few left). The system context knows how to render things, track events, complain about 404s, etc.
|
||||
- TODO: document the system context interface--it's different in `x_module.XModule.__init__` and in `x_module tests.py` (do this in the code, not here)
|
||||
- in `common/lib/xmodule`
|
||||
- xmodule: generic learning modules. *x* can be sequence, video, template, html,
|
||||
vertical, capa, etc. These are the things that one puts inside sections
|
||||
in the course structure.
|
||||
|
||||
- XModuleDescriptor: This defines the problem and all data and UI needed to edit
|
||||
that problem. It is unaware of any student data, but can be used to retrieve
|
||||
an XModule, which is aware of that student state.
|
||||
|
||||
- XModule: The XModule is a problem instance that is particular to a student. It knows
|
||||
how to render itself to html to display the problem, how to score itself,
|
||||
and how to handle ajax calls from the front end.
|
||||
|
||||
- Both XModule and XModuleDescriptor take system context parameters. These are named
|
||||
ModuleSystem and DescriptorSystem respectively. These help isolate the XModules
|
||||
from any interactions with external resources that they require.
|
||||
|
||||
For instance, the DescriptorSystem has a function to load an XModuleDescriptor
|
||||
from a Location object, and the ModuleSystem knows how to render things,
|
||||
track events, and complain about 404s
|
||||
- TODO: document the system context interface--it's different in `x_module.XModule.__init__` and in `x_module tests.py` (do this in the code, not here)
|
||||
- in `common/lib/xmodule`
|
||||
|
||||
- capa modules -- defines `LoncapaProblem` and many related things.
|
||||
- in `common/lib/capa`
|
||||
@@ -76,7 +93,14 @@ The LMS is a django site, with root in `lms/`. It runs in many different enviro
|
||||
|
||||
- See `lms/urls.py` for the wirings of urls to views.
|
||||
|
||||
- Tracking: there is support for basic tracking of client-side events in `lms/djangoapps/track`.
|
||||
- Tracking: there is support for basic tracking of client-side events in `lms/djangoapps/track`.
|
||||
|
||||
### CMS
|
||||
|
||||
The CMS is a django site, with root in `cms`. It can run in a number of different
|
||||
environments, defined in `cms/envs`.
|
||||
|
||||
- Core rendering path: Still TBD
|
||||
|
||||
### Other modules
|
||||
|
||||
|
||||
Reference in New Issue
Block a user