From bf4ae6b7e6ff4caf7cd2fa8f12f9d35a0212c76f Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 21 Sep 2012 11:27:47 -0400 Subject: [PATCH] Load modules from the mongo modulestore as errordescriptors if creating a regular descriptor fails, but leave the location the same --- common/lib/xmodule/xmodule/error_module.py | 45 +++++++++++++------ .../lib/xmodule/xmodule/modulestore/mongo.py | 20 ++++++--- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/common/lib/xmodule/xmodule/error_module.py b/common/lib/xmodule/xmodule/error_module.py index ca0f2c5718..220d818a7a 100644 --- a/common/lib/xmodule/xmodule/error_module.py +++ b/common/lib/xmodule/xmodule/error_module.py @@ -7,6 +7,7 @@ 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 log = logging.getLogger(__name__) @@ -52,14 +53,18 @@ class ErrorDescriptor(JSONEditingDescriptor): module_class = ErrorModule @classmethod - def _construct(self, system, contents, error_msg, org=None, course=None): + def _construct(self, system, contents, error_msg, location): - # 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. - url_name = hashlib.sha1(contents).hexdigest() + 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() + ) definition = { 'data': { @@ -68,12 +73,8 @@ class ErrorDescriptor(JSONEditingDescriptor): } } - # TODO (vshnayder): Do we need a unique slug here? Just pick a random - # 64-bit num? - location = ['i4x', org, course, 'error', url_name] - # real metadata stays in the content, but add a display name - metadata = {'display_name': 'Error ' + url_name} + metadata = {'display_name': 'Error: ' + location.name} super(ErrorDescriptor, self).__init__( system, definition, @@ -81,6 +82,21 @@ class ErrorDescriptor(JSONEditingDescriptor): metadata=metadata ) + def get_context(self): + return { + 'module': self, + 'data': self.definition['data']['contents'], + } + + @classmethod + def from_json(cls, json_data, system, error_msg='Error not available'): + return cls( + 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( @@ -89,7 +105,8 @@ class ErrorDescriptor(JSONEditingDescriptor): 'definition': descriptor.definition, 'metadata': descriptor.metadata, }, indent=4), - error_msg + error_msg, + location=descriptor.location, ) @classmethod @@ -119,7 +136,7 @@ class ErrorDescriptor(JSONEditingDescriptor): # 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, org=org, course=course) + return cls._construct(system, xml_data, error_msg, location=Location('i4x', org, course, None, None)) def export_to_xml(self, resource_fs): ''' diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index 23adc0077d..baa4e7870c 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -1,4 +1,5 @@ import pymongo +import sys from bson.son import SON from fs.osfs import OSFS @@ -6,13 +7,14 @@ from itertools import repeat from path import path from importlib import import_module -from xmodule.errortracker import null_error_tracker +from xmodule.errortracker import null_error_tracker, exc_info_to_str from xmodule.x_module import XModuleDescriptor from xmodule.mako_module import MakoDescriptorSystem +from xmodule.error_module import ErrorDescriptor from . import ModuleStoreBase, Location from .exceptions import (ItemNotFoundError, - NoPathToItem, DuplicateItemError) + DuplicateItemError) # 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, @@ -57,7 +59,14 @@ class CachingDescriptorSystem(MakoDescriptorSystem): # TODO (vshnayder): metadata inheritance is somewhat broken because mongo, doesn't # always load an entire course. We're punting on this until after launch, and then # will build a proper course policy framework. - return XModuleDescriptor.load_from_json(json_data, self, self.default_class) + try: + return XModuleDescriptor.load_from_json(json_data, self, self.default_class) + except: + return ErrorDescriptor.from_json( + json_data, + self, + error_msg=exc_info_to_str(sys.exc_info()) + ) def location_to_query(location): @@ -154,7 +163,7 @@ class MongoModuleStore(ModuleStoreBase): """ data_dir = item.get('metadata', {}).get('data_dir', item['location']['course']) root = self.fs_root / data_dir - + if not root.isdir(): root.mkdir() @@ -267,7 +276,6 @@ class MongoModuleStore(ModuleStoreBase): if result['n'] == 0: raise ItemNotFoundError(location) - def update_item(self, location, data): """ Set the data in the item specified by the location to @@ -313,7 +321,7 @@ class MongoModuleStore(ModuleStoreBase): ''' location = Location.ensure_fully_specified(location) # Check that it's actually in this modulestore. - item = self._find_one(location) + self._find_one(location) # now get the parents items = self.collection.find({'definition.children': location.url()}, {'_id': True})