diff --git a/xmodule/modulestore/split_mongo/caching_descriptor_system.py b/xmodule/modulestore/split_mongo/caching_descriptor_system.py index f6b5cdd227..eb6f43f266 100644 --- a/xmodule/modulestore/split_mongo/caching_descriptor_system.py +++ b/xmodule/modulestore/split_mongo/caching_descriptor_system.py @@ -218,10 +218,15 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): # li Services are objects implementing arbitrary other interfaces. """ # Implement field data service: - if service_name == "field-data": + if service_name in ('field-data', 'field-data-unbound'): + if hasattr(block, "_bound_field_data") and service_name != "field-data-unbound": + # Return the user-specific wrapped field data that gets set onto the block during XModule.bind_for_user + # This complexity is due to XModule heritage. If we didn't load the block as one step, then sometimes + # "rebind" it to be user-specific as a later step, we could load the field data and overrides at once. + return block._bound_field_data # pylint: disable=protected-access if block.scope_ids not in self.block_field_datas: try: - self.block_field_datas[block.scope_ids] = self._init_field_data_for_block(block) + self._init_field_data_for_block(block) except: # Don't try again pointlessly every time another field is accessed self.block_field_datas[block.scope_ids] = None @@ -295,9 +300,17 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): # li else: field_data = KvsFieldData(kvs) + # Save the field_data now, because some of the wrappers will try to read fields from the block while they + # determine if they want to wrap the field_data or not. + self.block_field_datas[block.scope_ids] = field_data + # Now add in any wrappers that want to be added: for wrapper in self.modulestore.xblock_field_data_wrappers: - field_data = wrapper(block, field_data) - return field_data + self.block_field_datas[block.scope_ids] = wrapper(block, self.block_field_datas[block.scope_ids]) + + # Note: user-specific wrappers like LmsFieldData or OverrideFieldData do not get set here. They get set later + # during bind_for_student(), which wraps the field-data and sets block._bound_field_data. Calling + # runtime.service(block, "field-data") or block._field_data (deprecated) will load block._bound_field_data if + # the block has been bound to a user, otherwise it returns the wrapped field data we just created above. def get_edited_by(self, xblock): """ diff --git a/xmodule/x_module.py b/xmodule/x_module.py index 5478ccc45d..f61dc227ad 100644 --- a/xmodule/x_module.py +++ b/xmodule/x_module.py @@ -10,7 +10,6 @@ from functools import partial import yaml from django.conf import settings -from lazy import lazy from lxml import etree from opaque_keys.edx.asides import AsideDefinitionKeyV2, AsideUsageKeyV2 from opaque_keys.edx.keys import UsageKey @@ -417,13 +416,12 @@ class XModuleMixin(XModuleFields, XBlock): self.save() return self._field_data._kvs # pylint: disable=protected-access - @lazy def _unwrapped_field_data(self): """ - This property hold the value _field_data here before we wrap it in - the LmsFieldData or OverrideFieldData classes. + This property gets the field-data service before we wrap it in user-specifc wrappers during bind_for_student, + e.g. the LmsFieldData or OverrideFieldData classes. """ - return self._field_data + return self.runtime.service(self, 'field-data-unbound') def add_aside(self, aside): """ @@ -653,12 +651,13 @@ class XModuleMixin(XModuleFields, XBlock): if wrappers is None: wrappers = [] - - wrapped_field_data = self._unwrapped_field_data - for wrapper in wrappers: - wrapped_field_data = wrapper(wrapped_field_data) - - self._field_data = wrapped_field_data + if wrappers: + # Put user-specific wrappers around the field-data service for this block. + # Note that these are different from modulestore.xblock_field_data_wrappers, which are not user-specific. + wrapped_field_data = self.runtime.service(self, 'field-data-unbound') + for wrapper in wrappers: + wrapped_field_data = wrapper(wrapped_field_data) + self._bound_field_data = wrapped_field_data @property def non_editable_metadata_fields(self):