Begin using a Keystore for XML parsing. Still broken: sequence icons, custom tags, problems, video js

This commit is contained in:
Calen Pennington
2012-06-27 14:25:44 -04:00
parent 5b8120280e
commit de07b8b345
27 changed files with 814 additions and 826 deletions

View File

@@ -0,0 +1,87 @@
from fs.osfs import OSFS
from importlib import import_module
from lxml import etree
from path import path
from xmodule.x_module import XModuleDescriptor, XMLParsingSystem
from . import ModuleStore, Location
from .exceptions import ItemNotFoundError
class XMLModuleStore(ModuleStore):
"""
An XML backed ModuleStore
"""
def __init__(self, org, course, data_dir, default_class=None):
self.data_dir = path(data_dir)
self.modules = {}
module_path, _, class_name = default_class.rpartition('.')
class_ = getattr(import_module(module_path), class_name)
self.default_class = class_
with open(data_dir / "course.xml") as course_file:
class ImportSystem(XMLParsingSystem):
def __init__(self, keystore):
self.unnamed_modules = 0
def process_xml(xml):
try:
xml_data = etree.fromstring(xml)
except:
print xml
raise
if xml_data.get('name'):
xml_data.set('slug', Location.clean(xml_data.get('name')))
else:
self.unnamed_modules += 1
xml_data.set('slug', '{tag}_{count}'.format(tag=xml_data.tag, count=self.unnamed_modules))
module = XModuleDescriptor.load_from_xml(etree.tostring(xml_data), self, org, course, keystore.default_class)
keystore.modules[module.url] = module
return module
XMLParsingSystem.__init__(self, keystore.get_item, OSFS(data_dir), process_xml)
ImportSystem(self).process_xml(course_file.read())
def get_item(self, 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
location: Something that can be passed to Location
"""
location = Location(location)
try:
return self.modules[location.url()]
except KeyError:
raise ItemNotFoundError(location)
def create_item(self, location):
raise NotImplementedError("XMLModuleStores are read-only")
def update_item(self, location, data):
"""
Set the data in the item specified by the location to
data
location: Something that can be passed to Location
data: A nested dictionary of problem data
"""
raise NotImplementedError("XMLModuleStores are read-only")
def update_children(self, location, children):
"""
Set the children for the item specified by the location to
data
location: Something that can be passed to Location
children: A list of child item identifiers
"""
raise NotImplementedError("XMLModuleStores are read-only")

View File

@@ -1,62 +0,0 @@
import capa_module
import html_module
import schematic_module
import seq_module
import template_module
import vertical_module
import video_module
# Import all files in modules directory, excluding backups (# and . in name)
# and __init__
#
# Stick them in a list
# modx_module_list = []
# for f in os.listdir(os.path.dirname(__file__)):
# if f!='__init__.py' and \
# f[-3:] == ".py" and \
# "." not in f[:-3] \
# and '#' not in f:
# mod_path = 'courseware.modules.'+f[:-3]
# mod = __import__(mod_path, fromlist = "courseware.modules")
# if 'Module' in mod.__dict__:
# modx_module_list.append(mod)
#print modx_module_list
modx_module_list = [capa_module, html_module, schematic_module, seq_module, template_module, vertical_module, video_module]
#print modx_module_list
modx_modules = {}
# Convert list to a dictionary for lookup by tag
def update_modules():
global modx_modules
modx_modules = dict()
for module in modx_module_list:
for tag in module.Module.get_xml_tags():
modx_modules[tag] = module.Module
update_modules()
def get_module_class(tag):
''' Given an XML tag (e.g. 'video'), return
the associated module (e.g. video_module.Module).
'''
if tag not in modx_modules:
update_modules()
return modx_modules[tag]
def get_module_id(tag):
''' Given an XML tag (e.g. 'video'), return
the default ID for that module (e.g. 'youtube_id')
'''
return modx_modules[tag].id_attribute
def get_valid_tags():
return modx_modules.keys()
def get_default_ids():
tags = get_valid_tags()
ids = map(get_module_id, tags)
return dict(zip(tags, ids))

View File

@@ -0,0 +1,95 @@
import json
import random
from lxml import etree
from x_module import XModule, XModuleDescriptor
class ModuleDescriptor(XModuleDescriptor):
pass
def group_from_value(groups, v):
''' Given group: (('a',0.3),('b',0.4),('c',0.3)) And random value
in [0,1], return the associated group (in the above case, return
'a' if v<0.3, 'b' if 0.3<=v<0.7, and 'c' if v>0.7
'''
sum = 0
for (g, p) in groups:
sum = sum + p
if sum > v:
return g
# Round off errors might cause us to run to the end of the list
# If the do, return the last element
return g
class Module(XModule):
"""
Implements an A/B test with an aribtrary number of competing groups
Format:
<abtest>
<group name="a" portion=".1"><contenta/></group>
<group name="b" portion=".2"><contentb/></group>
<default><contentdefault/></default>
</abtest>
"""
def __init__(self, system, xml, item_id, instance_state=None, shared_state=None):
XModule.__init__(self, system, xml, item_id, instance_state, shared_state)
self.xmltree = etree.fromstring(xml)
target_groups = self.xmltree.findall('group')
if shared_state is None:
target_values = [
(elem.get('name'), float(elem.get('portion')))
for elem in target_groups
]
default_value = 1 - sum(val for (_, val) in target_values)
self.group = group_from_value(
target_values + [(None, default_value)],
random.uniform(0, 1)
)
else:
shared_state = json.loads(shared_state)
# TODO (cpennington): Remove this once we aren't passing in
# groups from django groups
if 'groups' in shared_state:
self.group = None
target_names = [elem.get('name') for elem in target_groups]
for group in shared_state['groups']:
if group in target_names:
self.group = group
break
else:
self.group = shared_state['group']
def get_shared_state(self):
return json.dumps({'group': self.group})
def _xml_children(self):
group = None
if self.group is None:
group = self.xmltree.find('default')
else:
for candidate_group in self.xmltree.find('group'):
if self.group == candidate_group.get('name'):
group = candidate_group
break
if group is None:
return []
return list(group)
def get_children(self):
return [self.module_from_xml(child) for child in self._xml_children()]
def rendered_children(self):
return [self.render_function(child) for child in self._xml_children()]
def get_html(self):
return '\n'.join(child.get_html() for child in self.get_children())

View File

@@ -81,14 +81,7 @@ class Module(XModule):
reset.
'''
id_attribute = "filename"
@classmethod
def get_xml_tags(c):
return ["problem"]
def get_state(self):
def get_instance_state(self):
state = self.lcp.get_state()
state['attempts'] = self.attempts
return json.dumps(state)
@@ -191,8 +184,8 @@ class Module(XModule):
return html
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
def __init__(self, system, xml, item_id, instance_state=None, shared_state=None):
XModule.__init__(self, system, xml, item_id, instance_state, shared_state)
self.attempts = 0
self.max_attempts = None
@@ -232,19 +225,19 @@ class Module(XModule):
self.show_answer = "closed"
self.rerandomize = only_one(dom2.xpath('/problem/@rerandomize'))
if self.rerandomize == "" or self.rerandomize=="always" or self.rerandomize=="true":
self.rerandomize="always"
elif self.rerandomize=="false" or self.rerandomize=="per_student":
self.rerandomize="per_student"
elif self.rerandomize=="never":
self.rerandomize="never"
if self.rerandomize == "" or self.rerandomize == "always" or self.rerandomize == "true":
self.rerandomize = "always"
elif self.rerandomize == "false" or self.rerandomize == "per_student":
self.rerandomize = "per_student"
elif self.rerandomize == "never":
self.rerandomize = "never"
else:
raise Exception("Invalid rerandomize attribute "+self.rerandomize)
raise Exception("Invalid rerandomize attribute " + self.rerandomize)
if state!=None:
state=json.loads(state)
if state!=None and 'attempts' in state:
self.attempts=state['attempts']
if instance_state != None:
instance_state = json.loads(instance_state)
if instance_state != None and 'attempts' in instance_state:
self.attempts = instance_state['attempts']
# TODO: Should be: self.filename=only_one(dom2.xpath('/problem/@filename'))
self.filename= "problems/"+only_one(dom2.xpath('/problem/@filename'))+".xml"
@@ -267,7 +260,7 @@ class Module(XModule):
else:
raise
try:
self.lcp=LoncapaProblem(fp, self.item_id, state, seed = seed, system=self.system)
self.lcp=LoncapaProblem(fp, self.item_id, instance_state, seed = seed, system=self.system)
except Exception,err:
msg = '[courseware.capa.capa_module.Module.init] error %s: cannot create LoncapaProblem %s' % (err,self.filename)
log.exception(msg)
@@ -277,7 +270,7 @@ class Module(XModule):
# create a dummy problem with error message instead of failing
fp = StringIO.StringIO('<problem><text><font color="red" size="+2">Problem file %s has an error:</font>%s</text></problem>' % (self.filename,msg))
fp.name = "StringIO"
self.lcp=LoncapaProblem(fp, self.item_id, state, seed = seed, system=self.system)
self.lcp=LoncapaProblem(fp, self.item_id, instance_state, seed = seed, system=self.system)
else:
raise

View File

@@ -0,0 +1,10 @@
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
class HiddenModule(XModule):
pass
class HiddenDescriptor(RawDescriptor):
module_class = HiddenModule

View File

@@ -1,75 +1,34 @@
import json
import logging
from x_module import XModule
from mako_module import MakoModuleDescriptor
from xmodule.x_module import XModule
from xmodule.mako_module import MakoModuleDescriptor
from xmodule.xml_module import XmlDescriptor
from lxml import etree
from pkg_resources import resource_string
log = logging.getLogger("mitx.courseware")
#-----------------------------------------------------------------------------
class HtmlModuleDescriptor(MakoModuleDescriptor):
class HtmlModule(XModule):
def get_html(self):
return self.html
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)
self.html = self.definition['data']['text']
class HtmlDescriptor(MakoModuleDescriptor, XmlDescriptor):
"""
Module for putting raw html in a course
"""
mako_template = "widgets/html-edit.html"
module_class = HtmlModule
js = {'coffee': [resource_string(__name__, 'js/module/html.coffee')]}
js_module = 'HTML'
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None):
"""
Creates an instance of this descriptor from the supplied xml_data.
This may be overridden by subclasses
xml_data: A string of xml that will be translated into data and children for
this module
system: An XModuleSystem for interacting with external resources
org and course are optional strings that will be used in the generated modules
url identifiers
"""
xml_object = etree.fromstring(xml_data)
return cls(
system,
definition={'data': {'text': xml_data}},
location=['i4x',
org,
course,
xml_object.tag,
xml_object.get('name')]
)
class Module(XModule):
id_attribute = 'filename'
def get_state(self):
return json.dumps({ })
@classmethod
def get_xml_tags(c):
return ["html"]
def get_html(self):
if self.filename==None:
xmltree=etree.fromstring(self.xml)
textlist=[xmltree.text]+[etree.tostring(i) for i in xmltree]+[xmltree.tail]
textlist=[i for i in textlist if type(i)==str]
return "".join(textlist)
try:
filename="html/"+self.filename
return self.filestore.open(filename).read()
except: # For backwards compatibility. TODO: Remove
if self.DEBUG:
log.info('[courseware.modules.html_module] filename=%s' % self.filename)
return self.system.render_template(self.filename, {'id': self.item_id}, namespace='course')
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml)
self.filename = None
filename_l=xmltree.xpath("/html/@filename")
if len(filename_l)>0:
self.filename=str(filename_l[0])
def definition_from_xml(cls, xml_object, system):
return {'data': {'text': etree.tostring(xml_object)}}

View File

@@ -1,8 +1,9 @@
from pkg_resources import resource_string
from mako_module import MakoModuleDescriptor
from lxml import etree
from xmodule.mako_module import MakoModuleDescriptor
from xmodule.xml_module import XmlDescriptor
class RawDescriptor(MakoModuleDescriptor):
class RawDescriptor(MakoModuleDescriptor, XmlDescriptor):
"""
Module that provides a raw editing view of it's data and children
"""
@@ -18,24 +19,5 @@ class RawDescriptor(MakoModuleDescriptor):
}
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None):
"""
Creates an instance of this descriptor from the supplied xml_data.
This may be overridden by subclasses
xml_data: A string of xml that will be translated into data and children for
this module
system: An XModuleSystem for interacting with external resources
org and course are optional strings that will be used in the generated modules
url identifiers
"""
xml_object = etree.fromstring(xml_data)
return cls(
system,
definition={'data': xml_data},
location=['i4x',
org,
course,
xml_object.tag,
xml_object.get('name')]
)
def definition_from_xml(cls, xml_object, system):
return {'data': etree.tostring(xml_object)}

View File

@@ -6,18 +6,5 @@ class ModuleDescriptor(XModuleDescriptor):
pass
class Module(XModule):
id_attribute = 'id'
def get_state(self):
return json.dumps({ })
@classmethod
def get_xml_tags(c):
return ["schematic"]
def get_html(self):
return '<input type="hidden" class="schematic" name="{item_id}" height="480" width="640">'.format(item_id=self.item_id)
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)

View File

@@ -3,8 +3,9 @@ import logging
from lxml import etree
from x_module import XModule
from mako_module import MakoModuleDescriptor
from xmodule.mako_module import MakoModuleDescriptor
from xmodule.xml_module import XmlDescriptor
from xmodule.x_module import XModule
from xmodule.progress import Progress
log = logging.getLogger("mitx.common.lib.seq_module")
@@ -13,32 +14,17 @@ log = logging.getLogger("mitx.common.lib.seq_module")
# OBSOLETE: This obsoletes 'type'
class_priority = ['video', 'problem']
class Module(XModule):
class SequenceModule(XModule):
''' Layout module which lays out content in a temporal sequence
'''
id_attribute = 'id'
def get_instance_state(self):
return json.dumps({'position': self.position})
def get_state(self):
return json.dumps({ 'position':self.position })
@classmethod
def get_xml_tags(c):
obsolete_tags = ["sequential", 'tab']
modern_tags = ["videosequence"]
return obsolete_tags + modern_tags
def get_html(self):
self.render()
return self.content
def get_init_js(self):
self.render()
return self.init_js
def get_destroy_js(self):
self.render()
return self.destroy_js
def get_progress(self):
''' Return the total progress, adding total done and total available.
(assumes that each submodule uses the same "units" for progress.)
@@ -60,53 +46,51 @@ class Module(XModule):
if self.rendered:
return
## Returns a set of all types of all sub-children
child_classes = [set([i.tag for i in e.iter()]) for e in self.xmltree]
contents = []
for child in self.get_display_items():
progress = child.get_progress()
contents.append({
'content': child.get_html(),
'title': "\n".join(
grand_child.name.strip()
for grand_child in child.get_children()
if grand_child.name is not None
),
'progress_status': Progress.to_js_status_str(progress),
'progress_detail': Progress.to_js_detail_str(progress),
'type': child.get_icon_class(),
})
titles = ["\n".join([i.get("name").strip() for i in e.iter() if i.get("name") is not None]) \
for e in self.xmltree]
children = self.get_children()
progresses = [child.get_progress() for child in children]
self.contents = self.rendered_children()
for contents, title, progress in zip(self.contents, titles, progresses):
contents['title'] = title
contents['progress_status'] = Progress.to_js_status_str(progress)
contents['progress_detail'] = Progress.to_js_detail_str(progress)
for (content, element_class) in zip(self.contents, child_classes):
new_class = 'other'
for c in class_priority:
if c in element_class:
new_class = c
content['type'] = new_class
print json.dumps(contents, indent=4)
# Split </script> tags -- browsers handle this as end
# of script, even if it occurs mid-string. Do this after json.dumps()ing
# so that we can be sure of the quotations being used
params={'items': json.dumps(self.contents).replace('</script>', '<"+"/script>'),
'id': self.item_id,
'position': self.position,
'titles': titles,
'tag': self.xmltree.tag}
params = {'items': json.dumps(contents).replace('</script>', '<"+"/script>'),
'element_id': "-".join(str(v) for v in self.location.list()),
'item_id': self.id,
'position': self.position,
'tag': self.location.category}
if self.xmltree.tag in ['sequential', 'videosequence']:
self.content = self.system.render_template('seq_module.html', params)
if self.xmltree.tag == 'tab':
self.content = self.system.render_template('tab_module.html', params)
log.debug("rendered content: %s", content)
self.content = self.system.render_template('seq_module.html', params)
self.rendered = True
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
self.xmltree = etree.fromstring(xml)
def get_icon_class(self):
child_classes = set(child.get_icon_class() for child in self.get_children())
new_class = 'other'
for c in class_priority:
if c in child_classes:
new_class = c
return new_class
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)
self.position = 1
if state is not None:
state = json.loads(state)
if 'position' in state: self.position = int(state['position'])
if instance_state is not None:
state = json.loads(instance_state)
if 'position' in state:
self.position = int(state['position'])
# if position is specified in system, then use that instead
if system.get('position'):
@@ -115,23 +99,13 @@ class Module(XModule):
self.rendered = False
class SequenceDescriptor(MakoModuleDescriptor):
class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor):
mako_template = 'widgets/sequence-edit.html'
module_class = SequenceModule
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None):
xml_object = etree.fromstring(xml_data)
children = [
def definition_from_xml(cls, xml_object, system):
return {'children': [
system.process_xml(etree.tostring(child_module)).url
for child_module in xml_object
]
return cls(
system, {'children': children},
location=['i4x',
org,
course,
xml_object.tag,
xml_object.get('name')]
)
]}

View File

@@ -13,14 +13,15 @@ setup(
# for a description of entry_points
entry_points={
'xmodule.v1': [
"chapter = seq_module:SequenceDescriptor",
"course = seq_module:SequenceDescriptor",
"html = html_module:HtmlModuleDescriptor",
"section = translation_module:SemanticSectionDescriptor",
"sequential = seq_module:SequenceDescriptor",
"vertical = seq_module:SequenceDescriptor",
"problemset = seq_module:SequenceDescriptor",
"videosequence = seq_module:SequenceDescriptor",
"chapter = xmodule.seq_module:SequenceDescriptor",
"course = xmodule.seq_module:SequenceDescriptor",
"html = xmodule.html_module:HtmlDescriptor",
"section = xmodule.translation_module:SemanticSectionDescriptor",
"sequential = xmodule.seq_module:SequenceDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor",
"problemset = xmodule.seq_module:SequenceDescriptor",
"videosequence = xmodule.seq_module:SequenceDescriptor",
"video = xmodule.video_module:VideoDescriptor",
]
}
)

View File

@@ -31,18 +31,11 @@ class Module(XModule):
Renders to::
More information given in <a href="/book/234">the text</a>
"""
def get_state(self):
return json.dumps({})
@classmethod
def get_xml_tags(c):
return ['customtag']
def get_html(self):
return self.html
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
def __init__(self, system, xml, item_id, instance_state=None, shared_state=None):
XModule.__init__(self, system, xml, item_id, instance_state, shared_state)
xmltree = etree.fromstring(xml)
filename = xmltree.find('impl').text
params = dict(xmltree.items())

View File

@@ -1,23 +1,10 @@
import json
from x_module import XModule, XModuleDescriptor
from xmodule.x_module import XModule
from xmodule.seq_module import SequenceDescriptor
from xmodule.progress import Progress
from lxml import etree
class ModuleDescriptor(XModuleDescriptor):
pass
class Module(XModule):
class VerticalModule(XModule):
''' Layout module for laying out submodules vertically.'''
id_attribute = 'id'
def get_state(self):
return json.dumps({ })
@classmethod
def get_xml_tags(c):
return ["vertical", "problemset"]
def get_html(self):
return self.system.render_template('vert_module.html', {
'items': self.contents
@@ -30,8 +17,10 @@ class Module(XModule):
progress = reduce(Progress.add_counts, progresses)
return progress
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml)
self.contents=[(e.get("name"),self.render_function(e)) \
for e in xmltree]
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)
self.contents = [child.get_html() for child in self.get_display_items()]
class VerticalDescriptor(SequenceDescriptor):
module_class = VerticalModule

View File

@@ -3,16 +3,13 @@ import logging
from lxml import etree
from x_module import XModule, XModuleDescriptor
from progress import Progress
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
log = logging.getLogger("mitx.courseware.modules")
log = logging.getLogger(__name__)
class ModuleDescriptor(XModuleDescriptor):
pass
class Module(XModule):
id_attribute = 'youtube'
class VideoModule(XModule):
video_time = 0
def handle_ajax(self, dispatch, get):
@@ -39,14 +36,9 @@ class Module(XModule):
'''
return None
def get_state(self):
def get_instance_state(self):
log.debug(u"STATE POSITION {0}".format(self.position))
return json.dumps({ 'position': self.position })
@classmethod
def get_xml_tags(c):
'''Tags in the courseware file guaranteed to correspond to the module'''
return ["video"]
return json.dumps({'position': self.position})
def video_list(self):
return self.youtube
@@ -54,27 +46,27 @@ class Module(XModule):
def get_html(self):
return self.system.render_template('video.html', {
'streams': self.video_list(),
'id': self.item_id,
'id': self.id,
'position': self.position,
'name': self.name,
'annotations': self.annotations,
})
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree = etree.fromstring(xml)
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)
xmltree = etree.fromstring(self.definition['data'])
self.youtube = xmltree.get('youtube')
self.name = xmltree.get('name')
self.position = 0
if state is not None:
state = json.loads(state)
if instance_state is not None:
state = json.loads(instance_state)
if 'position' in state:
self.position = int(float(state['position']))
self.annotations=[(e.get("name"),self.render_function(e)) \
self.annotations = [(e.get("name"), self.render_function(e)) \
for e in xmltree]
class VideoSegmentDescriptor(XModuleDescriptor):
pass
class VideoDescriptor(RawDescriptor):
module_class = VideoModule

View File

@@ -3,6 +3,7 @@ import pkg_resources
import logging
from keystore import Location
from functools import partial
log = logging.getLogger('mitx.' + __name__)
@@ -56,85 +57,87 @@ class Plugin(object):
class XModule(object):
''' Implements a generic learning module.
Initialized on access with __init__, first time with state=None, and
then with state
Initialized on access with __init__, first time with instance_state=None, and
shared_state=None. In later instantiations, instance_state will not be None,
but shared_state may be
See the HTML module for a simple example
'''
id_attribute='id' # An attribute guaranteed to be unique
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
'''
Construct a new xmodule
@classmethod
def get_xml_tags(c):
''' Tags in the courseware file guaranteed to correspond to the module '''
return []
@classmethod
def get_usage_tags(c):
''' We should convert to a real module system
For now, this tells us whether we use this as an xmodule, a CAPA response type
or a CAPA input type '''
return ['xmodule']
system: An I4xSystem allowing access to external resources
location: Something Location-like that identifies this xmodule
definition: A dictionary containing 'data' and 'children'. Both are optional
'data': is a json object specifying the behavior of this xmodule
'children': is a list of xmodule urls for child modules that this module depends on
'''
self.system = system
self.location = Location(location)
self.definition = definition
self.instance_state = instance_state
self.shared_state = shared_state
self.id = self.location.url()
self.name = self.location.name
self.display_name = kwargs.get('display_name', '')
self._loaded_children = None
def get_name(self):
name = self.__xmltree.get('name')
if name:
if name:
return name
else:
else:
raise "We should iterate through children and find a default name"
def get_children(self):
'''
Return module instances for all the children of this module.
'''
children = [self.module_from_xml(e) for e in self.__xmltree]
return children
if self._loaded_children is None:
self._loaded_children = [self.system.get_module(child) for child in self.definition.get('children', [])]
return self._loaded_children
def rendered_children(self):
def get_display_items(self):
'''
Render all children.
This really ought to return a list of xmodules, instead of dictionaries
Returns a list of descendent module instances that will display immediately
inside this module
'''
children = [self.render_function(e) for e in self.__xmltree]
return children
items = []
for child in self.get_children():
items.extend(child.displayable_items())
def __init__(self, system = None, xml = None, item_id = None,
json = None, track_url=None, state=None):
''' In most cases, you must pass state or xml'''
if not item_id:
raise ValueError("Missing Index")
if not xml and not json:
raise ValueError("xml or json required")
if not system:
raise ValueError("System context required")
return items
self.xml = xml
self.json = json
self.item_id = item_id
self.state = state
self.DEBUG = False
self.__xmltree = etree.fromstring(xml) # PRIVATE
def displayable_items(self):
'''
Returns list of displayable modules contained by this module. If this module
is visible, should return [self]
'''
return [self]
if system:
## These are temporary; we really should go
## through self.system.
self.ajax_url = system.ajax_url
self.tracker = system.track_function
self.filestore = system.filestore
self.render_function = system.render_function
self.module_from_xml = system.module_from_xml
self.DEBUG = system.DEBUG
self.system = system
def get_icon_class(self):
'''
Return a class identifying this module in the context of an icon
'''
return 'other'
### Functions used in the LMS
def get_state(self):
''' State of the object, as stored in the database
def get_instance_state(self):
''' State of the object, as stored in the database
'''
return ""
return '{}'
def get_shared_state(self):
'''
Get state that should be shared with other instances
using the same 'shared_state_key' attribute.
'''
return '{}'
def get_score(self):
''' Score the student received on the problem.
''' Score the student received on the problem.
'''
return None
@@ -281,6 +284,7 @@ class XModuleDescriptor(Plugin):
self.name = Location(kwargs.get('location')).name
self.type = Location(kwargs.get('location')).category
self.url = Location(kwargs.get('location')).url()
self.display_name = kwargs.get('display_name')
# 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
@@ -302,33 +306,13 @@ class XModuleDescriptor(Plugin):
"""
raise NotImplementedError("get_html() must be provided by specific modules")
def get_xml(self):
''' For conversions between JSON and legacy XML representations.
'''
if self.xml:
return self.xml
else:
raise NotImplementedError("JSON->XML Translation not implemented")
def get_json(self):
''' For conversions between JSON and legacy XML representations.
'''
if self.json:
raise NotImplementedError
return self.json # TODO: Return context as well -- files, etc.
else:
raise NotImplementedError("XML->JSON Translation not implemented")
#def handle_cms_json(self):
# raise NotImplementedError
#def render(self, size):
# ''' Size: [thumbnail, small, full]
# Small ==> what we drag around
# Full ==> what we edit
# '''
# raise NotImplementedError
def xmodule_constructor(self, system):
"""
Returns a constructor for an XModule. This constructor takes two arguments:
instance_state and shared_state, and returns a fully nstantiated XModule
"""
return partial(self.module_class, system, self.url, self.definition,
display_name=self.display_name)
class DescriptorSystem(object):
def __init__(self, load_item, resources_fs):

View File

@@ -0,0 +1,41 @@
from xmodule.x_module import XModuleDescriptor
from lxml import etree
class XmlDescriptor(XModuleDescriptor):
"""
Mixin class for standardized parsing of from xml
"""
@classmethod
def definition_from_xml(cls, xml_object, system):
"""
Return the definition to be passed to the newly created descriptor
during from_xml
"""
raise NotImplementedError("%s does not implement definition_from_xml" % cls.__class__.__name__)
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None):
"""
Creates an instance of this descriptor from the supplied xml_data.
This may be overridden by subclasses
xml_data: A string of xml that will be translated into data and children for
this module
system: An XModuleSystem for interacting with external resources
org and course are optional strings that will be used in the generated modules
url identifiers
"""
xml_object = etree.fromstring(xml_data)
return cls(
system,
cls.definition_from_xml(xml_object, system),
location=['i4x',
org,
course,
xml_object.tag,
xml_object.get('slug')],
display_name=xml_object.get('name')
)