diff --git a/common/djangoapps/mitxmako/shortcuts.py b/common/djangoapps/mitxmako/shortcuts.py
index 181d3befd5..8f39efdc02 100644
--- a/common/djangoapps/mitxmako/shortcuts.py
+++ b/common/djangoapps/mitxmako/shortcuts.py
@@ -14,7 +14,7 @@
import logging
-log = logging.getLogger("mitx." + __name__)
+log = logging.getLogger(__name__)
from django.template import Context
from django.http import HttpResponse
diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py
index 5a13582a2d..30098c94fc 100644
--- a/common/djangoapps/xmodule_modifiers.py
+++ b/common/djangoapps/xmodule_modifiers.py
@@ -131,6 +131,7 @@ def add_histogram(get_html, module, user):
is_released = "Yes!" if (now > mstart) else "Not yet"
staff_context = {'fields': [(field.name, getattr(module, field.name)) for field in module.fields],
+ 'lms_fields': [(field.name, getattr(module.lms, field.name)) for field in module.lms.fields],
'location': module.location,
'xqa_key': module.lms.xqa_key,
'source_file' : source_file,
diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py
index 85670063c5..d5ec6a1439 100644
--- a/common/lib/capa/capa/capa_problem.py
+++ b/common/lib/capa/capa/capa_problem.py
@@ -72,7 +72,7 @@ global_context = {'random': random,
# These should be removed from HTML output, including all subelements
html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup"]
-log = logging.getLogger('mitx.' + __name__)
+log = logging.getLogger(__name__)
#-----------------------------------------------------------------------------
# main class for this module
diff --git a/common/lib/capa/capa/customrender.py b/common/lib/capa/capa/customrender.py
index ef1044e8b1..0268d7b266 100644
--- a/common/lib/capa/capa/customrender.py
+++ b/common/lib/capa/capa/customrender.py
@@ -17,7 +17,7 @@ from lxml import etree
import xml.sax.saxutils as saxutils
from registry import TagRegistry
-log = logging.getLogger('mitx.' + __name__)
+log = logging.getLogger(__name__)
registry = TagRegistry()
diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py
index 0b2250f98d..8a15434d1d 100644
--- a/common/lib/capa/capa/inputtypes.py
+++ b/common/lib/capa/capa/inputtypes.py
@@ -44,7 +44,7 @@ import sys
from registry import TagRegistry
-log = logging.getLogger('mitx.' + __name__)
+log = logging.getLogger(__name__)
#########################################################################
diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py
index 418ee9d8ae..2074e8f648 100644
--- a/common/lib/capa/capa/responsetypes.py
+++ b/common/lib/capa/capa/responsetypes.py
@@ -33,7 +33,7 @@ from lxml import etree
from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
import xqueue_interface
-log = logging.getLogger('mitx.' + __name__)
+log = logging.getLogger(__name__)
#-----------------------------------------------------------------------------
diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py
index 0214488cce..a7d31db7e1 100644
--- a/common/lib/capa/capa/xqueue_interface.py
+++ b/common/lib/capa/capa/xqueue_interface.py
@@ -7,7 +7,7 @@ import logging
import requests
-log = logging.getLogger('mitx.' + __name__)
+log = logging.getLogger(__name__)
dateformat = '%Y%m%d%H%M%S'
def make_hashkey(seed):
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index b8a85595dc..36da929926 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -551,8 +551,7 @@ class CapaModule(XModule):
msg = "Error checking problem: " + str(err)
msg += '\nTraceback:\n' + traceback.format_exc()
return {'success': msg}
- log.exception("Error in capa_module problem checking")
- raise Exception("error in capa_module")
+ raise
self.attempts = self.attempts + 1
self.lcp.done = True
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index b2a1917912..b8d4032a18 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -131,9 +131,9 @@ class CourseDescriptor(SequenceDescriptor):
if self.start is None:
msg = "Course loaded without a valid start date. id = %s" % self.id
# hack it -- start in 1970
- self.lms.start = time.gmtime(0)
+ self.start = time.gmtime(0)
log.critical(msg)
- system.error_tracker(msg)
+ self.system.error_tracker(msg)
# NOTE: relies on the modulestore to call set_grading_policy() right after
# init. (Modulestore is in charge of figuring out where to load the policy from)
diff --git a/common/lib/xmodule/xmodule/error_module.py b/common/lib/xmodule/xmodule/error_module.py
index 0f95bcd256..67b52d383c 100644
--- a/common/lib/xmodule/xmodule/error_module.py
+++ b/common/lib/xmodule/xmodule/error_module.py
@@ -8,6 +8,7 @@ from xmodule.x_module import XModule
from xmodule.editing_module import JSONEditingDescriptor
from xmodule.errortracker import exc_info_to_str
from xmodule.modulestore import Location
+from .model import String, Scope
log = logging.getLogger(__name__)
@@ -52,6 +53,10 @@ class ErrorDescriptor(JSONEditingDescriptor):
"""
module_class = ErrorModule
+ contents = String(scope=Scope.content)
+ error_msg = String(scope=Scope.content)
+ display_name = String(scope=Scope.settings)
+
@classmethod
def _construct(self, system, contents, error_msg, location):
@@ -66,15 +71,12 @@ class ErrorDescriptor(JSONEditingDescriptor):
name=hashlib.sha1(contents).hexdigest()
)
- definition = {
- 'data': {
- 'error_msg': str(error_msg),
- 'contents': contents,
- }
- }
-
# real metadata stays in the content, but add a display name
- model_data = {'display_name': 'Error: ' + location.name}
+ model_data = {
+ 'error_msg': str(error_msg),
+ 'contents': contents,
+ 'display_name': 'Error: ' + location.name
+ }
return ErrorDescriptor(
system,
location,
@@ -84,7 +86,7 @@ class ErrorDescriptor(JSONEditingDescriptor):
def get_context(self):
return {
'module': self,
- 'data': self.definition['data']['contents'],
+ 'data': self.contents,
}
@classmethod
@@ -100,10 +102,7 @@ class ErrorDescriptor(JSONEditingDescriptor):
def from_descriptor(cls, descriptor, error_msg='Error not available'):
return cls._construct(
descriptor.system,
- json.dumps({
- 'definition': descriptor.definition,
- 'metadata': descriptor.metadata,
- }, indent=4),
+ json.dumps(descriptor._model_data, indent=4),
error_msg,
location=descriptor.location,
)
@@ -147,14 +146,14 @@ class ErrorDescriptor(JSONEditingDescriptor):
files, etc. That would just get re-wrapped on import.
'''
try:
- xml = etree.fromstring(self.definition['data']['contents'])
+ xml = etree.fromstring(self.contents)
return etree.tostring(xml)
except etree.XMLSyntaxError:
# still not valid.
root = etree.Element('error')
- root.text = self.definition['data']['contents']
+ root.text = self.contents
err_node = etree.SubElement(root, 'error_msg')
- err_node.text = self.definition['data']['error_msg']
+ err_node.text = self.error_msg
return etree.tostring(root)
diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py
new file mode 100644
index 0000000000..715aea0c7c
--- /dev/null
+++ b/common/lib/xmodule/xmodule/fields.py
@@ -0,0 +1,30 @@
+import time
+import logging
+
+from .model import ModelType
+
+log = logging.getLogger(__name__)
+
+
+class Date(ModelType):
+ time_format = "%Y-%m-%dT%H:%M"
+
+ def from_json(self, value):
+ """
+ Parse an optional metadata key containing a time: if present, complain
+ if it doesn't parse.
+ Return None if not present or invalid.
+ """
+ try:
+ return time.strptime(value, self.time_format)
+ except ValueError as e:
+ msg = "Field {0} has bad value '{1}': '{2}'".format(
+ self._name, value, e)
+ log.warning(msg)
+ return None
+
+ def to_json(self, value):
+ """
+ Convert a time struct to a string
+ """
+ return time.strftime(self.time_format, value)
diff --git a/common/lib/xmodule/xmodule/model.py b/common/lib/xmodule/xmodule/model.py
index 89fc9d56d1..93ed12f69d 100644
--- a/common/lib/xmodule/xmodule/model.py
+++ b/common/lib/xmodule/xmodule/model.py
@@ -43,14 +43,14 @@ class ModelType(object):
if instance is None:
return self
- if self.name not in instance._model_data:
+ try:
+ return self.from_json(instance._model_data[self.name])
+ except KeyError:
if self.default is None and self.computed_default is not None:
return self.computed_default(instance)
return self.default
- return self.from_json(instance._model_data[self.name])
-
def __set__(self, instance, value):
instance._model_data[self.name] = self.to_json(value)
@@ -166,18 +166,18 @@ class Namespace(Plugin):
super(Namespace, self).__setattr__(name, value)
return
- container_class_attr = getattr(type(container), name, None)
+ namespace_attr = getattr(type(self), name, None)
- if container_class_attr is None or not isinstance(container_class_attr, ModelType):
+ if namespace_attr is None or not isinstance(namespace_attr, ModelType):
return super(Namespace, self).__setattr__(name, value)
- return container_class_attr.__set__(container)
+ return namespace_attr.__set__(container, value)
def __delattr__(self, name):
container = super(Namespace, self).__getattribute__('_container')
- container_class_attr = getattr(type(container), name, None)
+ namespace_attr = getattr(type(self), name, None)
- if container_class_attr is None or not isinstance(container_class_attr, ModelType):
+ if namespace_attr is None or not isinstance(namespace_attr, ModelType):
return super(Namespace, self).__detattr__(name)
- return container_class_attr.__delete__(container)
+ return namespace_attr.__delete__(container)
diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py
index ce6d9df093..72c9093bbf 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml.py
@@ -28,7 +28,7 @@ edx_xml_parser = etree.XMLParser(dtd_validation=False, load_dtd=False,
etree.set_default_parser(edx_xml_parser)
-log = logging.getLogger('mitx.' + __name__)
+log = logging.getLogger(__name__)
# VS[compat]
@@ -160,7 +160,6 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
etree.tostring(xml_data), self, self.org,
self.course, xmlstore.default_class)
except Exception as err:
- print err, self.load_error_modules
if not self.load_error_modules:
raise
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index 35375d7c51..f90291aaa2 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -8,6 +8,7 @@ from .xml import XMLModuleStore
from .exceptions import DuplicateItemError
from xmodule.modulestore import Location
from xmodule.contentstore.content import StaticContent, XASSET_SRCREF_PREFIX
+from xmodule.model import Scope
log = logging.getLogger(__name__)
@@ -123,7 +124,7 @@ def import_from_xml(store, data_dir, course_dirs=None,
# Quick scan to get course Location as well as the course_data_path
for module in module_store.modules[course_id].itervalues():
if module.category == 'course':
- course_data_path = path(data_dir) / module.metadata['data_dir']
+ course_data_path = path(data_dir) / module.data_dir
course_location = module.location
if static_content_store is not None:
@@ -159,18 +160,17 @@ def import_from_xml(store, data_dir, course_dirs=None,
module.definition['children'] = new_locs
-
if module.category == 'course':
# HACK: for now we don't support progress tabs. There's a special metadata configuration setting for this.
- module.metadata['hide_progress_tab'] = True
+ module.hide_progress_tab = True
- # cdodge: more hacks (what else). Seems like we have a problem when importing a course (like 6.002) which
- # does not have any tabs defined in the policy file. The import goes fine and then displays fine in LMS,
- # but if someone tries to add a new tab in the CMS, then the LMS barfs because it expects that -
+ # cdodge: more hacks (what else). Seems like we have a problem when importing a course (like 6.002) which
+ # does not have any tabs defined in the policy file. The import goes fine and then displays fine in LMS,
+ # but if someone tries to add a new tab in the CMS, then the LMS barfs because it expects that -
# if there is *any* tabs - then there at least needs to be some predefined ones
if module.tabs is None or len(module.tabs) == 0:
- module.tabs = [{"type": "courseware"},
- {"type": "course_info", "name": "Course Info"},
+ module.tabs = [{"type": "courseware"},
+ {"type": "course_info", "name": "Course Info"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"}] # note, add 'progress' when we can support it on Edge
@@ -180,39 +180,43 @@ def import_from_xml(store, data_dir, course_dirs=None,
course_items.append(module)
- if 'data' in module.definition:
- module_data = module.definition['data']
-
+ if hasattr(module, 'data'):
# cdodge: now go through any link references to '/static/' and make sure we've imported
# it as a StaticContent asset
- try:
+ try:
remap_dict = {}
# use the rewrite_links as a utility means to enumerate through all links
# in the module data. We use that to load that reference into our asset store
# IMPORTANT: There appears to be a bug in lxml.rewrite_link which makes us not be able to
# do the rewrites natively in that code.
- # For example, what I'm seeing is
->
+ # For example, what I'm seeing is
->
# Note the dropped element closing tag. This causes the LMS to fail when rendering modules - that's
# no good, so we have to do this kludge
- if isinstance(module_data, str) or isinstance(module_data, unicode): # some module 'data' fields are non strings which blows up the link traversal code
- lxml_rewrite_links(module_data, lambda link: verify_content_links(module, course_data_path,
- static_content_store, link, remap_dict))
+ if isinstance(module.data, str) or isinstance(module.data, unicode): # some module 'data' fields are non strings which blows up the link traversal code
+ lxml_rewrite_links(module.data, lambda link: verify_content_links(module, course_data_path,
+ static_content_store, link, remap_dict))
for key in remap_dict.keys():
- module_data = module_data.replace(key, remap_dict[key])
+ module.data = module.data.replace(key, remap_dict[key])
- except Exception, e:
+ except Exception:
logging.exception("failed to rewrite links on {0}. Continuing...".format(module.location))
- store.update_item(module.location, module_data)
+ store.update_item(module.location, module.data)
- if 'children' in module.definition:
- store.update_children(module.location, module.definition['children'])
+ if module.has_children:
+ store.update_children(module.location, module.children)
- # NOTE: It's important to use own_metadata here to avoid writing
- # inherited metadata everywhere.
- store.update_metadata(module.location, dict(module.own_metadata))
+ metadata = {}
+ for field in module.fields + module.lms.fields:
+ # Only save metadata that wasn't inherited
+ if (field.scope == Scope.settings and
+ field.name in module._inherited_metadata and
+ field.name in module._model_data):
+
+ metadata[field.name] = module._model_data[field.name]
+ store.update_metadata(module.location, metadata)
return module_store, course_items
diff --git a/common/lib/xmodule/xmodule/runtime.py b/common/lib/xmodule/xmodule/runtime.py
index 8be4bfe346..7030f04f9f 100644
--- a/common/lib/xmodule/xmodule/runtime.py
+++ b/common/lib/xmodule/xmodule/runtime.py
@@ -33,19 +33,33 @@ class DbModel(MutableMapping):
def __repr__(self):
return "<{0.__class__.__name__} {0._module_cls!r}>".format(self)
- def __str__(self):
- return str(dict(self.iteritems()))
-
def _getfield(self, name):
- if (not hasattr(self._module_cls, name) or
- not isinstance(getattr(self._module_cls, name), ModelType)):
+ # First, get the field from the class, if defined
+ module_field = getattr(self._module_cls, name, None)
+ if module_field is not None and isinstance(module_field, ModelType):
+ return module_field
- raise KeyError(name)
+ # If the class doesn't have the field, and it also
+ # doesn't have any namespaces, then the the name isn't a field
+ # so KeyError
+ if not hasattr(self._module_cls, 'namespaces'):
+ return KeyError(name)
- return getattr(self._module_cls, name)
+ # Resolve the field name in the first namespace where it's
+ # available
+ for namespace_name in self._module_cls.namespaces:
+ namespace = getattr(self._module_cls, namespace_name)
+ namespace_field = getattr(type(namespace), name, None)
+ if namespace_field is not None and isinstance(module_field, ModelType):
+ return namespace_field
+
+ # Not in the class or in any of the namespaces, so name
+ # really doesn't name a field
+ raise KeyError(name)
def _key(self, name):
field = self._getfield(name)
+ print name, field
module = field.scope.module
if module == ModuleScope.ALL:
@@ -88,5 +102,5 @@ class DbModel(MutableMapping):
def keys(self):
fields = [field.name for field in self._module_cls.fields]
for namespace_name in self._module_cls.namespaces:
- fields.extend(field.name for field in getattr(self._module_cls, namespace_name))
+ fields.extend(field.name for field in getattr(self._module_cls, namespace_name).fields)
return fields
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 2edf5467b2..2f4674cf7c 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -25,6 +25,7 @@ from .stringify import stringify_children
from .x_module import XModule
from .xml_module import XmlDescriptor
from xmodule.modulestore import Location
+from .model import List, String, Scope, Int
log = logging.getLogger("mitx.courseware")
@@ -61,67 +62,21 @@ class SelfAssessmentModule(XModule):
js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]}
js_module_name = "SelfAssessment"
- def __init__(self, system, location, definition, descriptor,
- instance_state=None, shared_state=None, **kwargs):
- XModule.__init__(self, system, location, definition, descriptor,
- instance_state, shared_state, **kwargs)
+ student_answers = List(scope=Scope.student_state, default=[])
+ scores = List(scope=Scope.student_state, default=[])
+ hints = List(scope=Scope.student_state, default=[])
+ state = String(scope=Scope.student_state, default=INITIAL)
- """
- Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt,
- and two optional attributes:
- attempts, which should be an integer that defaults to 1.
- If it's > 1, the student will be able to re-submit after they see
- the rubric.
- max_score, which should be an integer that defaults to 1.
- It defines the maximum number of points a student can get. Assumed to be integer scale
- from 0 to max_score, with an interval of 1.
+ # Used for progress / grading. Currently get credit just for
+ # completion (doesn't matter if you self-assessed correct/incorrect).
+ max_score = Int(scope=Scope.settings, default=MAX_SCORE)
- Note: all the submissions are stored.
-
- Sample file:
-
-
${field | h}
-%endfor
+${field | h}
+ ${field | h}
+