WIP: Get the cms running. Component previews work

This commit is contained in:
Calen Pennington
2012-12-28 14:33:33 -05:00
parent 85e109da57
commit 6427dd6742
16 changed files with 241 additions and 139 deletions

View File

@@ -1,5 +1,6 @@
from pkg_resources import resource_string
from xmodule.mako_module import MakoModuleDescriptor
from xmodule.model import Scope, String
import logging
log = logging.getLogger(__name__)
@@ -14,13 +15,15 @@ class EditingDescriptor(MakoModuleDescriptor):
"""
mako_template = "widgets/raw-edit.html"
data = String(scope=Scope.content, default='')
# cdodge: a little refactoring here, since we're basically doing the same thing
# here as with our parent class, let's call into it to get the basic fields
# set and then add our additional fields. Trying to keep it DRY.
def get_context(self):
_context = MakoModuleDescriptor.get_context(self)
# Add our specific template information (the raw data body)
_context.update({'data': self.definition.get('data', '')})
_context.update({'data': self.data})
return _context

View File

@@ -106,7 +106,7 @@ class ErrorDescriptor(JSONEditingDescriptor):
def from_descriptor(cls, descriptor, error_msg='Error not available'):
return cls._construct(
descriptor.system,
json.dumps(descriptor._model_data, indent=4),
descriptor._model_data,
error_msg,
location=descriptor.location,
)

View File

@@ -32,9 +32,7 @@ class MakoModuleDescriptor(XModuleDescriptor):
"""
Return the context to render the mako template with
"""
return {'module': self,
'editable_metadata_fields': self.editable_fields
}
return {'module': self}
def get_html(self):
return self.system.render_template(

View File

@@ -15,11 +15,11 @@ def as_draft(location):
def wrap_draft(item):
"""
Sets `item.metadata['is_draft']` to `True` if the item is a
draft, and false otherwise. Sets the item's location to the
Sets `item.cms.is_draft` to `True` if the item is a
draft, and `False` otherwise. Sets the item's location to the
non-draft location in either case
"""
item.metadata['is_draft'] = item.location.revision == DRAFT
item.cms.is_draft = item.location.revision == DRAFT
item.location = item.location._replace(revision=None)
return item
@@ -112,7 +112,7 @@ class DraftModuleStore(ModuleStoreBase):
"""
draft_loc = as_draft(location)
draft_item = self.get_item(location)
if not draft_item.metadata['is_draft']:
if not draft_item.cms.is_draft:
self.clone_item(location, draft_loc)
return super(DraftModuleStore, self).update_item(draft_loc, data)
@@ -127,7 +127,7 @@ class DraftModuleStore(ModuleStoreBase):
"""
draft_loc = as_draft(location)
draft_item = self.get_item(location)
if not draft_item.metadata['is_draft']:
if not draft_item.cms.is_draft:
self.clone_item(location, draft_loc)
return super(DraftModuleStore, self).update_children(draft_loc, children)
@@ -143,7 +143,7 @@ class DraftModuleStore(ModuleStoreBase):
draft_loc = as_draft(location)
draft_item = self.get_item(location)
if not draft_item.metadata['is_draft']:
if not draft_item.cms.is_draft:
self.clone_item(location, draft_loc)
if 'is_draft' in metadata:
@@ -175,8 +175,8 @@ class DraftModuleStore(ModuleStoreBase):
draft = self.get_item(location)
metadata = {}
metadata.update(draft.metadata)
metadata['published_date'] = tuple(datetime.utcnow().timetuple())
metadata['published_by'] = published_by_id
metadata.cms.published_date = datetime.utcnow()
metadata.cms.published_by = published_by_id
super(DraftModuleStore, self).update_item(location, draft.definition.get('data', {}))
super(DraftModuleStore, self).update_children(location, draft.definition.get('children', []))
super(DraftModuleStore, self).update_metadata(location, metadata)

View File

@@ -52,6 +52,11 @@ def own_metadata(module):
field.name not in inherited_metadata and
field.name in module._model_data):
metadata[field.name] = module._model_data[field.name]
try:
metadata[field.name] = module._model_data[field.name]
except KeyError:
# Ignore any missing keys in _model_data
pass
return metadata

View File

@@ -1,7 +1,9 @@
import pymongo
import sys
import logging
from bson.son import SON
from collections import namedtuple
from fs.osfs import OSFS
from itertools import repeat
from path import path
@@ -11,17 +13,79 @@ from xmodule.errortracker import null_error_tracker, exc_info_to_str
from xmodule.mako_module import MakoDescriptorSystem
from xmodule.x_module import XModuleDescriptor
from xmodule.error_module import ErrorDescriptor
from xmodule.runtime import DbModel, KeyValueStore, InvalidScopeError
from xmodule.model import Scope
from . import ModuleStoreBase, Location
from .draft import DraftModuleStore
from .exceptions import (ItemNotFoundError,
DuplicateItemError)
log = logging.getLogger(__name__)
# TODO (cpennington): This code currently operates under the assumption that
# there is only one revision for each item. Once we start versioning inside the CMS,
# that assumption will have to change
class MongoKeyValueStore(KeyValueStore):
"""
A KeyValueStore that maps keyed data access to one of the 3 data areas
known to the MongoModuleStore (data, children, and metadata)
"""
def __init__(self, data, children, metadata):
self._data = data
self._children = children
self._metadata = metadata
def get(self, key):
print "GET", key
if key.field_name == 'children':
return self._children
elif key.scope == Scope.settings:
return self._metadata[key.field_name]
elif key.scope == Scope.content:
if key.field_name == 'data' and not isinstance(self._data, dict):
return self._data
else:
return self._data[key.field_name]
else:
raise InvalidScopeError(key.scope)
def set(self, key, value):
print "SET", key, value
if key.field_name == 'children':
self._children = value
elif key.scope == Scope.settings:
self._metadata[key.field_name] = value
elif key.scope == Scope.content:
if key.field_name == 'data' and not isinstance(self._data, dict):
self._data = value
else:
self._data[key.field_name] = value
else:
raise InvalidScopeError(key.scope)
def delete(self, key):
print "DELETE", key
if key.field_name == 'children':
self._children = []
elif key.scope == Scope.settings:
if key.field_name in self._metadata:
del self._metadata[key.field_name]
elif key.scope == Scope.content:
if key.field_name == 'data' and not isinstance(self._data, dict):
self._data = None
else:
del self._data[key.field_name]
else:
raise InvalidScopeError(key.scope)
MongoUsage = namedtuple('MongoUsage', 'id, def_id')
class CachingDescriptorSystem(MakoDescriptorSystem):
"""
A system that has a cache of module json that it will use to load modules
@@ -64,8 +128,21 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
# always load an entire course. We're punting on this until after launch, and then
# will build a proper course policy framework.
try:
return XModuleDescriptor.load_from_json(json_data, self, self.default_class)
class_ = XModuleDescriptor.load_class(
json_data['location']['category'],
self.default_class
)
definition = json_data.get('definition', {})
kvs = MongoKeyValueStore(
definition.get('data', {}),
definition.get('children', []),
json_data.get('metadata', {}),
)
model_data = DbModel(kvs, class_, None, MongoUsage(self.course_id, location))
return class_(self, location, model_data)
except:
log.debug("Failed to load descriptor", exc_info=True)
return ErrorDescriptor.from_json(
json_data,
self,

View File

@@ -3,6 +3,13 @@ from collections import MutableMapping, namedtuple
from .model import ModuleScope, ModelType
class InvalidScopeError(Exception):
"""
Raised to indicated that operating on the supplied scope isn't allowed by a KeyValueStore
"""
pass
class KeyValueStore(object):
"""The abstract interface for Key Value Stores."""
@@ -102,8 +109,12 @@ class DbModel(MutableMapping):
def __len__(self):
return len(self.keys())
def __contains__(self, item):
return item in self.keys()
def keys(self):
fields = [field.name for field in self._module_cls.fields]
for namespace_name in self._module_cls.namespaces:
fields.extend(field.name for field in getattr(self._module_cls, namespace_name).fields)
print fields
return fields

View File

@@ -28,13 +28,41 @@ class VideoModule(XModule):
css = {'scss': [resource_string(__name__, 'css/video/display.scss')]}
js_module_name = "Video"
youtube = String(help="Youtube ids for each speed, in the format <speed>:<id>[,<speed>:<id> ...]", scope=Scope.content)
show_captions = String(help="Whether to display captions with this video", scope=Scope.content)
source = String(help="External source for this video", scope=Scope.content)
track = String(help="Subtitle file", scope=Scope.content)
position = Int(help="Current position in the video", scope=Scope.student_state, default=0)
data = String(help="XML data for the problem", scope=Scope.content)
position = Int(help="Current position in the video", scope=Scope.student_state)
display_name = String(help="Display name for this module", scope=Scope.settings)
def __init__(self, *args, **kwargs):
XModule.__init__(self, *args, **kwargs)
xmltree = etree.fromstring(self.data)
self.youtube = xmltree.get('youtube')
self.position = 0
self.show_captions = xmltree.get('show_captions', 'true')
self.source = self._get_source(xmltree)
self.track = self._get_track(xmltree)
def _get_source(self, xmltree):
# find the first valid source
return self._get_first_external(xmltree, 'source')
def _get_track(self, xmltree):
# find the first valid track
return self._get_first_external(xmltree, 'track')
def _get_first_external(self, xmltree, tag):
"""
Will return the first valid element
of the given tag.
'valid' means has a non-empty 'src' attribute
"""
result = None
for element in xmltree.findall(tag):
src = element.get('src')
if src:
result = src
break
return result
def handle_ajax(self, dispatch, get):
'''
@@ -87,50 +115,7 @@ class VideoModule(XModule):
})
class VideoDescriptor(RawDescriptor):
module_class = VideoModule
stores_state = True
template_dir_name = "video"
youtube = String(help="Youtube ids for each speed, in the format <speed>:<id>[,<speed>:<id> ...]", scope=Scope.content)
show_captions = String(help="Whether to display captions with this video", scope=Scope.content)
source = String(help="External source for this video", scope=Scope.content)
track = String(help="Subtitle file", scope=Scope.content)
@classmethod
def definition_from_xml(cls, xml_object, system):
return {
'youtube': xml_object.get('youtube'),
'show_captions': xml_object.get('show_captions', 'true'),
'source': _get_first_external(xml_object, 'source'),
'track': _get_first_external(xml_object, 'track'),
}, []
def definition_to_xml(self, resource_fs):
xml_object = etree.Element('video', {
'youtube': self.youtube,
'show_captions': self.show_captions,
})
if self.source is not None:
SubElement(xml_object, 'source', {'src': self.source})
if self.track is not None:
SubElement(xml_object, 'track', {'src': self.track})
return xml_object
def _get_first_external(xmltree, tag):
"""
Will return the first valid element
of the given tag.
'valid' means has a non-empty 'src' attribute
"""
result = None
for element in xmltree.findall(tag):
src = element.get('src')
if src:
result = src
break
return result