Commit 34d68348 by Calen Pennington

Begin using a Keystore for XML parsing. Still broken: sequence icons, custom…

Begin using a Keystore for XML parsing. Still broken: sequence icons, custom tags, problems, video js
parent 0a2fd0a1
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")
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))
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
......
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
class HiddenModule(XModule):
pass
class HiddenDescriptor(RawDescriptor):
module_class = HiddenModule
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)}}
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_state(self):
return json.dumps({ 'position':self.position })
def get_instance_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]
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
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(),
})
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}
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)
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}
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())
......
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
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")
self.xml = xml
self.json = json
self.item_id = item_id
self.state = state
self.DEBUG = False
self.__xmltree = etree.fromstring(xml) # PRIVATE
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
items = []
for child in self.get_children():
items.extend(child.displayable_items())
return items
def displayable_items(self):
'''
Returns list of displayable modules contained by this module. If this module
is visible, should return [self]
'''
return [self]
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):
......
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')
)
......@@ -19,10 +19,12 @@ from django.conf import settings
from student.models import UserProfile
from student.models import UserTestGroup
from courseware.models import StudentModuleCache
from mitxmako.shortcuts import render_to_string
from util.cache import cache
from multicourse import multicourse_settings
import xmodule
from keystore.django import keystore
''' This file will eventually form an abstraction layer between the
course XML file and the rest of the system.
......@@ -103,6 +105,7 @@ def course_xml_process(tree):
items without. Propagate due dates, grace periods, etc. to child
items.
'''
process_includes(tree)
replace_custom_tags(tree)
id_tag(tree)
propogate_downward_tag(tree, "due")
......@@ -113,45 +116,32 @@ def course_xml_process(tree):
return tree
def toc_from_xml(dom, active_chapter, active_section):
'''
Create a table of contents from the course xml.
Return format:
[ {'name': name, 'sections': SECTIONS, 'active': bool}, ... ]
where SECTIONS is a list
[ {'name': name, 'format': format, 'due': due, 'active' : bool}, ...]
active is set for the section and chapter corresponding to the passed
parameters. Everything else comes from the xml, or defaults to "".
chapters with name 'hidden' are skipped.
'''
name = dom.xpath('//course/@name')[0]
chapters = dom.xpath('//course[@name=$name]/chapter', name=name)
ch = list()
for c in chapters:
if c.get('name') == 'hidden':
continue
sections = list()
for s in dom.xpath('//course[@name=$name]/chapter[@name=$chname]/section',
name=name, chname=c.get('name')):
format = s.get("subtitle") if s.get("subtitle") else s.get("format") or ""
active = (c.get("name") == active_chapter and
s.get("name") == active_section)
sections.append({'name': s.get("name") or "",
'format': format,
'due': s.get("due") or "",
'active': active})
ch.append({'name': c.get("name"),
'sections': sections,
'active': c.get("name") == active_chapter})
return ch
def process_includes_dir(tree, dir):
"""
Process tree to replace all <include file=""> tags
with the contents of the file specified, relative to dir
"""
includes = tree.findall('.//include')
for inc in includes:
file = inc.get('file')
if file is not None:
try:
ifp = open(os.path.join(dir, file))
except Exception:
log.exception('Error in problem xml include: %s' % (etree.tostring(inc, pretty_print=True)))
log.exception('Cannot find file %s in %s' % (file, dir))
raise
try:
# read in and convert to XML
incxml = etree.XML(ifp.read())
except Exception:
log.exception('Error in problem xml include: %s' % (etree.tostring(inc, pretty_print=True)))
log.exception('Cannot parse XML in %s' % (file))
raise
# insert new XML into tree in place of inlcude
parent = inc.getparent()
parent.insert(parent.index(inc), incxml)
parent.remove(inc)
def replace_custom_tags_dir(tree, dir):
......@@ -181,78 +171,6 @@ def parse_course_file(filename, options, namespace):
return course_xml_process(xml)
def get_section(section, options, dirname):
'''
Given the name of a section, an options dict containing keys
'dev_content' and 'groups', and a directory to look in,
returns the xml tree for the section, or None if there's no
such section.
'''
filename = section + ".xml"
if filename not in os.listdir(dirname):
log.error(filename + " not in " + str(os.listdir(dirname)))
return None
tree = parse_course_file(filename, options, namespace='sections')
return tree
def get_module(tree, module, id_tag, module_id, sections_dirname, options):
'''
Given the xml tree of the course, get the xml string for a module
with the specified module type, id_tag, module_id. Looks in
sections_dirname for sections.
id_tag -- use id_tag if the place the module stores its id is not 'id'
'''
# Sanitize input
if not module.isalnum():
raise Exception("Module is not alphanumeric")
if not module_id.isalnum():
raise Exception("Module ID is not alphanumeric")
# Generate search
xpath_search='//{module}[(@{id_tag} = "{id}") or (@id = "{id}")]'.format(
module=module,
id_tag=id_tag,
id=module_id)
result_set = tree.xpath(xpath_search)
if len(result_set) < 1:
# Not found in main tree. Let's look in the section files.
section_list = (s[:-4] for s in os.listdir(sections_dirname) if s.endswith('.xml'))
for section in section_list:
try:
s = get_section(section, options, sections_dirname)
except etree.XMLSyntaxError:
ex = sys.exc_info()
raise ContentException("Malformed XML in " + section +
"(" + str(ex[1].msg) + ")")
result_set = s.xpath(xpath_search)
if len(result_set) != 0:
break
if len(result_set) > 1:
log.error("WARNING: Potentially malformed course file", module, module_id)
if len(result_set)==0:
log.error('[content_parser.get_module] cannot find %s in course.xml tree',
xpath_search)
log.error('tree = %s' % etree.tostring(tree, pretty_print=True))
return None
# log.debug('[courseware.content_parser.module_xml] found %s' % result_set)
return etree.tostring(result_set[0])
# ==== All Django-specific code below =============================================
def user_groups(user):
......@@ -278,6 +196,11 @@ def get_options(user):
'groups': user_groups(user)}
def process_includes(tree):
'''Replace <include> tags with the contents from the course directory'''
process_includes_dir(tree, settings.DATA_DIR)
def replace_custom_tags(tree):
'''Replace custom tags defined in our custom_tags dir'''
replace_custom_tags_dir(tree, settings.DATA_DIR+'/custom_tags')
......@@ -337,29 +260,3 @@ def sections_dir(coursename=None):
xp = multicourse_settings.get_course_xmlpath(coursename)
return settings.DATA_DIR + xp + '/sections/'
def section_file(user, section, coursename=None):
'''
Given a user and the name of a section, return that section.
This is done specific to each course.
Returns the xml tree for the section, or None if there's no such section.
'''
dirname = sections_dir(coursename)
return get_section(section, options, dirname)
def module_xml(user, module, id_tag, module_id, coursename=None):
''' Get XML for a module based on module and module_id. Assumes
module occurs once in courseware XML file or hidden section.
'''
tree = course_file(user, coursename)
sdirname = sections_dir(coursename)
options = get_options(user)
return get_module(tree, module, id_tag, module_id, sdirname, options)
......@@ -81,12 +81,12 @@ def grade_sheet(student,coursename=None):
course = dom.xpath('//course/@name')[0]
xmlChapters = dom.xpath('//course[@name=$course]/chapter', course=course)
responses=StudentModule.objects.filter(student=student)
responses = StudentModule.objects.filter(student=student)
response_by_id = {}
for response in responses:
response_by_id[response.module_id] = response
response_by_id[response.module_state_key] = response
totaled_scores = {}
chapters=[]
for c in xmlChapters:
......@@ -147,27 +147,39 @@ def grade_sheet(student,coursename=None):
'grade_summary' : grade_summary}
def get_score(user, problem, cache, coursename=None):
"""
Return the score for a user on a problem
user: a Student object
problem: the xml for the problem
cache: a dictionary mapping module_state_key tuples to instantiated StudentModules
module_state_key is either the problem_id, or a key used by the problem
to share state across instances
"""
## HACK: assumes max score is fixed per problem
id = problem.get('id')
module_type = problem.tag
module_class = xmodule.get_module_class(module_type)
module_id = problem.get('id')
module_state_key = problem.get(module_class.state_key, module_id)
correct = 0.0
# If the ID is not in the cache, add the item
if id not in cache:
module = StudentModule(module_type = 'problem', # TODO: Move into StudentModule.__init__?
module_id = id,
student = user,
state = None,
grade = 0,
max_grade = None,
done = 'i')
cache[id] = module
if module_state_key not in cache:
module = StudentModule(module_type='problem', # TODO: Move into StudentModule.__init__?
module_state_key=id,
student=user,
state=None,
grade=0,
max_grade=None,
done='i')
cache[module_id] = module
# Grab the # correct from cache
if id in cache:
response = cache[id]
if response.grade!=None:
correct=float(response.grade)
if response.grade != None:
correct = float(response.grade)
# Grab max grade from cache, or if it doesn't exist, compute and save to DB
if id in cache and response.max_grade is not None:
total = response.max_grade
......
......@@ -13,7 +13,6 @@ ASSUMPTIONS: modules have unique IDs, even across different module_types
"""
from django.db import models
from django.db.models.signals import post_save, post_delete
#from django.core.cache import cache
from django.contrib.auth.models import User
......@@ -21,72 +20,97 @@ from django.contrib.auth.models import User
#CACHE_TIMEOUT = 60 * 60 * 4 # Set the cache timeout to be four hours
class StudentModule(models.Model):
# For a homework problem, contains a JSON
# object consisting of state
MODULE_TYPES = (('problem','problem'),
('video','video'),
('html','html'),
MODULE_TYPES = (('problem', 'problem'),
('video', 'video'),
('html', 'html'),
)
## These three are the key for the object
module_type = models.CharField(max_length=32, choices=MODULE_TYPES, default='problem', db_index=True)
module_id = models.CharField(max_length=255, db_index=True) # Filename for homeworks, etc.
# Key used to share state. By default, this is the module_id,
# but for abtests and the like, this can be set to a shared value
# for many instances of the module.
# Filename for homeworks, etc.
module_state_key = models.CharField(max_length=255, db_index=True, db_column='module_id')
student = models.ForeignKey(User, db_index=True)
class Meta:
unique_together = (('student', 'module_id'),)
unique_together = (('student', 'module_state_key'),)
## Internal state of the object
state = models.TextField(null=True, blank=True)
## Grade, and are we done?
## Grade, and are we done?
grade = models.FloatField(null=True, blank=True, db_index=True)
max_grade = models.FloatField(null=True, blank=True)
DONE_TYPES = (('na','NOT_APPLICABLE'),
('f','FINISHED'),
('i','INCOMPLETE'),
DONE_TYPES = (('na', 'NOT_APPLICABLE'),
('f', 'FINISHED'),
('i', 'INCOMPLETE'),
)
done = models.CharField(max_length=8, choices=DONE_TYPES, default='na', db_index=True)
# DONE_TYPES = (('done','DONE'), # Finished
# ('incomplete','NOTDONE'), # Not finished
# ('na','NA')) # Not applicable (e.g. vertical)
# done = models.CharField(max_length=16, choices=DONE_TYPES)
created = models.DateTimeField(auto_now_add=True, db_index=True)
modified = models.DateTimeField(auto_now=True, db_index=True)
def __unicode__(self):
return self.module_type+'/'+self.student.username+"/"+self.module_id+'/'+str(self.state)[:20]
return '/'.join([self.module_type, self.student.username, self.module_state_key, str(self.state)[:20]])
# TODO (cpennington): Remove these once the LMS switches to using XModuleDescriptors
# @classmethod
# def get_with_caching(cls, student, module_id):
# k = cls.key_for(student, module_id)
# student_module = cache.get(k)
# if student_module is None:
# student_module = StudentModule.objects.filter(student=student,
# module_id=module_id)[0]
# # It's possible it really doesn't exist...
# if student_module is not None:
# cache.set(k, student_module, CACHE_TIMEOUT)
class StudentModuleCache(object):
"""
A cache of StudentModules for a specific student
"""
def __init__(self, user, descriptor, depth=None):
'''
Find any StudentModule objects that are needed by any child modules of the
supplied descriptor. Avoids making multiple queries to the database
'''
if user.is_authenticated():
module_ids = self._get_module_state_keys(descriptor, depth)
self.cache = list(StudentModule.objects.filter(student=user,
module_state_key__in=module_ids))
else:
self.cache = []
# return student_module
def _get_module_state_keys(self, descriptor, depth):
'''
Get a list of the state_keys needed for StudentModules
required for this chunk of module xml
'''
keys = [descriptor.url]
@classmethod
def key_for(cls, student, module_id):
return "StudentModule-student_id:{0};module_id:{1}".format(student.id, module_id)
shared_state_key = getattr(descriptor, 'shared_state_key', None)
if shared_state_key is not None:
keys.append(shared_state_key)
if depth is None or depth > 0:
new_depth = depth - 1 if depth is not None else depth
# def clear_cache_by_student_and_module_id(sender, instance, *args, **kwargs):
# k = sender.key_for(instance.student, instance.module_id)
# cache.delete(k)
for child in descriptor.get_children():
keys.extend(self._get_module_state_keys(child, new_depth))
# def update_cache_by_student_and_module_id(sender, instance, *args, **kwargs):
# k = sender.key_for(instance.student, instance.module_id)
# cache.set(k, instance, CACHE_TIMEOUT)
return keys
def lookup(self, module_type, module_state_key):
'''
Look for a student module with the given type and id in the cache.
#post_save.connect(update_cache_by_student_and_module_id, sender=StudentModule, weak=False)
#post_delete.connect(clear_cache_by_student_and_module_id, sender=StudentModule, weak=False)
cache -- list of student modules
#cache_model(StudentModule)
returns first found object, or None
'''
for o in self.cache:
if o.module_type == module_type and o.module_state_key == module_state_key:
return o
return None
def append(self, student_module):
self.cache.append(student_module)
......@@ -16,11 +16,10 @@ from django.views.decorators.cache import cache_control
from lxml import etree
from module_render import render_x_module, make_track_function, I4xSystem
from models import StudentModule
from module_render import render_x_module, toc_for_course, get_module, get_section
from models import StudentModuleCache
from student.models import UserProfile
from multicourse import multicourse_settings
import xmodule
import courseware.content_parser as content_parser
......@@ -87,23 +86,20 @@ def render_accordion(request, course, chapter, section):
If chapter and section are '' or None, renders a default accordion.
Returns (initialization_javascript, content)'''
if not course:
course = "6.002 Spring 2012"
toc = content_parser.toc_from_xml(
content_parser.course_file(request.user, course), chapter, section)
course_location = multicourse_settings.get_course_location(course)
toc = toc_for_course(request.user, request, course_location, chapter, section)
active_chapter = 1
for i in range(len(toc)):
if toc[i]['active']:
active_chapter = i
context=dict([('active_chapter', active_chapter),
('toc', toc),
('course_name', course),
('format_url_params', content_parser.format_url_params),
('csrf', csrf(request)['csrf_token'])] +
template_imports.items())
context = dict([('active_chapter', active_chapter),
('toc', toc),
('course_name', course),
('format_url_params', content_parser.format_url_params),
('csrf', csrf(request)['csrf_token'])] + template_imports.items())
return render_to_string('accordion.html', context)
......@@ -125,16 +121,10 @@ def render_section(request, section):
context = {
'csrf': csrf(request)['csrf_token'],
'accordion': render_accordion(request, '', '', '')
'accordion': render_accordion(request, get_course(request), '', '')
}
module_ids = dom.xpath("//@id")
if user.is_authenticated():
student_module_cache = list(StudentModule.objects.filter(student=user,
module_id__in=module_ids))
else:
student_module_cache = []
student_module_cache = StudentModuleCache(request.user, dom)
try:
module = render_x_module(user, request, dom, student_module_cache)
......@@ -147,13 +137,13 @@ def render_section(request, section):
return render_to_response('courseware.html', context)
context.update({
'init': module.get('init_js', ''),
'content': module['content'],
})
result = render_to_response('courseware.html', context)
return result
def get_course(request, course):
''' Figure out what the correct course is.
......@@ -161,7 +151,7 @@ def get_course(request, course):
TODO: Can this go away once multicourse becomes standard?
'''
if course==None:
if course == None:
if not settings.ENABLE_MULTICOURSE:
course = "6.002 Spring 2012"
elif 'coursename' in request.session:
......@@ -170,35 +160,6 @@ def get_course(request, course):
course = settings.COURSE_DEFAULT
return course
def get_module_xml(user, course, chapter, section):
''' Look up the module xml for the given course/chapter/section path.
Takes the user to look up the course file.
Returns None if there was a problem, or the lxml etree for the module.
'''
try:
# this is the course.xml etree
dom = content_parser.course_file(user, course)
except:
log.exception("Unable to parse courseware xml")
return None
# this is the module's parent's etree
path = "//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]"
dom_module = dom.xpath(path, course=course, chapter=chapter, section=section)
module_wrapper = dom_module[0] if len(dom_module) > 0 else None
if module_wrapper is None:
module = None
elif module_wrapper.get("src"):
module = content_parser.section_file(
user=user, section=module_wrapper.get("src"), coursename=course)
else:
# Copy the element out of the module's etree
module = etree.XML(etree.tostring(module_wrapper[0]))
return module
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
......@@ -228,55 +189,6 @@ def index(request, course=None, chapter=None, section=None,
'''
return s.replace('_', ' ') if s is not None else None
def get_submodule_ids(module_xml):
'''
Get a list with ids of the modules within this module.
'''
return module_xml.xpath("//@id")
def preload_student_modules(module_xml):
'''
Find any StudentModule objects for this user that match
one of the given module_ids. Used as a cache to avoid having
each rendered module hit the db separately.
Returns the list, or None on error.
'''
if request.user.is_authenticated():
module_ids = get_submodule_ids(module_xml)
return list(StudentModule.objects.filter(student=request.user,
module_id__in=module_ids))
else:
return []
def get_module_context():
'''
Look up the module object and render it. If all goes well, returns
{'init': module-init-js, 'content': module-rendered-content}
If there's an error, returns
{'content': module-error message}
'''
user = request.user
module_xml = get_module_xml(user, course, chapter, section)
if module_xml is None:
log.exception("couldn't get module_xml: course/chapter/section: '%s/%s/%s'",
course, chapter, section)
return {'content' : render_to_string("module-error.html", {})}
student_module_cache = preload_student_modules(module_xml)
try:
module_context = render_x_module(user, request, module_xml,
student_module_cache, position)
except:
log.exception("Unable to load module")
return {'content' : render_to_string("module-error.html", {})}
return {'init': module_context.get('init_js', ''),
'content': module_context['content']}
if not settings.COURSEWARE_ENABLED:
return redirect('/')
......@@ -300,11 +212,16 @@ def index(request, course=None, chapter=None, section=None,
look_for_module = chapter is not None and section is not None
if look_for_module:
context.update(get_module_context())
course_location = multicourse_settings.get_course_location(course)
section = get_section(course_location, chapter, section)
student_module_cache = StudentModuleCache(request.user, section)
module, _, _, _ = get_module(request.user, request, section.url, student_module_cache)
context['content'] = module.get_html()
result = render_to_response('courseware.html', context)
return result
def jump_to(request, probname=None):
'''
Jump to viewing a specific problem. The problem is specified by a
......
......@@ -31,11 +31,13 @@ if hasattr(settings,'COURSE_SETTINGS'): # in the future, this could be repla
elif hasattr(settings,'COURSE_NAME'): # backward compatibility
COURSE_SETTINGS = {settings.COURSE_NAME: {'number': settings.COURSE_NUMBER,
'title': settings.COURSE_TITLE,
'location': settings.COURSE_LOCATION,
},
}
else: # default to 6.002_Spring_2012
COURSE_SETTINGS = {'6.002_Spring_2012': {'number': '6.002x',
'title': 'Circuits and Electronics',
'location': 'i4x://edx/6002xs12/course/6.002 Spring 2012',
},
}
......@@ -51,31 +53,47 @@ def get_coursename_from_request(request):
def get_course_settings(coursename):
if not coursename:
if hasattr(settings,'COURSE_DEFAULT'):
if hasattr(settings, 'COURSE_DEFAULT'):
coursename = settings.COURSE_DEFAULT
else:
coursename = '6.002_Spring_2012'
if coursename in COURSE_SETTINGS: return COURSE_SETTINGS[coursename]
coursename = coursename.replace(' ','_')
if coursename in COURSE_SETTINGS: return COURSE_SETTINGS[coursename]
if coursename in COURSE_SETTINGS:
return COURSE_SETTINGS[coursename]
coursename = coursename.replace(' ', '_')
if coursename in COURSE_SETTINGS:
return COURSE_SETTINGS[coursename]
return None
def is_valid_course(coursename):
return get_course_settings(coursename) != None
def get_course_property(coursename,property):
def get_course_property(coursename, property):
cs = get_course_settings(coursename)
if not cs: return '' # raise exception instead?
if property in cs: return cs[property]
return '' # default
# raise exception instead?
if not cs:
return ''
if property in cs:
return cs[property]
# default
return ''
def get_course_xmlpath(coursename):
return get_course_property(coursename,'xmlpath')
return get_course_property(coursename, 'xmlpath')
def get_course_title(coursename):
return get_course_property(coursename,'title')
return get_course_property(coursename, 'title')
def get_course_number(coursename):
return get_course_property(coursename,'number')
return get_course_property(coursename, 'number')
def get_course_location(coursename):
return get_course_property(coursename, 'location')
......@@ -132,10 +132,25 @@ COURSE_DEFAULT = '6.002_Spring_2012'
COURSE_SETTINGS = {'6.002_Spring_2012': {'number' : '6.002x',
'title' : 'Circuits and Electronics',
'xmlpath': '6002x/',
'location': 'i4x://edx/6002xs12/course/6_002_Spring_2012',
}
}
############################### XModule Store ##################################
KEYSTORE = {
'default': {
'ENGINE': 'keystore.xml.XMLModuleStore',
'OPTIONS': {
'org': 'edx',
'course': '6002xs12',
'data_dir': DATA_DIR,
'default_class': 'xmodule.hidden_module.HiddenDescriptor',
}
}
}
############################### DJANGO BUILT-INS ###############################
# Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here
DEBUG = False
......
......@@ -11,7 +11,7 @@ from .common import *
from .logsettings import get_logger_config
DEBUG = True
TEMPLATE_DEBUG = True
TEMPLATE_DEBUG = False
LOGGING = get_logger_config(ENV_ROOT / "log",
logging_env="dev",
......
......@@ -184,29 +184,29 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
filestore = OSFS(settings.DATA_DIR + xp),
#role = 'staff' if request.user.is_staff else 'student', # TODO: generalize this
)
instance=xmodule.get_module_class(module)(system,
xml,
instance = xmodule.get_module_class(module)(system,
xml,
id,
state=None)
log.info('ajax_url = ' + instance.ajax_url)
# create empty student state for this problem, if not previously existing
s = StudentModule.objects.filter(student=request.user,
module_id=id)
s = StudentModule.objects.filter(student=request.user,
module_state_key=id)
if len(s) == 0 or s is None:
smod=StudentModule(student=request.user,
module_type = 'problem',
module_id=id,
state=instance.get_state())
smod = StudentModule(student=request.user,
module_type='problem',
module_state_key=id,
state=instance.get_instance_state())
smod.save()
lcp = instance.lcp
pxml = lcp.tree
pxmls = etree.tostring(pxml,pretty_print=True)
pxmls = etree.tostring(pxml, pretty_print=True)
return instance, pxmls
instance, pxmls = get_lcp(coursename,id)
instance, pxmls = get_lcp(coursename, id)
# if there was a POST, then process it
msg = ''
......@@ -246,8 +246,6 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
# get the rendered problem HTML
phtml = instance.get_html()
# phtml = instance.get_problem_html()
# init_js = instance.get_init_js()
# destory_js = instance.get_destroy_js()
context = {'id':id,
'msg' : msg,
......
class @Sequence
constructor: (@id, @elements, @tag, position) ->
@element = $("#sequence_#{@id}")
constructor: (@id, @element_id, @elements, @tag, position) ->
@element = $("#sequence_#{@element_id}")
@buildNavigation()
@initProgress()
@bind()
......
<div id="sequence_${id}" class="sequence">
<div id="sequence_${element_id}" class="sequence">
<nav aria-label="Section Navigation" class="sequence-nav">
<ol id="sequence-list">
</ol>
......@@ -22,7 +22,7 @@
<%block name="js_extra">
<script type="text/javascript">
$(function(){
new Sequence('${id}', ${items}, '${tag}', ${position});
new Sequence('${item_id}', '${element_id}', ${items}, '${tag}', ${position});
});
</script>
</%block>
<ol class="vert-mod">
% for t in items:
<li id="vert-${items.index(t)}">
${t[1]['content']}
% for idx, item in enumerate(items):
<li id="vert-${idx}">
${item}
</li>
% endfor
</ol>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment