From 2cf79c386f814d6d6d34f8ec1fc20c2dca7e1aee Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 18 Mar 2015 14:11:29 -0400 Subject: [PATCH] Pass parent to xblock on load --- xblock/core.py | 5 ++--- xblock/field_data.py | 3 +++ xblock/mixins.py | 42 ++++++++++++++++++++++++++++++++++++- xblock/runtime.py | 6 +++--- xblock/test/test_runtime.py | 3 ++- 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/xblock/core.py b/xblock/core.py index 9121ab282..069a32363 100644 --- a/xblock/core.py +++ b/xblock/core.py @@ -136,7 +136,7 @@ def load_tagged_classes(cls, tag, fail_silently=True): if tag in class_._class_tags: yield name, class_ - def __init__(self, runtime, field_data=None, scope_ids=UNSET): + def __init__(self, runtime, field_data=None, scope_ids=UNSET, *args, **kwargs): """ Construct a new XBlock. @@ -153,13 +153,12 @@ def __init__(self, runtime, field_data=None, scope_ids=UNSET): scope_ids (:class:`.ScopeIds`): Identifiers needed to resolve scopes. - """ if scope_ids is UNSET: raise TypeError('scope_ids are required') # Provide backwards compatibility for external access through _field_data - super(XBlock, self).__init__(runtime=runtime, scope_ids=scope_ids, field_data=field_data) + super(XBlock, self).__init__(runtime=runtime, scope_ids=scope_ids, field_data=field_data, *args, **kwargs) def render(self, view, context=None): """Render `view` with this block's runtime and the supplied `context`""" diff --git a/xblock/field_data.py b/xblock/field_data.py index d4e971705..fbe4680e0 100644 --- a/xblock/field_data.py +++ b/xblock/field_data.py @@ -199,3 +199,6 @@ def has(self, block, name): def default(self, block, name): return self._source.default(block, name) + + def __repr__(self): + return "ReadOnlyFieldData({!r})".format(self._source) diff --git a/xblock/mixins.py b/xblock/mixins.py index a2ddadec5..04f282bbe 100644 --- a/xblock/mixins.py +++ b/xblock/mixins.py @@ -332,12 +332,19 @@ def __init__(self, **kwargs): # A cache of the parent block, retrieved from .parent self._parent_block = None self._parent_block_id = None + self._child_cache = {} + + for_parent = kwargs.pop('for_parent', None) + + if for_parent is not None: + self._parent_block = for_parent + self._parent_block_id = for_parent.scope_ids.usage_id super(HierarchyMixin, self).__init__(**kwargs) def get_parent(self): """Return the parent block of this block, or None if there isn't one.""" - if self._parent_block_id != self.parent: + if not self.has_cached_parent: if self.parent is not None: self._parent_block = self.runtime.get_block(self.parent) else: @@ -345,6 +352,39 @@ def get_parent(self): self._parent_block_id = self.parent return self._parent_block + @property + def has_cached_parent(self): + """Return whether this block has a cached parent block.""" + return self.parent is not None and self._parent_block_id == self.parent + + def get_child(self, usage_id): + """Return the child identified by ``usage_id``.""" + if usage_id in self._child_cache: + return self._child_cache[usage_id] + + child_block = self.runtime.get_block(usage_id, for_parent=self) + self._child_cache[usage_id] = child_block + return child_block + + def get_children(self, usage_id_filter=None): + """ + Return instantiated XBlocks for each of this blocks ``children``. + """ + if not self.has_children: + return [] + + return [ + self.get_child(usage_id) + for usage_id in self.children + if usage_id_filter is None or usage_id_filter(usage_id) + ] + + def clear_child_cache(self): + """ + Reset the cache of children stored on this XBlock. + """ + self._child_cache.clear() + class XmlSerializationMixin(ScopedStorageMixin): """ diff --git a/xblock/runtime.py b/xblock/runtime.py index c215ab28b..4a91239b9 100644 --- a/xblock/runtime.py +++ b/xblock/runtime.py @@ -123,7 +123,7 @@ def __init__(self, kvs, **kwargs): self._kvs = kvs def __repr__(self): - return "<{0.__class__.__name__} {0._kvs!r}>".format(self) + return "{0.__class__.__name__}({0._kvs!r})".format(self) def _getfield(self, block, name): """ @@ -635,7 +635,7 @@ def construct_xblock_from_class(self, cls, scope_ids, field_data=None, *args, ** *args, **kwargs ) - def get_block(self, usage_id): + def get_block(self, usage_id, for_parent=None): """ Create an XBlock instance in this runtime. @@ -647,7 +647,7 @@ def get_block(self, usage_id): except NoSuchDefinition: raise NoSuchUsage(repr(usage_id)) keys = ScopeIds(self.user_id, block_type, def_id, usage_id) - block = self.construct_xblock(block_type, keys) + block = self.construct_xblock(block_type, keys, for_parent=for_parent) return block def get_aside(self, aside_usage_id): diff --git a/xblock/test/test_runtime.py b/xblock/test/test_runtime.py index 99e6e5642..280a960c6 100644 --- a/xblock/test/test_runtime.py +++ b/xblock/test/test_runtime.py @@ -646,7 +646,8 @@ def test_basic(self): self.id_reader.get_block_type.assert_called_with(self.def_id) self.construct_block.assert_called_with( self.block_type, - ScopeIds(self.user_id, self.block_type, self.def_id, self.usage_id) + ScopeIds(self.user_id, self.block_type, self.def_id, self.usage_id), + for_parent=None, ) def test_missing_usage(self):