compute (and cache) a generation of metadata inheritance tree for the course. We can then add that metadata to a newly instantiated xmodule
This commit is contained in:
@@ -44,5 +44,6 @@ class MakoModuleDescriptor(XModuleDescriptor):
|
||||
# cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata)
|
||||
@property
|
||||
def editable_metadata_fields(self):
|
||||
subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields]
|
||||
subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields and
|
||||
name not in self._inherited_metadata]
|
||||
return subset
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import pymongo
|
||||
import sys
|
||||
import logging
|
||||
import copy
|
||||
|
||||
from bson.son import SON
|
||||
from fs.osfs import OSFS
|
||||
from itertools import repeat
|
||||
from path import path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from importlib import import_module
|
||||
from xmodule.errortracker import null_error_tracker, exc_info_to_str
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
from xmodule.x_module import XModuleDescriptor, inheritable_metadata
|
||||
from xmodule.mako_module import MakoDescriptorSystem
|
||||
from xmodule.error_module import ErrorDescriptor
|
||||
|
||||
@@ -29,7 +31,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
from, with a backup of calling to the underlying modulestore for more data
|
||||
"""
|
||||
def __init__(self, modulestore, module_data, default_class, resources_fs,
|
||||
error_tracker, render_template):
|
||||
error_tracker, render_template, metadata_inheritance_tree = None):
|
||||
"""
|
||||
modulestore: the module store that can be used to retrieve additional modules
|
||||
|
||||
@@ -54,6 +56,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
# cdodge: other Systems have a course_id attribute defined. To keep things consistent, let's
|
||||
# define an attribute here as well, even though it's None
|
||||
self.course_id = None
|
||||
self.metadata_inheritance_tree = metadata_inheritance_tree
|
||||
|
||||
def load_item(self, location):
|
||||
location = Location(location)
|
||||
@@ -65,8 +68,12 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
# always load an entire course. We're punting on this until after launch, and then
|
||||
# will build a proper course policy framework.
|
||||
try:
|
||||
return XModuleDescriptor.load_from_json(json_data, self, self.default_class)
|
||||
module = XModuleDescriptor.load_from_json(json_data, self, self.default_class)
|
||||
metadata_to_inherit = self.metadata_inheritance_tree.get('parent_metadata', {}).get(location.url(),{})
|
||||
module.inherit_metadata(metadata_to_inherit)
|
||||
return module
|
||||
except:
|
||||
raise
|
||||
return ErrorDescriptor.from_json(
|
||||
json_data,
|
||||
self,
|
||||
@@ -142,6 +149,76 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
self.fs_root = path(fs_root)
|
||||
self.error_tracker = error_tracker
|
||||
self.render_template = render_template
|
||||
self.metadata_inheritance_cache = {}
|
||||
|
||||
def get_metadata_inheritance_tree(self, location):
|
||||
|
||||
# get all collections in the course, this query should not return any leaf nodes
|
||||
query = { '_id.org' : location.org,
|
||||
'_id.course' : location.course,
|
||||
'_id.revision' : None,
|
||||
'definition.children':{'$ne': []}
|
||||
}
|
||||
# we just want the Location, children, and metadata
|
||||
record_filter = {'_id':1,'definition.children':1,'metadata':1}
|
||||
|
||||
# call out to the DB
|
||||
resultset = self.collection.find(query, record_filter)
|
||||
|
||||
results_by_url = {}
|
||||
root = None
|
||||
|
||||
# now go through the results and order them by the location url
|
||||
for result in resultset:
|
||||
location = Location(result['_id'])
|
||||
results_by_url[location.url()] = result
|
||||
if location.category == 'course':
|
||||
root = location.url()
|
||||
|
||||
# now traverse the tree and compute down the inherited metadata
|
||||
metadata_to_inherit = {}
|
||||
def _compute_inherited_metadata(url):
|
||||
my_metadata = results_by_url[url]['metadata']
|
||||
for key in my_metadata.keys():
|
||||
if key not in inheritable_metadata:
|
||||
del my_metadata[key]
|
||||
results_by_url[url]['metadata'] = my_metadata
|
||||
|
||||
# go through all the children and recurse, but only if we have
|
||||
# in the result set. Remember results will not contain leaf nodes
|
||||
for child in results_by_url[url].get('definition',{}).get('children',[]):
|
||||
if child in results_by_url:
|
||||
new_child_metadata = copy.deepcopy(my_metadata)
|
||||
new_child_metadata.update(results_by_url[child]['metadata'])
|
||||
results_by_url[child]['metadata'] = new_child_metadata
|
||||
metadata_to_inherit[child] = new_child_metadata
|
||||
_compute_inherited_metadata(child)
|
||||
else:
|
||||
# this is likely a leaf node, so let's record what metadata we need to inherit
|
||||
metadata_to_inherit[child] = my_metadata
|
||||
|
||||
if root is not None:
|
||||
_compute_inherited_metadata(root)
|
||||
|
||||
cache = {'parent_metadata': metadata_to_inherit,
|
||||
'timestamp' : datetime.now()}
|
||||
|
||||
return cache
|
||||
|
||||
def get_cached_metadata_inheritance_tree(self, location, max_age_allowed):
|
||||
cache_name = '{0}/{1}'.format(location.org, location.course)
|
||||
cache = self.metadata_inheritance_cache.get(cache_name,{'parent_metadata': {},
|
||||
'timestamp': datetime.now() - timedelta(hours=1)})
|
||||
age = (datetime.now() - cache['timestamp'])
|
||||
|
||||
if age.seconds >= max_age_allowed:
|
||||
logging.debug('loading entire inheritance tree for {0}'.format(cache_name))
|
||||
cache = self.get_metadata_inheritance_tree(location)
|
||||
self.metadata_inheritance_cache[cache_name] = cache
|
||||
|
||||
return cache
|
||||
|
||||
|
||||
|
||||
def _clean_item_data(self, item):
|
||||
"""
|
||||
@@ -203,6 +280,7 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
resource_fs,
|
||||
self.error_tracker,
|
||||
self.render_template,
|
||||
metadata_inheritance_tree = self.get_cached_metadata_inheritance_tree(Location(item['location']), 60)
|
||||
)
|
||||
return system.load_item(item['location'])
|
||||
|
||||
@@ -261,11 +339,11 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
descendents of the queried modules for more efficient results later
|
||||
in the request. The depth is counted in the number of
|
||||
calls to get_children() to cache. None indicates to cache all descendents.
|
||||
|
||||
"""
|
||||
location = Location.ensure_fully_specified(location)
|
||||
item = self._find_one(location)
|
||||
return self._load_items([item], depth)[0]
|
||||
module = self._load_items([item], depth)[0]
|
||||
return module
|
||||
|
||||
def get_instance(self, course_id, location, depth=0):
|
||||
"""
|
||||
@@ -285,7 +363,8 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
sort=[('revision', pymongo.ASCENDING)],
|
||||
)
|
||||
|
||||
return self._load_items(list(items), depth)
|
||||
modules = self._load_items(list(items), depth)
|
||||
return modules
|
||||
|
||||
def clone_item(self, source, location):
|
||||
"""
|
||||
@@ -313,7 +392,7 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
raise DuplicateItemError(location)
|
||||
|
||||
|
||||
def get_course_for_item(self, location):
|
||||
def get_course_for_item(self, location, depth=0):
|
||||
'''
|
||||
VS[compat]
|
||||
cdodge: for a given Xmodule, return the course that it belongs to
|
||||
@@ -327,7 +406,7 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
# know the 'name' parameter in this context, so we have
|
||||
# to assume there's only one item in this query even though we are not specifying a name
|
||||
course_search_location = ['i4x', location.org, location.course, 'course', None]
|
||||
courses = self.get_items(course_search_location)
|
||||
courses = self.get_items(course_search_location, depth=depth)
|
||||
|
||||
# make sure we found exactly one match on this above course search
|
||||
found_cnt = len(courses)
|
||||
|
||||
@@ -411,6 +411,19 @@ class ResourceTemplates(object):
|
||||
|
||||
return templates
|
||||
|
||||
# A list of metadata that this module can inherit from its parent module
|
||||
inheritable_metadata = (
|
||||
'graded', 'start', 'due', 'graceperiod', 'showanswer', 'rerandomize',
|
||||
# TODO (ichuang): used for Fall 2012 xqa server access
|
||||
'xqa_key',
|
||||
# TODO: This is used by the XMLModuleStore to provide for locations for
|
||||
# static files, and will need to be removed when that code is removed
|
||||
'data_dir',
|
||||
# How many days early to show a course element to beta testers (float)
|
||||
# intended to be set per-course, but can be overridden in for specific
|
||||
# elements. Can be a float.
|
||||
'days_early_for_beta'
|
||||
)
|
||||
|
||||
class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
|
||||
"""
|
||||
@@ -433,20 +446,6 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
|
||||
# It should respond to max_score() and grade(). It can be graded or ungraded
|
||||
# (like a practice problem).
|
||||
|
||||
# A list of metadata that this module can inherit from its parent module
|
||||
inheritable_metadata = (
|
||||
'graded', 'start', 'due', 'graceperiod', 'showanswer', 'rerandomize',
|
||||
# TODO (ichuang): used for Fall 2012 xqa server access
|
||||
'xqa_key',
|
||||
# TODO: This is used by the XMLModuleStore to provide for locations for
|
||||
# static files, and will need to be removed when that code is removed
|
||||
'data_dir',
|
||||
# How many days early to show a course element to beta testers (float)
|
||||
# intended to be set per-course, but can be overridden in for specific
|
||||
# elements. Can be a float.
|
||||
'days_early_for_beta'
|
||||
)
|
||||
|
||||
# cdodge: this is a list of metadata names which are 'system' metadata
|
||||
# and should not be edited by an end-user
|
||||
system_metadata_fields = ['data_dir', 'published_date', 'published_by', 'is_draft']
|
||||
@@ -585,12 +584,12 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
|
||||
def inherit_metadata(self, metadata):
|
||||
"""
|
||||
Updates this module with metadata inherited from a containing module.
|
||||
Only metadata specified in self.inheritable_metadata will
|
||||
Only metadata specified in inheritable_metadata will
|
||||
be inherited
|
||||
"""
|
||||
# Set all inheritable metadata from kwargs that are
|
||||
# in self.inheritable_metadata and aren't already set in metadata
|
||||
for attr in self.inheritable_metadata:
|
||||
# in inheritable_metadata and aren't already set in metadata
|
||||
for attr in inheritable_metadata:
|
||||
if attr not in self.metadata and attr in metadata:
|
||||
self._inherited_metadata.add(attr)
|
||||
self.metadata[attr] = metadata[attr]
|
||||
|
||||
Reference in New Issue
Block a user