From 4878f6e83cae5de48ca7391ce3fbad20c67d8bd4 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Wed, 27 Aug 2014 16:03:49 -0400 Subject: [PATCH] Implement edit info as mixin for split LMS-11183, LMS-11184 --- .../split_mongo/caching_descriptor_system.py | 104 ++++++++++++++++-- .../modulestore/split_mongo/split_draft.py | 8 ++ .../tests/test_mixed_modulestore.py | 3 + .../tests/test_split_modulestore.py | 3 +- 4 files changed, 107 insertions(+), 11 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py index 7c84f5e061..fee8f4ad2b 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py @@ -11,11 +11,12 @@ from ..exceptions import ItemNotFoundError from .split_mongo_kvs import SplitMongoKVS from fs.osfs import OSFS from .definition_lazy_loader import DefinitionLazyLoader +from xmodule.modulestore.edit_info import EditInfoRuntimeMixin log = logging.getLogger(__name__) -class CachingDescriptorSystem(MakoDescriptorSystem): +class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): """ A system that has a cache of a course version's json that it will use to load modules from, with a backup of calling to the underlying modulestore for more data. @@ -89,6 +90,19 @@ class CachingDescriptorSystem(MakoDescriptorSystem): run=course_info.get('run'), branch=course_info.get('branch'), ) + 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) + return new_item + + def get_module_data(self, block_id, course_key): + """ + Get block from module_data adding it to module_data if it's not already there but is in the structure + + Raises: + ItemNotFoundError if block is not in the structure + """ json_data = self.module_data.get(block_id) if json_data is None: # deeper than initial descendant fetch or doesn't exist @@ -97,9 +111,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem): if json_data is None: raise ItemNotFoundError(block_id) - 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) - return new_item + return json_data # xblock's runtime does not always pass enough contextual information to figure out # which named container (course x branch) or which parent is requesting an item. Because split allows @@ -181,12 +193,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ) edit_info = json_data.get('edit_info', {}) - module.edited_by = edit_info.get('edited_by') - module.edited_on = edit_info.get('edited_on') - module.subtree_edited_by = None # TODO - addressed with LMS-11183 - module.subtree_edited_on = None # TODO - addressed with LMS-11183 - module.published_by = None # TODO - addressed with LMS-11184 - module.published_date = None # TODO - addressed with LMS-11184 + module._edited_by = edit_info.get('edited_by') + module._edited_on = edit_info.get('edited_on') module.previous_version = edit_info.get('previous_version') module.update_version = edit_info.get('update_version') module.source_version = edit_info.get('source_version', None) @@ -199,3 +207,79 @@ class CachingDescriptorSystem(MakoDescriptorSystem): self.local_modules[block_locator] = module return module + + def get_edited_by(self, xblock): + """ + See :meth: cms.lib.xblock.runtime.EditInfoRuntimeMixin.get_edited_by + """ + return xblock._edited_by + + def get_edited_on(self, xblock): + """ + See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin + """ + return xblock._edited_on + + def get_subtree_edited_by(self, xblock): + """ + See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin + """ + if not hasattr(xblock, '_subtree_edited_by'): + json_data = self.module_data[xblock.location.block_id] + if '_subtree_edited_by' not in json_data.setdefault('edit_info', {}): + self._compute_subtree_edited_internal( + xblock.location.block_id, json_data, xblock.location.course_key + ) + setattr(xblock, '_subtree_edited_by', json_data['edit_info']['_subtree_edited_by']) + + return getattr(xblock, '_subtree_edited_by') + + def get_subtree_edited_on(self, xblock): + """ + See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin + """ + if not hasattr(xblock, '_subtree_edited_on'): + json_data = self.module_data[xblock.location.block_id] + if '_subtree_edited_on' not in json_data.setdefault('edit_info', {}): + self._compute_subtree_edited_internal( + xblock.location.block_id, json_data, xblock.location.course_key + ) + setattr(xblock, '_subtree_edited_on', json_data['edit_info']['_subtree_edited_on']) + + return getattr(xblock, '_subtree_edited_on') + + def get_published_by(self, xblock): + """ + See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin + """ + if not hasattr(xblock, '_published_by'): + self.modulestore.compute_published_info_internal(xblock) + + return getattr(xblock, '_published_by', None) + + def get_published_on(self, xblock): + """ + See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin + """ + if not hasattr(xblock, '_published_on'): + self.modulestore.compute_published_info_internal(xblock) + + return getattr(xblock, '_published_on', None) + + def _compute_subtree_edited_internal(self, block_id, json_data, course_key): + """ + Recurse the subtree finding the max edited_on date and its concomitant edited_by. Cache it + """ + max_date = json_data['edit_info']['edited_on'] + max_by = json_data['edit_info']['edited_by'] + + for child in json_data.get('fields', {}).get('children', []): + child_data = self.get_module_data(child, course_key) + if '_subtree_edited_on' not in json_data.setdefault('edit_info', {}): + self._compute_subtree_edited_internal(child, child_data) + if child_data['edit_info']['_subtree_edited_on'] > max_date: + max_date = child_data['edit_info']['_subtree_edited_on'] + max_by = child_data['edit_info']['_subtree_edited_by'] + + json_data['edit_info']['_subtree_edited_on'] = max_date + json_data['edit_info']['_subtree_edited_by'] = max_by diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py index d534e91ac3..4a935bd319 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py @@ -387,3 +387,11 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS return self._update_item_from_fields( user_id, course_key, block_type, block_id, partitioned_fields, None, allow_not_found=True, force=True ) + + def compute_published_info_internal(self, xblock): + """ + Get the published branch and find when it was published if it was. Cache the results in the xblock + """ + published_block = self._get_head(xblock, ModuleStoreEnum.BranchName.published) + setattr(xblock, '_published_by', published_block['edit_info']['edited_by']) + setattr(xblock, '_published_on', published_block['edit_info']['edited_on']) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py index 64831faea4..f1498ee63b 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py @@ -13,6 +13,8 @@ from uuid import uuid4 # before importing the module # TODO remove this import and the configuration -- xmodule should not depend on django! from django.conf import settings +from xmodule.modulestore.edit_info import EditInfoMixin + if not settings.configured: settings.configure() @@ -52,6 +54,7 @@ class TestMixedModuleStore(unittest.TestCase): 'default_class': DEFAULT_CLASS, 'fs_root': DATA_DIR, 'render_template': RENDER_TEMPLATE, + 'xblock_mixins': (EditInfoMixin,) } DOC_STORE_CONFIG = { 'host': HOST, diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py index cee7f08897..2ed87a592d 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py @@ -23,6 +23,7 @@ from xmodule.fields import Date, Timedelta from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore from xmodule.modulestore.tests.test_modulestore import check_has_course_method from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST +from xmodule.modulestore.edit_info import EditInfoMixin BRANCH_NAME_DRAFT = ModuleStoreEnum.BranchName.draft @@ -45,7 +46,7 @@ class SplitModuleTest(unittest.TestCase): modulestore_options = { 'default_class': 'xmodule.raw_module.RawDescriptor', 'fs_root': '', - 'xblock_mixins': (InheritanceMixin, XModuleMixin) + 'xblock_mixins': (InheritanceMixin, XModuleMixin, EditInfoMixin) } MODULESTORE = {