Begin using a Keystore for XML parsing. Still broken: sequence icons, custom tags, problems, video js
This commit is contained in:
87
common/lib/keystore/xml.py
Normal file
87
common/lib/keystore/xml.py
Normal 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")
|
||||
@@ -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))
|
||||
|
||||
|
||||
95
common/lib/xmodule/abtest_module.py
Normal file
95
common/lib/xmodule/abtest_module.py
Normal 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())
|
||||
@@ -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
|
||||
|
||||
|
||||
10
common/lib/xmodule/hidden_module.py
Normal file
10
common/lib/xmodule/hidden_module.py
Normal 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
|
||||
@@ -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)}}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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')]
|
||||
)
|
||||
]}
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
41
common/lib/xmodule/xml_module.py
Normal file
41
common/lib/xmodule/xml_module.py
Normal 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')
|
||||
)
|
||||
Reference in New Issue
Block a user