kopia lustrzana https://github.com/wagtail/wagtail
Implement bulk_to_python on StreamBlock
rodzic
47faf79308
commit
56b2d86e9a
|
@ -252,6 +252,57 @@ class BaseStreamBlock(Block):
|
|||
if child_data['type'] in self.child_blocks
|
||||
], is_lazy=True)
|
||||
|
||||
def bulk_to_python(self, values):
|
||||
# 'values' is a list of streams, each stream being a list of dicts with 'type', 'value' and
|
||||
# optionally 'id'.
|
||||
# We will iterate over these streams, constructing:
|
||||
# 1) a set of per-child-block lists ('child_inputs'), to be sent to each child block's
|
||||
# bulk_to_python method in turn (giving us 'child_outputs')
|
||||
# 2) a 'block map' of each stream, telling us the type and id of each block and the index we
|
||||
# need to look up in the corresponding child_outputs list to obtain its final value
|
||||
|
||||
child_inputs = defaultdict(list)
|
||||
block_maps = []
|
||||
|
||||
for stream in values:
|
||||
block_map = []
|
||||
for block_dict in stream:
|
||||
block_type = block_dict['type']
|
||||
|
||||
if block_type not in self.child_blocks:
|
||||
# skip any blocks with an unrecognised type
|
||||
continue
|
||||
|
||||
child_input_list = child_inputs[block_type]
|
||||
child_index = len(child_input_list)
|
||||
child_input_list.append(block_dict['value'])
|
||||
block_map.append(
|
||||
(block_type, block_dict.get('id'), child_index)
|
||||
)
|
||||
|
||||
block_maps.append(block_map)
|
||||
|
||||
# run each list in child_inputs through the relevant block's bulk_to_python
|
||||
# to obtain child_outputs
|
||||
child_outputs = {
|
||||
block_type: self.child_blocks[block_type].bulk_to_python(child_input_list)
|
||||
for block_type, child_input_list in child_inputs.items()
|
||||
}
|
||||
|
||||
# for each stream, go through the block map, picking out the appropriately-indexed
|
||||
# value from the relevant list in child_outputs
|
||||
return [
|
||||
StreamValue(
|
||||
self,
|
||||
[
|
||||
(block_type, child_outputs[block_type][child_index], id)
|
||||
for block_type, id, child_index in block_map
|
||||
],
|
||||
is_lazy=False
|
||||
)
|
||||
for block_map in block_maps
|
||||
]
|
||||
|
||||
def get_prep_value(self, value):
|
||||
if not value:
|
||||
# Falsy values (including None, empty string, empty list, and
|
||||
|
|
|
@ -3327,6 +3327,62 @@ class TestStructBlockWithFixtures(TestCase):
|
|||
])
|
||||
|
||||
|
||||
class TestStreamBlockWithFixtures(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_bulk_to_python(self):
|
||||
stream_block = blocks.StreamBlock([
|
||||
('page', blocks.PageChooserBlock()),
|
||||
('heading', blocks.CharBlock()),
|
||||
])
|
||||
|
||||
# The naive implementation of bulk_to_python (calling to_python on each item) would perform
|
||||
# NO queries, as StreamBlock.to_python returns a lazy StreamValue that only starts calling
|
||||
# to_python on its children (and thus triggering DB queries) when its items are accessed.
|
||||
# This is a good thing for a standalone to_python call, because loading a model instance
|
||||
# with a StreamField in it will immediately call StreamField.to_python which in turn calls
|
||||
# to_python on the top-level StreamBlock, and we really don't want
|
||||
# SomeModelWithAStreamField.objects.get(id=1) to immediately trigger a cascading fetch of
|
||||
# all objects referenced in the StreamField.
|
||||
#
|
||||
# However, for bulk_to_python that's bad, as it means each stream in the list would end up
|
||||
# doing its own object lookups in isolation, missing the opportunity to group them together
|
||||
# into a single call to the child block's bulk_to_python. Therefore, the ideal outcome is
|
||||
# that we perform one query now (covering all PageChooserBlocks across all streams),
|
||||
# returning a list of non-lazy StreamValues.
|
||||
|
||||
with self.assertNumQueries(1):
|
||||
results = stream_block.bulk_to_python([
|
||||
[{'type': 'heading', 'value': 'interesting pages'}, {'type': 'page', 'value': 2}, {'type': 'page', 'value': 3}],
|
||||
[{'type': 'heading', 'value': 'pages written by dogs'}, {'type': 'woof', 'value': 'woof woof'}],
|
||||
[{'type': 'heading', 'value': 'boring pages'}, {'type': 'page', 'value': 4}],
|
||||
])
|
||||
|
||||
# If bulk_to_python has indeed given us non-lazy StreamValues, then no further queries
|
||||
# should be performed when iterating over its child blocks.
|
||||
with self.assertNumQueries(0):
|
||||
block_types = [
|
||||
[block.block_type for block in stream]
|
||||
for stream in results
|
||||
]
|
||||
self.assertEqual(block_types, [
|
||||
['heading', 'page', 'page'],
|
||||
['heading'],
|
||||
['heading', 'page'],
|
||||
])
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
block_values = [
|
||||
[block.value for block in stream]
|
||||
for stream in results
|
||||
]
|
||||
self.assertEqual(block_values, [
|
||||
['interesting pages', Page.objects.get(id=2), Page.objects.get(id=3)],
|
||||
['pages written by dogs'],
|
||||
['boring pages', Page.objects.get(id=4)],
|
||||
])
|
||||
|
||||
|
||||
class TestPageChooserBlock(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue