Commit de23d6de by Calen Pennington

Clear cached children when binding an XModuleDescriptor/XBlock to a particular user

parent 9379fd27
...@@ -213,7 +213,8 @@ def _load_preview_module(request, descriptor): ...@@ -213,7 +213,8 @@ def _load_preview_module(request, descriptor):
field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access
descriptor.bind_for_student( descriptor.bind_for_student(
_preview_module_system(request, descriptor, field_data), _preview_module_system(request, descriptor, field_data),
field_data field_data,
request.user.id
) )
return descriptor return descriptor
......
...@@ -110,7 +110,7 @@ def get_test_system(course_id=SlashSeparatedCourseKey('org', 'course', 'run')): ...@@ -110,7 +110,7 @@ def get_test_system(course_id=SlashSeparatedCourseKey('org', 'course', 'run')):
# So, bind to the same one as the current descriptor. # So, bind to the same one as the current descriptor.
module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access
descriptor.bind_for_student(module_system, descriptor._field_data) descriptor.bind_for_student(module_system, descriptor._field_data, user.id)
return descriptor return descriptor
......
...@@ -58,7 +58,7 @@ class LibraryContentTest(MixedSplitTestCase): ...@@ -58,7 +58,7 @@ class LibraryContentTest(MixedSplitTestCase):
sub_module_system = get_test_system(course_id=self.course.location.course_key) sub_module_system = get_test_system(course_id=self.course.location.course_key)
sub_module_system.get_module = get_module sub_module_system.get_module = get_module
sub_module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access sub_module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access
descriptor.bind_for_student(sub_module_system, descriptor._field_data) # pylint: disable=protected-access descriptor.bind_for_student(sub_module_system, descriptor._field_data, self.user_id) # pylint: disable=protected-access
return descriptor return descriptor
module_system.get_module = get_module module_system.get_module = get_module
......
...@@ -81,6 +81,7 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase): ...@@ -81,6 +81,7 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
self.module_system.descriptor_runtime = self.course._runtime # pylint: disable=protected-access self.module_system.descriptor_runtime = self.course._runtime # pylint: disable=protected-access
self.course.runtime.export_fs = MemoryFS() self.course.runtime.export_fs = MemoryFS()
user = Mock(username='ma', email='ma@edx.org', is_staff=False, is_active=True)
self.partitions_service = StaticPartitionService( self.partitions_service = StaticPartitionService(
[ [
self.user_partition, self.user_partition,
...@@ -90,14 +91,18 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase): ...@@ -90,14 +91,18 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
MockUserPartitionScheme() MockUserPartitionScheme()
) )
], ],
user=Mock(username='ma', email='ma@edx.org', is_staff=False, is_active=True), user=user,
course_id=self.course.id, course_id=self.course.id,
track_function=Mock(name='track_function'), track_function=Mock(name='track_function'),
) )
self.module_system._services['partitions'] = self.partitions_service # pylint: disable=protected-access self.module_system._services['partitions'] = self.partitions_service # pylint: disable=protected-access
self.split_test_module = self.course_sequence.get_children()[0] self.split_test_module = self.course_sequence.get_children()[0]
self.split_test_module.bind_for_student(self.module_system, self.split_test_module._field_data) # pylint: disable=protected-access self.split_test_module.bind_for_student(
self.module_system,
self.split_test_module._field_data, # pylint: disable=protected-access
user.id
)
@ddt.ddt @ddt.ddt
......
...@@ -17,8 +17,11 @@ from webob import Response ...@@ -17,8 +17,11 @@ from webob import Response
from webob.multidict import MultiDict from webob.multidict import MultiDict
from xblock.core import XBlock, XBlockAside from xblock.core import XBlock, XBlockAside
from xblock.fields import Scope, Integer, Float, List, XBlockMixin, String, Dict, ScopeIds, Reference, \ from xblock.fields import (
ReferenceList, ReferenceValueDict Scope, Integer, Float, List, XBlockMixin,
String, Dict, ScopeIds, Reference, ReferenceList,
ReferenceValueDict, UserScope
)
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xblock.runtime import Runtime, IdReader, IdGenerator from xblock.runtime import Runtime, IdReader, IdGenerator
from xmodule.fields import RelativeTime from xmodule.fields import RelativeTime
...@@ -281,6 +284,8 @@ class XModuleMixin(XBlockMixin): ...@@ -281,6 +284,8 @@ class XModuleMixin(XBlockMixin):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.xmodule_runtime = None self.xmodule_runtime = None
self._child_instances = None
super(XModuleMixin, self).__init__(*args, **kwargs) super(XModuleMixin, self).__init__(*args, **kwargs)
@property @property
...@@ -410,7 +415,7 @@ class XModuleMixin(XBlockMixin): ...@@ -410,7 +415,7 @@ class XModuleMixin(XBlockMixin):
if not self.has_children: if not self.has_children:
return [] return []
if getattr(self, '_child_instances', None) is None: if self._child_instances is None:
self._child_instances = [] # pylint: disable=attribute-defined-outside-init self._child_instances = [] # pylint: disable=attribute-defined-outside-init
for child_loc in self.children: for child_loc in self.children:
# Skip if it doesn't satisfy the filter function # Skip if it doesn't satisfy the filter function
...@@ -525,15 +530,39 @@ class XModuleMixin(XBlockMixin): ...@@ -525,15 +530,39 @@ class XModuleMixin(XBlockMixin):
""" """
return None return None
def bind_for_student(self, xmodule_runtime, field_data): def bind_for_student(self, xmodule_runtime, field_data, user_id):
""" """
Set up this XBlock to act as an XModule instead of an XModuleDescriptor. Set up this XBlock to act as an XModule instead of an XModuleDescriptor.
Arguments: Arguments:
xmodule_runtime (:class:`ModuleSystem'): the runtime to use when accessing student facing methods xmodule_runtime (:class:`ModuleSystem'): the runtime to use when accessing student facing methods
field_data (:class:`FieldData`): The :class:`FieldData` to use for all subsequent data access field_data (:class:`FieldData`): The :class:`FieldData` to use for all subsequent data access
user_id: The user_id to set in scope_ids
""" """
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
# Skip rebinding if we're already bound a user, and it's this user.
if self.scope_ids.user_id is not None and user_id == self.scope_ids.user_id:
return
# If we are switching users mid-request, save the data from the old user.
self.save()
# Update scope_ids to point to the new user.
self.scope_ids = self.scope_ids._replace(user_id=user_id)
# Clear out any cached instantiated children.
self._child_instances = None
# Clear out any cached field data scoped to the old user.
for field in self.fields.values():
if field.scope in (Scope.parent, Scope.children):
continue
if field.scope.user == UserScope.ONE:
field._del_cached_value(self)
# Set the new xmodule_runtime and field_data (which are user-specific)
self.xmodule_runtime = xmodule_runtime self.xmodule_runtime = xmodule_runtime
self._field_data = field_data self._field_data = field_data
......
...@@ -497,10 +497,8 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -497,10 +497,8 @@ def get_module_system_for_user(user, field_data_cache,
# rebinds module to a different student. We'll change system, student_data, and scope_ids # rebinds module to a different student. We'll change system, student_data, and scope_ids
module.descriptor.bind_for_student( module.descriptor.bind_for_student(
inner_system, inner_system,
LmsFieldData(module.descriptor._field_data, inner_student_data) # pylint: disable=protected-access LmsFieldData(module.descriptor._field_data, inner_student_data), # pylint: disable=protected-access
) real_user.id,
module.descriptor.scope_ids = (
module.descriptor.scope_ids._replace(user_id=real_user.id) # pylint: disable=protected-access
) )
module.scope_ids = module.descriptor.scope_ids # this is needed b/c NamedTuples are immutable module.scope_ids = module.descriptor.scope_ids # this is needed b/c NamedTuples are immutable
# now bind the module to the new ModuleSystem instance and vice-versa # now bind the module to the new ModuleSystem instance and vice-versa
...@@ -688,8 +686,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -688,8 +686,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
request_token=request_token request_token=request_token
) )
descriptor.bind_for_student(system, field_data) # pylint: disable=protected-access descriptor.bind_for_student(system, field_data, user.id) # pylint: disable=protected-access
descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id) # pylint: disable=protected-access
return descriptor return descriptor
......
...@@ -921,7 +921,7 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -921,7 +921,7 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase):
location = course_id.make_usage_key('dummy_category', 'dummy_name') location = course_id.make_usage_key('dummy_category', 'dummy_name')
descriptor = Mock( descriptor = Mock(
spec=xblock_class, spec=xblock_class,
_field_data=Mock(spec=FieldData), _field_data=Mock(spec=FieldData, name='field_data'),
location=location, location=location,
static_asset_path=None, static_asset_path=None,
_runtime=Mock( _runtime=Mock(
...@@ -931,7 +931,10 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -931,7 +931,10 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase):
name='runtime', name='runtime',
), ),
scope_ids=Mock(spec=ScopeIds), scope_ids=Mock(spec=ScopeIds),
name='descriptor' name='descriptor',
_field_data_cache={},
_dirty_fields={},
fields={},
) )
descriptor.runtime = CombinedSystem(descriptor._runtime, None) # pylint: disable=protected-access descriptor.runtime = CombinedSystem(descriptor._runtime, None) # pylint: disable=protected-access
# Use the xblock_class's bind_for_student method # Use the xblock_class's bind_for_student method
......
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