From 188e1c68d79d108f59adbb54018343bcbfe7fd4d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 30 Oct 2012 20:13:35 -0400 Subject: [PATCH] add self assessment module --- .idea/.name | 1 + .idea/encodings.xml | 5 + .../inspectionProfiles/profiles_settings.xml | 7 + .idea/misc.xml | 34 ++ .idea/mitx.iml | 9 + .idea/modules.xml | 9 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + .idea/workspace.xml | 394 ++++++++++++++++++ common/lib/xmodule/setup.py | 1 + .../xmodule/xmodule/self_assessment_module.py | 174 ++++++++ 11 files changed, 646 insertions(+) create mode 100644 .idea/.name create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/mitx.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 common/lib/xmodule/xmodule/self_assessment_module.py diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000000..36d4bf6d51 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +mitx \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000000..e206d70d85 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..c60c33bb47 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..715d0f8f2d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,34 @@ + + + + $APPLICATION_HOME_DIR$/lib/pycharm.jar!/resources/html5-schema/html5.rnc + + + + + + + + + + diff --git a/.idea/mitx.iml b/.idea/mitx.iml new file mode 100644 index 0000000000..a34a85703f --- /dev/null +++ b/.idea/mitx.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..7eaf1301cf --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000000..922003b843 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..c80f2198b5 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000000..08fc21c094 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,394 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1351640399572 + 1351640399572 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 0441357427..e6c8fe577d 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -28,6 +28,7 @@ setup( "problem = xmodule.capa_module:CapaDescriptor", "problemset = xmodule.seq_module:SequenceDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor", + "selfassessment = xmodule.self_assessment_module:SelfAssessmentModule" "sequential = xmodule.seq_module:SequenceDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor", diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py new file mode 100644 index 0000000000..c0dd835208 --- /dev/null +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -0,0 +1,174 @@ +import copy +from fs.errors import ResourceNotFoundError +import logging +import os +import sys +from lxml import etree +from lxml.html import rewrite_links +from path import path + +from .x_module import XModule +from pkg_resources import resource_string +from .xml_module import XmlDescriptor, name_to_pathname +from .editing_module import EditingDescriptor +from .stringify import stringify_children +from .html_checker import check_html +from xmodule.modulestore import Location + +from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent + +log = logging.getLogger("mitx.courseware") + + +class SelfAssessmentModule(XModule): + js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), + resource_string(__name__, 'js/src/collapsible.coffee'), + resource_string(__name__, 'js/src/html/display.coffee') + ] + } + js_module_name = "SelfAssessmentModule" + + def get_html(self): + # cdodge: perform link substitutions for any references to course static content (e.g. images) + return rewrite_links(self.html, self.rewrite_content_links) + + 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) + self.html = self.definition['data'] + + + +class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): + """ + Module for putting raw html in a course + """ + mako_template = "widgets/html-edit.html" + module_class = HtmlModule + filename_extension = "xml" + template_dir_name = "selfassessment" + + js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} + js_module_name = "HTMLEditingDescriptor" + + # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course + # are being edited in the cms + @classmethod + def backcompat_paths(cls, path): + if path.endswith('.html.xml'): + path = path[:-9] + '.html' # backcompat--look for html instead of xml + if path.endswith('.html.html'): + path = path[:-5] # some people like to include .html in filenames.. + candidates = [] + while os.sep in path: + candidates.append(path) + _, _, path = path.partition(os.sep) + + # also look for .html versions instead of .xml + nc = [] + for candidate in candidates: + if candidate.endswith('.xml'): + nc.append(candidate[:-4] + '.html') + return candidates + nc + + # NOTE: html descriptors are special. We do not want to parse and + # export them ourselves, because that can break things (e.g. lxml + # adds body tags when it exports, but they should just be html + # snippets that will be included in the middle of pages. + + @classmethod + def load_definition(cls, xml_object, system, location): + '''Load a descriptor from the specified xml_object: + + If there is a filename attribute, load it as a string, and + log a warning if it is not parseable by etree.HTMLParser. + + If there is not a filename attribute, the definition is the body + of the xml_object, without the root tag (do not want in the + middle of a page) + ''' + filename = xml_object.get('filename') + if filename is None: + definition_xml = copy.deepcopy(xml_object) + cls.clean_metadata_from_xml(definition_xml) + return {'data': stringify_children(definition_xml)} + else: + # html is special. cls.filename_extension is 'xml', but + # if 'filename' is in the definition, that means to load + # from .html + # 'filename' in html pointers is a relative path + # (not same as 'html/blah.html' when the pointer is in a directory itself) + pointer_path = "{category}/{url_path}".format(category='html', + url_path=name_to_pathname(location.name)) + base = path(pointer_path).dirname() + #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename)) + filepath = "{base}/{name}.html".format(base=base, name=filename) + #log.debug("looking for html file for {0} at {1}".format(location, filepath)) + + + + # VS[compat] + # TODO (cpennington): If the file doesn't exist at the right path, + # give the class a chance to fix it up. The file will be written out + # again in the correct format. This should go away once the CMS is + # online and has imported all current (fall 2012) courses from xml + if not system.resources_fs.exists(filepath): + candidates = cls.backcompat_paths(filepath) + #log.debug("candidates = {0}".format(candidates)) + for candidate in candidates: + if system.resources_fs.exists(candidate): + filepath = candidate + break + + try: + with system.resources_fs.open(filepath) as file: + html = file.read() + # Log a warning if we can't parse the file, but don't error + if not check_html(html): + msg = "Couldn't parse html in {0}.".format(filepath) + log.warning(msg) + system.error_tracker("Warning: " + msg) + + definition = {'data': html} + + # TODO (ichuang): remove this after migration + # for Fall 2012 LMS migration: keep filename (and unmangled filename) + definition['filename'] = [ filepath, filename ] + + return definition + + except (ResourceNotFoundError) as err: + msg = 'Unable to load file contents at path {0}: {1} '.format( + filepath, err) + # add more info and re-raise + raise Exception(msg), None, sys.exc_info()[2] + + # TODO (vshnayder): make export put things in the right places. + + def definition_to_xml(self, resource_fs): + '''If the contents are valid xml, write them to filename.xml. Otherwise, + write just to filename.xml, and the html + string to filename.html. + ''' + try: + return etree.fromstring(self.definition['data']) + except etree.XMLSyntaxError: + pass + + # Not proper format. Write html to file, return an empty tag + pathname = name_to_pathname(self.url_name) + pathdir = path(pathname).dirname() + filepath = u'{category}/{pathname}.html'.format(category=self.category, + pathname=pathname) + + resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) + with resource_fs.open(filepath, 'w') as file: + file.write(self.definition['data']) + + # write out the relative name + relname = path(pathname).basename() + + elt = etree.Element('html') + elt.set("filename", relname) + return elt