Commit 01411ae6 by Calen Pennington

WIP: Trying to get tests working

parent 7e224f58
......@@ -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
......
......@@ -131,6 +131,7 @@ def add_histogram(get_html, module, user):
is_released = "<font color='red'>Yes!</font>" if (now > mstart) else "<font color='green'>Not yet</font>"
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,
......
......@@ -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
......
......@@ -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()
......
......@@ -44,7 +44,7 @@ import sys
from registry import TagRegistry
log = logging.getLogger('mitx.' + __name__)
log = logging.getLogger(__name__)
#########################################################################
......
......@@ -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__)
#-----------------------------------------------------------------------------
......
......@@ -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):
......
......@@ -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
......
......@@ -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)
......
......@@ -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': {
# real metadata stays in the content, but add a display name
model_data = {
'error_msg': str(error_msg),
'contents': contents,
'display_name': 'Error: ' + location.name
}
}
# real metadata stays in the content, but add a display name
model_data = {'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)
......
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)
......@@ -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)
......@@ -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
......
......@@ -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,10 +160,9 @@ 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,
......@@ -180,9 +180,7 @@ 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:
......@@ -195,24 +193,30 @@ def import_from_xml(store, data_dir, course_dirs=None,
# For example, what I'm seeing is <img src='foo.jpg' /> -> <img src='bar.jpg'>
# 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,
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 module.has_children:
store.update_children(module.location, module.children)
if 'children' in module.definition:
store.update_children(module.location, module.definition['children'])
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):
# 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[field.name] = module._model_data[field.name]
store.update_metadata(module.location, metadata)
return module_store, course_items
......
......@@ -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
# 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)
# 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)
return getattr(self._module_cls, 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
......@@ -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)
"""
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.
Note: all the submissions are stored.
Sample file:
<selfassessment attempts="1" max_score="1">
<prompt>
Insert prompt text here. (arbitrary html)
</prompt>
<rubric>
Insert grading rubric here. (arbitrary html)
</rubric>
<hintprompt>
Please enter a hint below: (arbitrary html)
</hintprompt>
<submitmessage>
Thanks for submitting! (arbitrary html)
</submitmessage>
</selfassessment>
"""
# Load instance state
if instance_state is not None:
instance_state = json.loads(instance_state)
else:
instance_state = {}
# Note: score responses are on scale from 0 to max_score
self.student_answers = instance_state.get('student_answers', [])
self.scores = instance_state.get('scores', [])
self.hints = instance_state.get('hints', [])
self.state = instance_state.get('state', 'initial')
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)
# 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)
self._max_score = int(self.metadata.get('max_score', MAX_SCORE))
self.attempts = instance_state.get('attempts', 0)
self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS))
self.rubric = definition['rubric']
self.prompt = definition['prompt']
self.submit_message = definition['submitmessage']
self.hint_prompt = definition['hintprompt']
attempts = Int(scope=Scope.student_state, default=0), Int
max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS)
rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content)
submit_message = String(scope=Scope.content)
hint_prompt = String(scope=Scope.content)
def _allow_reset(self):
"""Can the module be reset?"""
......@@ -432,6 +387,21 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
js_module_name = "HTMLEditingDescriptor"
# The capa format specifies that what we call max_attempts in the code
# is the attribute `attempts`. This will do that conversion
metadata_translations = dict(XmlDescriptor.metadata_translations)
metadata_translations['attempts'] = 'max_attempts'
# 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)
max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS)
rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content)
submit_message = String(scope=Scope.content)
hint_prompt = String(scope=Scope.content)
@classmethod
def definition_from_xml(cls, xml_object, system):
"""
......
......@@ -30,7 +30,8 @@ i4xs = ModuleSystem(
debug=True,
xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10},
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
anonymous_student_id = 'student'
anonymous_student_id = 'student',
xmodule_model_data = lambda x: x,
)
......
......@@ -91,8 +91,10 @@ class ImportTestCase(unittest.TestCase):
self.assertEqual(re_import_descriptor.__class__.__name__,
'ErrorDescriptor')
self.assertEqual(descriptor.definition['data'],
re_import_descriptor.definition['data'])
self.assertEqual(descriptor.contents,
re_import_descriptor.contents)
self.assertEqual(descriptor.error_msg,
re_import_descriptor.error_msg)
def test_fixed_xml_tag(self):
"""Make sure a tag that's been fixed exports as the original tag type"""
......@@ -126,23 +128,19 @@ class ImportTestCase(unittest.TestCase):
url_name = 'test1'
start_xml = '''
<course org="{org}" course="{course}"
graceperiod="{grace}" url_name="{url_name}" unicorn="purple">
due="{due}" url_name="{url_name}" unicorn="purple">
<chapter url="hi" url_name="ch" display_name="CH">
<html url_name="h" display_name="H">Two houses, ...</html>
</chapter>
</course>'''.format(grace=v, org=ORG, course=COURSE, url_name=url_name)
</course>'''.format(due=v, org=ORG, course=COURSE, url_name=url_name)
descriptor = system.process_xml(start_xml)
print descriptor, descriptor.metadata
self.assertEqual(descriptor.metadata['graceperiod'], v)
self.assertEqual(descriptor.metadata['unicorn'], 'purple')
print descriptor, descriptor._model_data
self.assertEqual(descriptor.lms.due, v)
# Check that the child inherits graceperiod correctly
# Check that the child inherits due correctly
child = descriptor.get_children()[0]
self.assertEqual(child.metadata['graceperiod'], v)
# check that the child does _not_ inherit any unicorns
self.assertTrue('unicorn' not in child.metadata)
self.assertEqual(child.lms.due, v)
# Now export and check things
resource_fs = MemoryFS()
......@@ -169,12 +167,12 @@ class ImportTestCase(unittest.TestCase):
# did we successfully strip the url_name from the definition contents?
self.assertTrue('url_name' not in course_xml.attrib)
# Does the chapter tag now have a graceperiod attribute?
# Does the chapter tag now have a due attribute?
# hardcoded path to child
with resource_fs.open('chapter/ch.xml') as f:
chapter_xml = etree.fromstring(f.read())
self.assertEqual(chapter_xml.tag, 'chapter')
self.assertFalse('graceperiod' in chapter_xml.attrib)
self.assertFalse('due' in chapter_xml.attrib)
def test_is_pointer_tag(self):
"""
......@@ -216,7 +214,7 @@ class ImportTestCase(unittest.TestCase):
def check_for_key(key, node):
"recursive check for presence of key"
print "Checking {0}".format(node.location.url())
self.assertTrue(key in node.metadata)
self.assertTrue(key in node._model_data)
for c in node.get_children():
check_for_key(key, c)
......@@ -244,15 +242,15 @@ class ImportTestCase(unittest.TestCase):
toy_ch = toy.get_children()[0]
two_toys_ch = two_toys.get_children()[0]
self.assertEqual(toy_ch.display_name, "Overview")
self.assertEqual(two_toys_ch.display_name, "Two Toy Overview")
self.assertEqual(toy_ch.lms.display_name, "Overview")
self.assertEqual(two_toys_ch.lms.display_name, "Two Toy Overview")
# Also check that the grading policy loaded
self.assertEqual(two_toys.grade_cutoffs['C'], 0.5999)
# Also check that keys from policy are run through the
# appropriate attribute maps -- 'graded' should be True, not 'true'
self.assertEqual(toy.metadata['graded'], True)
self.assertEqual(toy.lms.graded, True)
def test_definition_loading(self):
......@@ -271,8 +269,8 @@ class ImportTestCase(unittest.TestCase):
location = Location(["i4x", "edX", "toy", "video", "Welcome"])
toy_video = modulestore.get_instance(toy_id, location)
two_toy_video = modulestore.get_instance(two_toy_id, location)
self.assertEqual(toy_video.metadata['youtube'], "1.0:p2Q6BrNhdh8")
self.assertEqual(two_toy_video.metadata['youtube'], "1.0:p2Q6BrNhdh9")
self.assertEqual(toy_video.youtube, "1.0:p2Q6BrNhdh8")
self.assertEqual(two_toy_video.youtube, "1.0:p2Q6BrNhdh9")
def test_colon_in_url_name(self):
......@@ -306,7 +304,7 @@ class ImportTestCase(unittest.TestCase):
cloc = course.location
loc = Location(cloc.tag, cloc.org, cloc.course, 'html', 'secret:toylab')
html = modulestore.get_instance(course_id, loc)
self.assertEquals(html.display_name, "Toy lab")
self.assertEquals(html.lms.display_name, "Toy lab")
def test_url_name_mangling(self):
"""
......@@ -351,4 +349,4 @@ class ImportTestCase(unittest.TestCase):
location = Location(["i4x", "edX", "sa_test", "selfassessment", "SampleQuestion"])
sa_sample = modulestore.get_instance(sa_id, location)
#10 attempts is hard coded into SampleQuestion, which is the url_name of a selfassessment xml tag
self.assertEqual(sa_sample.metadata['attempts'], '10')
self.assertEqual(sa_sample.max_attempts, '10')
class ModelMetaclassTester(object):
__metaclass__ = ModelMetaclass
field_a = Int(scope=Scope.settings)
field_b = Int(scope=Scope.content)
def test_model_metaclass():
import logging
import yaml
import os
import time
from lxml import etree
from pprint import pprint
......@@ -10,38 +9,14 @@ from pkg_resources import resource_listdir, resource_string, resource_isdir
from xmodule.modulestore import Location
from .model import ModelMetaclass, ParentModelMetaclass, NamespacesMetaclass, ModelType
from .model import ModelMetaclass, ParentModelMetaclass, NamespacesMetaclass
from .plugin import Plugin
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)
class XModuleMetaclass(ParentModelMetaclass, NamespacesMetaclass, ModelMetaclass):
pass
log = logging.getLogger('mitx.' + __name__)
log = logging.getLogger(__name__)
def dummy_track(event_type, event):
......
......@@ -311,11 +311,13 @@ class XmlDescriptor(XModuleDescriptor):
# Set/override any metadata specified by policy
k = policy_key(location)
if k in system.policy:
if k == 'video/labintro': print k, metadata, system.policy[k]
cls.apply_policy(metadata, system.policy[k])
model_data = {}
model_data.update(metadata)
model_data.update(definition)
if k == 'video/labintro': print model_data
return cls(
system,
......
......@@ -148,7 +148,7 @@ def grade(student, request, course, student_module_cache=None, keep_raw_scores=F
format_scores = []
for section in sections:
section_descriptor = section['section_descriptor']
section_name = section_descriptor.display_name
section_name = section_descriptor.lms.display_name
should_grade_section = False
# If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0%
......@@ -276,15 +276,13 @@ def progress_summary(student, request, course, student_module_cache):
# Don't include chapters that aren't displayable (e.g. due to error)
for chapter_module in course_module.get_display_items():
# Skip if the chapter is hidden
hidden = chapter_module._model_data.get('hide_from_toc','false')
if hidden.lower() == 'true':
if chapter_module.lms.hide_from_toc:
continue
sections = []
for section_module in chapter_module.get_display_items():
# Skip if the section is hidden
hidden = section_module._model_data.get('hide_from_toc','false')
if hidden.lower() == 'true':
if section_module.lms.hide_from_toc:
continue
# Same for sections
......@@ -309,17 +307,17 @@ def progress_summary(student, request, course, student_module_cache):
format = section_module.lms.format
sections.append({
'display_name': section_module.display_name,
'display_name': section_module.lms.display_name,
'url_name': section_module.url_name,
'scores': scores,
'section_total': section_total,
'format': format,
'due': section_module.due,
'due': section_module.lms.due,
'graded': graded,
})
chapters.append({'course': course.display_name,
'display_name': chapter_module.display_name,
chapters.append({'course': course.lms.display_name,
'display_name': chapter_module.lms.display_name,
'url_name': chapter_module.url_name,
'sections': sections})
......
......@@ -92,6 +92,7 @@ def toc_for_course(user, request, course, active_chapter, active_section):
chapters = list()
for chapter in course_module.get_display_items():
print chapter, chapter._model_data, chapter._model_data.get('hide_from_toc'), chapter.lms.hide_from_toc
if chapter.lms.hide_from_toc:
continue
......
......@@ -76,7 +76,7 @@ def instructor_dashboard(request, course_id):
data = [['# Enrolled', CourseEnrollment.objects.filter(course_id=course_id).count()]]
data += compute_course_stats(course).items()
if request.user.is_staff:
data.append(['metadata', escape(str(course.metadata))])
data.append(['metadata', escape(str(course._model_data))])
datatable['data'] = data
def return_csv(fn, datatable):
......
......@@ -3,7 +3,7 @@ import logging
import requests
import settings
log = logging.getLogger('mitx.' + __name__)
log = logging.getLogger(__name__)
def strip_none(dic):
return dict([(k, v) for k, v in dic.iteritems() if v is not None])
......
......@@ -46,9 +46,22 @@ github = <a href="${edit_link}">${edit_link | h}</a>
%if source_file:
source_url = <a href="${source_url}">${source_file | h}</a>
%endif
%for name, field in fields:
${name} = <pre style="display:inline-block">${field | h}</pre>
%endfor
<div>
<header><h3>Module Fields</h3></header>
%for name, field in fields:
<div>
${name} = <pre style="display:inline-block">${field | h}</pre>
</div>
%endfor
</div>
<div>
<header><h3>edX Fields</h3></header>
%for name, field in lms_fields:
<div>
${name} = <pre style="display:inline-block">${field | h}</pre>
</div>
%endfor
</div>
category = ${category | h}
</div>
%if render_histogram:
......
from xmodule.model import Namespace, Boolean, Scope, String, List
from xmodule.x_module import Date
from xmodule.fields import Date
class StringyBoolean(Boolean):
def from_json(self, value):
print "StringyBoolean ", value
if isinstance(value, basestring):
return value.lower() == 'true'
return value
class LmsNamespace(Namespace):
hide_from_toc = Boolean(
hide_from_toc = StringyBoolean(
help="Whether to display this module in the table of contents",
default=False,
scope=Scope.settings
......@@ -16,6 +25,7 @@ class LmsNamespace(Namespace):
help="What format this module is in (used for deciding which "
"grader to apply, and what to show in the TOC)",
scope=Scope.settings,
default='',
)
display_name = String(
......
# Python libraries to install that are local to the mitx repo
-e common/lib/capa
-e common/lib/xmodule
-e lms
-e .
from setuptools import setup, find_packages
setup(
name="edX LMS",
name="edX Apps",
version="0.1",
install_requires=['distribute'],
requires=[
'xmodule',
],
py_modules=['lms.xmodule_namespace'],
# See http://guide.python-distribute.org/creation.html#entry-points
# for a description of entry_points
entry_points={
......
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