Commit bd73cc16 by Chris Dodge

compute (and cache) a generation of metadata inheritance tree for the course. We…

compute (and cache) a generation of metadata inheritance tree for the course. We can then add that metadata to a newly instantiated xmodule
parent bfcc0559
......@@ -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
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]
......
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