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):
field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access
descriptor.bind_for_student(
_preview_module_system(request, descriptor, field_data),
field_data
field_data,
request.user.id
)
return descriptor
......
......@@ -110,7 +110,7 @@ def get_test_system(course_id=SlashSeparatedCourseKey('org', 'course', 'run')):
# So, bind to the same one as the current descriptor.
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
......
......@@ -58,7 +58,7 @@ class LibraryContentTest(MixedSplitTestCase):
sub_module_system = get_test_system(course_id=self.course.location.course_key)
sub_module_system.get_module = get_module
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
module_system.get_module = get_module
......
......@@ -81,6 +81,7 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
self.module_system.descriptor_runtime = self.course._runtime # pylint: disable=protected-access
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.user_partition,
......@@ -90,14 +91,18 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
MockUserPartitionScheme()
)
],
user=Mock(username='ma', email='ma@edx.org', is_staff=False, is_active=True),
user=user,
course_id=self.course.id,
track_function=Mock(name='track_function'),
)
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.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
......
......@@ -17,8 +17,11 @@ from webob import Response
from webob.multidict import MultiDict
from xblock.core import XBlock, XBlockAside
from xblock.fields import Scope, Integer, Float, List, XBlockMixin, String, Dict, ScopeIds, Reference, \
ReferenceList, ReferenceValueDict
from xblock.fields import (
Scope, Integer, Float, List, XBlockMixin,
String, Dict, ScopeIds, Reference, ReferenceList,
ReferenceValueDict, UserScope
)
from xblock.fragment import Fragment
from xblock.runtime import Runtime, IdReader, IdGenerator
from xmodule.fields import RelativeTime
......@@ -281,6 +284,8 @@ class XModuleMixin(XBlockMixin):
def __init__(self, *args, **kwargs):
self.xmodule_runtime = None
self._child_instances = None
super(XModuleMixin, self).__init__(*args, **kwargs)
@property
......@@ -410,7 +415,7 @@ class XModuleMixin(XBlockMixin):
if not self.has_children:
return []
if getattr(self, '_child_instances', None) is None:
if self._child_instances is None:
self._child_instances = [] # pylint: disable=attribute-defined-outside-init
for child_loc in self.children:
# Skip if it doesn't satisfy the filter function
......@@ -525,15 +530,39 @@ class XModuleMixin(XBlockMixin):
"""
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.
Arguments:
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
user_id: The user_id to set in scope_ids
"""
# 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._field_data = field_data
......
......@@ -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
module.descriptor.bind_for_student(
inner_system,
LmsFieldData(module.descriptor._field_data, inner_student_data) # pylint: disable=protected-access
)
module.descriptor.scope_ids = (
module.descriptor.scope_ids._replace(user_id=real_user.id) # pylint: disable=protected-access
LmsFieldData(module.descriptor._field_data, inner_student_data), # pylint: disable=protected-access
real_user.id,
)
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
......@@ -688,8 +686,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
request_token=request_token
)
descriptor.bind_for_student(system, field_data) # pylint: disable=protected-access
descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id) # pylint: disable=protected-access
descriptor.bind_for_student(system, field_data, user.id) # pylint: disable=protected-access
return descriptor
......
......@@ -921,7 +921,7 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase):
location = course_id.make_usage_key('dummy_category', 'dummy_name')
descriptor = Mock(
spec=xblock_class,
_field_data=Mock(spec=FieldData),
_field_data=Mock(spec=FieldData, name='field_data'),
location=location,
static_asset_path=None,
_runtime=Mock(
......@@ -931,7 +931,10 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase):
name='runtime',
),
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
# 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