Merge remote-tracking branch 'origin/master' into dormsbee/multicourse

Conflicts:
	common/lib/xmodule/xmodule/capa_module.py
	common/lib/xmodule/xmodule/modulestore/xml.py
	lms/djangoapps/courseware/views.py
	lms/templates/index.html
	lms/templates/info.html
	lms/templates/main.html
	lms/templates/navigation.html
	requirements.txt
This commit is contained in:
Calen Pennington
2012-07-10 13:39:19 -04:00
164 changed files with 3612 additions and 713 deletions

View File

@@ -8,6 +8,10 @@ setup(
package_data={
'xmodule': ['js/module/*']
},
requires=[
'capa',
'mitxmako'
],
# See http://guide.python-distribute.org/creation.html#entry-points
# for a description of entry_points

View File

@@ -180,7 +180,12 @@ class CapaModule(XModule):
score = d['score']
total = d['total']
if total > 0:
return Progress(score, total)
try:
return Progress(score, total)
except Exception as err:
if self.system.DEBUG:
return None
raise
return None
def get_html(self):
@@ -194,7 +199,18 @@ class CapaModule(XModule):
'''Return html for the problem. Adds check, reset, save buttons
as necessary based on the problem config and state.'''
html = self.lcp.get_html()
try:
html = self.lcp.get_html()
except Exception, err:
if self.system.DEBUG:
log.exception(err)
msg = '[courseware.capa.capa_module] <font size="+1" color="red">Failed to generate HTML for problem %s</font>' % (self.location.url())
msg += '<p>Error:</p><p><pre>%s</pre></p>' % str(err).replace('<','&lt;')
msg += '<p><pre>%s</pre></p>' % traceback.format_exc().replace('<','&lt;')
html = msg
else:
raise
content = {'name': self.metadata['display_name'],
'html': html,
'weight': self.weight,
@@ -395,14 +411,18 @@ class CapaModule(XModule):
correct_map = self.lcp.grade_answers(answers)
except StudentInputError as inst:
# TODO (vshnayder): why is this line here?
self.lcp = LoncapaProblem(self.definition['data'],
id=lcp_id, state=old_state, system=self.system)
#self.lcp = LoncapaProblem(self.definition['data'],
# id=lcp_id, state=old_state, system=self.system)
traceback.print_exc()
return {'success': inst.message}
except:
except Exception, err:
# TODO: why is this line here?
self.lcp = LoncapaProblem(self.definition['data'],
id=lcp_id, state=old_state, system=self.system)
#self.lcp = LoncapaProblem(self.definition['data'],
# id=lcp_id, state=old_state, system=self.system)
if self.system.DEBUG:
msg = "Error checking problem: " + str(err)
msg += '\nTraceback:\n' + traceback.format_exc()
return {'success':msg}
traceback.print_exc()
raise Exception("error in capa_module")

View File

@@ -1,4 +1,5 @@
import logging
from lxml import etree
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
@@ -26,3 +27,8 @@ class HtmlDescriptor(RawDescriptor):
js = {'coffee': [resource_string(__name__, 'js/module/html.coffee')]}
js_module = 'HTML'
@classmethod
def file_to_xml(cls, file_object):
parser = etree.HTMLParser()
return etree.parse(file_object, parser).getroot()

View File

@@ -1,9 +1,9 @@
class @HTML
constructor: (@id) ->
@edit_box = $("##{@id} .edit-box")
@preview = $("##{@id} .preview")
constructor: (@element) ->
@edit_box = $(".edit-box", @element)
@preview = $(".preview", @element)
@edit_box.on('input', =>
@preview.empty().append(@edit_box.val())
)
save: -> {text: @edit_box.val()}
save: -> @edit_box.val()

View File

@@ -1,7 +1,7 @@
class @Raw
constructor: (@id) ->
@edit_box = $("##{@id} .edit-box")
@preview = $("##{@id} .preview")
constructor: (@element) ->
@edit_box = $(".edit-box", @element)
@preview = $(".preview", @element)
@edit_box.on('input', =>
@preview.empty().text(@edit_box.val())
)

View File

@@ -6,6 +6,9 @@ that are stored in a database an accessible using their Location as an identifie
import re
from collections import namedtuple
from .exceptions import InvalidLocationError
import logging
log = logging.getLogger('mitx.' + 'modulestore')
URL_RE = re.compile("""
(?P<tag>[^:]+)://
@@ -74,11 +77,13 @@ class Location(_LocationBase):
def check_list(list_):
for val in list_:
if val is not None and INVALID_CHARS.search(val) is not None:
log.debug('invalid characters val="%s", list_="%s"' % (val,list_))
raise InvalidLocationError(location)
if isinstance(location, basestring):
match = URL_RE.match(location)
if match is None:
log.debug('location is instance of %s but no URL match' % basestring)
raise InvalidLocationError(location)
else:
groups = match.groupdict()
@@ -86,6 +91,7 @@ class Location(_LocationBase):
return _LocationBase.__new__(_cls, **groups)
elif isinstance(location, (list, tuple)):
if len(location) not in (5, 6):
log.debug('location has wrong length')
raise InvalidLocationError(location)
if len(location) == 5:
@@ -153,6 +159,18 @@ class ModuleStore(object):
location is found
"""
raise NotImplementedError
def get_items(self, location, default_class=None):
"""
Returns a list of XModuleDescriptor instances for the items
that match location. Any element of location that is None is treated
as a wildcard that matches any value
location: Something that can be passed to Location
default_class: An XModuleDescriptor subclass to use if no plugin matching the
location is found
"""
raise NotImplementedError
# TODO (cpennington): Replace with clone_item
def create_item(self, location, editor):

View File

@@ -1,4 +1,5 @@
import pymongo
from bson.objectid import ObjectId
from importlib import import_module
from xmodule.x_module import XModuleDescriptor
from xmodule.mako_module import MakoDescriptorSystem
@@ -8,6 +9,19 @@ from . import ModuleStore, Location
from .exceptions import ItemNotFoundError, InsufficientSpecificationError
# TODO (cpennington): This code currently operates under the assumption that
# there is only one revision for each item. Once we start versioning inside the CMS,
# that assumption will have to change
def location_to_query(loc):
query = {}
for key, val in Location(loc).dict().iteritems():
if val is not None:
query['_id.{key}'.format(key=key)] = val
return query
class MongoModuleStore(ModuleStore):
"""
A Mongodb backed ModuleStore
@@ -17,7 +31,6 @@ class MongoModuleStore(ModuleStore):
host=host,
port=port
)[db][collection]
self.collection.ensure_index('location')
# Force mongo to report errors, at the expense of performance
self.collection.safe = True
@@ -26,6 +39,18 @@ class MongoModuleStore(ModuleStore):
class_ = getattr(import_module(module_path), class_name)
self.default_class = class_
# TODO (cpennington): Pass a proper resources_fs to the system
self.system = MakoDescriptorSystem(
load_item=self.get_item,
resources_fs=None,
render_template=render_to_string
)
def _load_item(self, item):
item['location'] = item['_id']
del item['_id']
return XModuleDescriptor.load_from_json(item, self.system, self.default_class)
def get_item(self, location):
"""
Returns an XModuleDescriptor instance for the item at location.
@@ -39,24 +64,26 @@ class MongoModuleStore(ModuleStore):
location: Something that can be passed to Location
"""
query = {}
for key, val in Location(location).dict().iteritems():
if key != 'revision' and val is None:
raise InsufficientSpecificationError(location)
if val is not None:
query['location.{key}'.format(key=key)] = val
item = self.collection.find_one(
query,
location_to_query(location),
sort=[('revision', pymongo.ASCENDING)],
)
if item is None:
raise ItemNotFoundError(location)
return self._load_item(item)
# TODO (cpennington): Pass a proper resources_fs to the system
return XModuleDescriptor.load_from_json(
item, MakoDescriptorSystem(load_item=self.get_item, resources_fs=None, render_template=render_to_string), self.default_class)
def get_items(self, location, default_class=None):
print location_to_query(location)
items = self.collection.find(
location_to_query(location),
sort=[('revision', pymongo.ASCENDING)],
)
return [self._load_item(item) for item in items]
def create_item(self, location):
"""
@@ -65,7 +92,7 @@ class MongoModuleStore(ModuleStore):
location: Something that can be passed to Location
"""
self.collection.insert({
'location': Location(location).dict(),
'_id': Location(location).dict(),
})
def update_item(self, location, data):
@@ -80,8 +107,9 @@ class MongoModuleStore(ModuleStore):
# See http://www.mongodb.org/display/DOCS/Updating for
# atomic update syntax
self.collection.update(
{'location': Location(location).dict()},
{'$set': {'definition.data': data}}
{'_id': Location(location).dict()},
{'$set': {'definition.data': data}},
)
def update_children(self, location, children):
@@ -96,7 +124,7 @@ class MongoModuleStore(ModuleStore):
# See http://www.mongodb.org/display/DOCS/Updating for
# atomic update syntax
self.collection.update(
{'location': Location(location).dict()},
{'_id': Location(location).dict()},
{'$set': {'definition.children': children}}
)
@@ -112,6 +140,6 @@ class MongoModuleStore(ModuleStore):
# See http://www.mongodb.org/display/DOCS/Updating for
# atomic update syntax
self.collection.update(
{'location': Location(location).dict()},
{'_id': Location(location).dict()},
{'$set': {'metadata': metadata}}
)

View File

@@ -13,7 +13,7 @@ from .exceptions import ItemNotFoundError
etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False,
remove_comments=True, remove_blank_text=True))
log = logging.getLogger(__name__)
log = logging.getLogger('mitx.' + __name__)
class XMLModuleStore(ModuleStore):
@@ -38,9 +38,13 @@ class XMLModuleStore(ModuleStore):
self.default_class = None
else:
module_path, _, class_name = default_class.rpartition('.')
log.debug('module_path = %s' % module_path)
class_ = getattr(import_module(module_path), class_name)
self.default_class = class_
log.debug('XMLModuleStore: eager=%s, data_dir = %s' % (eager,self.data_dir))
log.debug('default_class = %s' % self.default_class)
for course_dir in os.listdir(self.data_dir):
if not os.path.exists(self.data_dir + "/" + course_dir + "/course.xml"):
continue
@@ -97,9 +101,11 @@ class XMLModuleStore(ModuleStore):
slug = '{slug}_{count}'.format(slug=slug, count=self.unnamed_modules)
self.used_slugs.add(slug)
# log.debug('-> slug=%s' % slug)
xml_data.set('slug', slug)
module = XModuleDescriptor.load_from_xml(etree.tostring(xml_data), self, org, course, modulestore.default_class)
log.debug('==> importing module location %s' % repr(module.location))
modulestore.modules[module.location] = module
if modulestore.eager:
@@ -117,6 +123,7 @@ class XMLModuleStore(ModuleStore):
course_descriptor = ImportSystem(self).process_xml(etree.tostring(course_data))
course_descriptor.metadata['data_dir'] = course_dir
log.debug('========> Done with course import')
return course_descriptor
def get_item(self, location):

View File

@@ -350,6 +350,8 @@ class XModuleDescriptor(Plugin):
etree.fromstring(xml_data).tag,
default_class
)
# leave next line in code, commented out - useful for low-level debugging
# log.debug('[XModuleDescriptor.load_from_xml] tag=%s, class_=%s' % (etree.fromstring(xml_data).tag,class_))
return class_.from_xml(xml_data, system, org, course)
@classmethod

View File

@@ -90,6 +90,16 @@ class XmlDescriptor(XModuleDescriptor):
if xml_object.get(attr) is not None:
del xml_object.attrib[attr]
@classmethod
def file_to_xml(cls, file_object):
"""
Used when this module wants to parse a file object to xml
that will be converted to the definition.
Returns an lxml Element
"""
return etree.parse(file_object).getroot()
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None):
"""
@@ -125,9 +135,10 @@ class XmlDescriptor(XModuleDescriptor):
definition_xml = copy.deepcopy(xml_object)
else:
filepath = cls._format_filepath(xml_object.tag, filename)
log.debug('filepath=%s, resources_fs=%s' % (filepath,system.resources_fs))
with system.resources_fs.open(filepath) as file:
try:
definition_xml = etree.parse(file).getroot()
definition_xml = cls.file_to_xml(file)
except:
log.exception("Failed to parse xml in file %s" % filepath)
raise
@@ -147,8 +158,8 @@ class XmlDescriptor(XModuleDescriptor):
)
@classmethod
def _format_filepath(cls, type, name):
return '{type}/{name}.{ext}'.format(type=type, name=name, ext=cls.filename_extension)
def _format_filepath(cls, category, name):
return u'{category}/{name}.{ext}'.format(category=category, name=name, ext=cls.filename_extension)
def export_to_xml(self, resource_fs):
"""