Commit ee71eba1 by Don Mitchell

Merge pull request #5109 from edx/split/inherited_settings

Cache inherited settings outside of structure
parents e2645774 0f8355fd
......@@ -30,7 +30,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
modulestore: the module store that can be used to retrieve additional
modules
course_entry: the originally fetched enveloped course_structure w/ branch and course id info.
course_entry: the originally fetched enveloped course_structure w/ branch and course id info
plus a dictionary of cached inherited_settings indexed by (block_type, block_id) tuple.
Callers to _load_item provide an override but that function ignores the provided structure and
only looks at the branch and course id
......@@ -47,7 +48,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
super(CachingDescriptorSystem, self).__init__(
field_data=None,
load_item=self._load_item,
resources_fs = OSFS(root),
resources_fs=OSFS(root),
**kwargs
)
self.modulestore = modulestore
......@@ -57,9 +58,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
# Compute inheritance
modulestore.inherit_settings(
course_entry['structure'].get('blocks', {}),
course_entry['structure'].get('blocks', {}).get(
encode_key_for_mongo(course_entry['structure'].get('root'))
)
encode_key_for_mongo(course_entry['structure'].get('root')),
course_entry.setdefault('inherited_settings', {}),
)
self.default_class = default_class
self.local_modules = {}
......@@ -93,7 +93,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
json_data = self.get_module_data(block_id, course_key)
class_ = self.load_block_type(json_data.get('category'))
new_item = self.xblock_from_json(class_, course_key, block_id, json_data, course_entry_override, **kwargs)
# pass None for inherited_settings to signal that it should get the settings from cache
new_item = self.xblock_from_json(class_, course_key, block_id, json_data, None, course_entry_override, **kwargs)
return new_item
def get_module_data(self, block_id, course_key):
......@@ -124,7 +125,9 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
# low; thus, the course_entry is most likely correct. If the thread is looking at > 1 named container
# pointing to the same structure, the access is likely to be chunky enough that the last known container
# is the intended one when not given a course_entry_override; thus, the caching of the last branch/course id.
def xblock_from_json(self, class_, course_key, block_id, json_data, course_entry_override=None, **kwargs):
def xblock_from_json(
self, class_, course_key, block_id, json_data, inherited_settings, course_entry_override=None, **kwargs
):
if course_entry_override is None:
course_entry_override = self.course_entry
else:
......@@ -136,6 +139,13 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
definition_id = json_data.get('definition')
block_type = json_data['category']
if block_id is not None:
if inherited_settings is None:
# see if there's a value in course_entry
if (block_type, block_id) in self.course_entry['inherited_settings']:
inherited_settings = self.course_entry['inherited_settings'][(block_type, block_id)]
elif (block_type, block_id) not in self.course_entry['inherited_settings']:
self.course_entry['inherited_settings'][(block_type, block_id)] = inherited_settings
if definition_id is not None and not json_data.get('definition_loaded', False):
definition_loader = DefinitionLazyLoader(
......@@ -168,7 +178,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
kvs = SplitMongoKVS(
definition_loader,
converted_fields,
json_data.get('_inherited_settings'),
inherited_settings,
**kwargs
)
field_data = KvsFieldData(kvs)
......
......@@ -57,7 +57,6 @@ from path import path
import copy
from pytz import UTC
from bson.objectid import ObjectId
from pymongo.errors import DuplicateKeyError
from xblock.core import XBlock
from xblock.fields import Scope, Reference, ReferenceList, ReferenceValueDict
......@@ -1628,15 +1627,20 @@ class SplitMongoModuleStore(BulkWriteMixin, ModuleStoreWriteBase):
}
if definition_id is not None:
json_data['definition'] = definition_id
if parent_xblock is not None:
json_data['_inherited_settings'] = parent_xblock.xblock_kvs.inherited_settings.copy()
if parent_xblock is None:
# If no parent, then nothing to inherit.
inherited_settings = {}
else:
inherited_settings = parent_xblock.xblock_kvs.inherited_settings.copy()
if fields is not None:
for field_name in inheritance.InheritanceMixin.fields:
if field_name in fields:
json_data['_inherited_settings'][field_name] = fields[field_name]
inherited_settings[field_name] = fields[field_name]
new_block = runtime.xblock_from_json(xblock_class, course_key, block_id, json_data, **kwargs)
for field_name, value in fields.iteritems():
new_block = runtime.xblock_from_json(
xblock_class, course_key, block_id, json_data, inherited_settings, **kwargs
)
for field_name, value in (fields or {}).iteritems():
setattr(new_block, field_name, value)
if parent_xblock is not None:
......@@ -1936,12 +1940,14 @@ class SplitMongoModuleStore(BulkWriteMixin, ModuleStoreWriteBase):
# in case the course is later restored.
# super(SplitMongoModuleStore, self).delete_course(course_key, user_id)
def inherit_settings(self, block_map, block_json, inheriting_settings=None):
def inherit_settings(self, block_map, block_id, inherited_settings_map, inheriting_settings=None):
"""
Updates block_json with any inheritable setting set by an ancestor and recurses to children.
"""
if block_json is None:
encoded_key = encode_key_for_mongo(block_id)
if encoded_key not in block_map:
return
block_json = block_map[encoded_key]
if inheriting_settings is None:
inheriting_settings = {}
......@@ -1949,11 +1955,10 @@ class SplitMongoModuleStore(BulkWriteMixin, ModuleStoreWriteBase):
# the currently passed down values take precedence over any previously cached ones
# NOTE: this should show the values which all fields would have if inherited: i.e.,
# not set to the locally defined value but to value set by nearest ancestor who sets it
# ALSO NOTE: no xblock should ever define a _inherited_settings field as it will collide w/ this logic.
block_json.setdefault('_inherited_settings', {}).update(inheriting_settings)
inherited_settings_map.setdefault((block_json['category'], block_id), {}).update(inheriting_settings)
# update the inheriting w/ what should pass to children
inheriting_settings = block_json['_inherited_settings'].copy()
inheriting_settings = inherited_settings_map[(block_json['category'], block_id)].copy()
block_fields = block_json['fields']
for field_name in inheritance.InheritanceMixin.fields:
if field_name in block_fields:
......@@ -1962,7 +1967,7 @@ class SplitMongoModuleStore(BulkWriteMixin, ModuleStoreWriteBase):
for child in block_fields.get('children', []):
try:
child = encode_key_for_mongo(child)
self.inherit_settings(block_map, block_map[child], inheriting_settings)
self.inherit_settings(block_map, child, inherited_settings_map, inheriting_settings)
except KeyError:
# here's where we need logic for looking up in other structures when we allow cross pointers
# but it's also getting this during course creation if creating top down w/ children set or
......
......@@ -1564,6 +1564,56 @@ class TestInheritance(SplitModuleTest):
# overridden
self.assertEqual(node.graceperiod, datetime.timedelta(hours=4))
def test_inheritance_not_saved(self):
"""
Was saving inherited settings with updated blocks causing inheritance to be sticky
"""
# set on parent, retrieve child, verify setting
chapter = modulestore().get_item(
BlockUsageLocator(
CourseLocator(org='testx', course='GreekHero', run="run", branch=BRANCH_NAME_DRAFT), 'chapter', 'chapter3'
)
)
problem = modulestore().get_item(
BlockUsageLocator(
CourseLocator(org='testx', course='GreekHero', run="run", branch=BRANCH_NAME_DRAFT), 'problem', 'problem3_2'
)
)
self.assertFalse(problem.visible_to_staff_only)
chapter.visible_to_staff_only = True
modulestore().update_item(chapter, self.user_id)
problem = modulestore().get_item(problem.location.version_agnostic())
self.assertTrue(problem.visible_to_staff_only)
# unset on parent, retrieve child, verify unset
chapter = modulestore().get_item(chapter.location.version_agnostic())
chapter.fields['visible_to_staff_only'].delete_from(chapter)
modulestore().update_item(chapter, self.user_id)
problem = modulestore().get_item(problem.location.version_agnostic())
self.assertFalse(problem.visible_to_staff_only)
def test_dynamic_inheritance(self):
"""
Test inheritance for create_item with and without a parent pointer
"""
course_key = CourseLocator(org='testx', course='GreekHero', run="run", branch=BRANCH_NAME_DRAFT)
chapter = modulestore().get_item(BlockUsageLocator(course_key, 'chapter', 'chapter3'))
chapter.visible_to_staff_only = True
orphan_problem = modulestore().create_item(self.user_id, course_key, 'problem')
self.assertFalse(orphan_problem.visible_to_staff_only)
parented_problem = modulestore().create_child(self.user_id, chapter.location.version_agnostic(), 'problem')
# FIXME LMS-11376
# self.assertTrue(parented_problem.visible_to_staff_only)
orphan_problem = modulestore().create_xblock(chapter.runtime, course_key, 'problem')
self.assertFalse(orphan_problem.visible_to_staff_only)
parented_problem = modulestore().create_xblock(chapter.runtime, course_key, 'problem', parent_xblock=chapter)
# FIXME LMS-11376
# self.assertTrue(parented_problem.visible_to_staff_only)
class TestPublish(SplitModuleTest):
"""
......@@ -1732,7 +1782,7 @@ class TestSchema(SplitModuleTest):
)
#===========================================
# ===========================================
def modulestore():
"""
Mock the django dependent global modulestore function to disentangle tests from django
......
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