Files
edx-platform/common/lib/xmodule/xmodule/error_module.py
Calen Pennington 4f555f6480 Merge remote-tracking branch 'origin/master' into feature/cale/xmodule-storage-model
Conflicts:
	cms/djangoapps/contentstore/course_info_model.py
	cms/djangoapps/contentstore/module_info_model.py
	cms/djangoapps/contentstore/tests/factories.py
	cms/djangoapps/contentstore/tests/test_course_settings.py
	cms/djangoapps/contentstore/views.py
	cms/djangoapps/models/settings/course_details.py
	cms/djangoapps/models/settings/course_grading.py
	cms/templates/edit_subsection.html
	common/djangoapps/mitxmako/shortcuts.py
	common/djangoapps/static_replace.py
	common/djangoapps/util/converters.py
	common/djangoapps/xmodule_modifiers.py
	common/lib/xmodule/setup.py
	common/lib/xmodule/xmodule/abtest_module.py
	common/lib/xmodule/xmodule/capa_module.py
	common/lib/xmodule/xmodule/course_module.py
	common/lib/xmodule/xmodule/discussion_module.py
	common/lib/xmodule/xmodule/error_module.py
	common/lib/xmodule/xmodule/html_module.py
	common/lib/xmodule/xmodule/js/src/video/display.coffee
	common/lib/xmodule/xmodule/mako_module.py
	common/lib/xmodule/xmodule/modulestore/mongo.py
	common/lib/xmodule/xmodule/modulestore/store_utilities.py
	common/lib/xmodule/xmodule/modulestore/xml.py
	common/lib/xmodule/xmodule/modulestore/xml_importer.py
	common/lib/xmodule/xmodule/raw_module.py
	common/lib/xmodule/xmodule/self_assessment_module.py
	common/lib/xmodule/xmodule/seq_module.py
	common/lib/xmodule/xmodule/template_module.py
	common/lib/xmodule/xmodule/tests/__init__.py
	common/lib/xmodule/xmodule/tests/test_export.py
	common/lib/xmodule/xmodule/tests/test_import.py
	common/lib/xmodule/xmodule/video_module.py
	common/lib/xmodule/xmodule/x_module.py
	common/lib/xmodule/xmodule/xml_module.py
	lms/djangoapps/courseware/access.py
	lms/djangoapps/courseware/courses.py
	lms/djangoapps/courseware/grades.py
	lms/djangoapps/courseware/models.py
	lms/djangoapps/courseware/module_render.py
	lms/djangoapps/courseware/tests/tests.py
	lms/djangoapps/courseware/views.py
	lms/djangoapps/django_comment_client/utils.py
	lms/templates/staff_problem_info.html
	lms/templates/video.html
	rakefile
2013-02-07 15:12:30 -05:00

169 lines
5.7 KiB
Python

import hashlib
import logging
import json
import sys
from lxml import etree
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 xblock.core import String, Scope
log = logging.getLogger(__name__)
# NOTE: This is not the most beautiful design in the world, but there's no good
# way to tell if the module is being used in a staff context or not. Errors that get discovered
# at course load time are turned into ErrorDescriptor objects, and automatically hidden from students.
# Unfortunately, we can also have errors when loading modules mid-request, and then we need to decide
# what to show, and the logic for that belongs in the LMS (e.g. in get_module), so the error handler
# decides whether to create a staff or not-staff module.
class ErrorModule(XModule):
contents = String(scope=Scope.content)
error_msg = String(scope=Scope.content)
def get_html(self):
'''Show an error to staff.
TODO (vshnayder): proper style, divs, etc.
'''
# staff get to see all the details
return self.system.render_template('module-error.html', {
'staff_access': True,
'data': self.contents,
'error': self.error_msg,
})
class NonStaffErrorModule(XModule):
def get_html(self):
'''Show an error to a student.
TODO (vshnayder): proper style, divs, etc.
'''
# staff get to see all the details
return self.system.render_template('module-error.html', {
'staff_access': False,
'data': "",
'error': "",
})
class ErrorDescriptor(JSONEditingDescriptor):
"""
Module that provides a raw editing view of broken xml.
"""
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):
if location.name is None:
location = location._replace(
category='error',
# Pick a unique url_name -- the sha1 hash of the contents.
# NOTE: We could try to pull out the url_name of the errored descriptor,
# but url_names aren't guaranteed to be unique between descriptor types,
# and ErrorDescriptor can wrap any type. When the wrapped module is fixed,
# it will be written out with the original url_name.
name=hashlib.sha1(contents).hexdigest()
)
# 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
}
return ErrorDescriptor(
system,
location,
model_data,
)
def get_context(self):
return {
'module': self,
'data': self.contents,
}
@classmethod
def from_json(cls, json_data, system, error_msg='Error not available'):
return cls._construct(
system,
json.dumps(json_data, indent=4),
error_msg,
location=Location(json_data['location']),
)
@classmethod
def from_descriptor(cls, descriptor, error_msg='Error not available'):
return cls._construct(
descriptor.system,
descriptor._model_data,
error_msg,
location=descriptor.location,
)
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None,
error_msg='Error not available'):
'''Create an instance of this descriptor from the supplied data.
Does not require that xml_data be parseable--just stores it and exports
as-is if not.
Takes an extra, optional, parameter--the error that caused an
issue. (should be a string, or convert usefully into one).
'''
try:
# If this is already an error tag, don't want to re-wrap it.
xml_obj = etree.fromstring(xml_data)
if xml_obj.tag == 'error':
xml_data = xml_obj.text
error_node = xml_obj.find('error_msg')
if error_node is not None:
error_msg = error_node.text
else:
error_msg = 'Error not available'
except etree.XMLSyntaxError:
# Save the error to display later--overrides other problems
error_msg = exc_info_to_str(sys.exc_info())
return cls._construct(system, xml_data, error_msg, location=Location('i4x', org, course, None, None))
def export_to_xml(self, resource_fs):
'''
If the definition data is invalid xml, export it wrapped in an "error"
tag. If it is valid, export without the wrapper.
NOTE: There may still be problems with the valid xml--it could be
missing required attributes, could have the wrong tags, refer to missing
files, etc. That would just get re-wrapped on import.
'''
try:
xml = etree.fromstring(self.contents)
return etree.tostring(xml, encoding='unicode')
except etree.XMLSyntaxError:
# still not valid.
root = etree.Element('error')
root.text = self.contents
err_node = etree.SubElement(root, 'error_msg')
err_node.text = self.error_msg
return etree.tostring(root, encoding='unicode')
class NonStaffErrorDescriptor(ErrorDescriptor):
"""
Module that provides non-staff error messages.
"""
module_class = NonStaffErrorModule