mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-28 02:24:48 +00:00
Implement initial version of Block.bulk_to_python (with jaroel)
This prevents n+ queries for n blocks of a specific type.
This commit is contained in:
parent
74d13822d5
commit
7d7509aee5
5 changed files with 62 additions and 1 deletions
|
|
@ -12,6 +12,7 @@ Changelog
|
|||
* Form builder now validates against multiple fields with the same name (Richard McMillan)
|
||||
* The 'choices' field on the form builder no longer has a maximum length (Johannes Spielmann)
|
||||
* The wagtailimages.Filter model has been removed, and converted to a Python class instead (Gagaro)
|
||||
* Multiple ChooserBlocks inside a StreamField are now prefetched in bulk, for improved performance (Michael van Tellingen, Roel Bruggink, Matt Westcott)
|
||||
* Fix: Email templates and document uploader now support custom `STATICFILES_STORAGE` (Jonny Scholes)
|
||||
* Fix: Removed alignment options (deprecated in HTML and not rendered by Wagtail) from `TableBlock` context menu (Moritz Pfeiffer)
|
||||
* Fix: Fixed incorrect CSS path on ModelAdmin's "choose a parent page" view
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ Minor features
|
|||
* Form builder now validates against multiple fields with the same name (Richard McMillan)
|
||||
* The 'choices' field on the form builder no longer has a maximum length (Johannes Spielmann)
|
||||
* The wagtailimages.Filter model has been removed, and converted to a Python class instead (Gagaro)
|
||||
* Multiple ChooserBlocks inside a StreamField are now prefetched in bulk, for improved performance (Michael van Tellingen, Roel Bruggink, Matt Westcott)
|
||||
|
||||
|
||||
Bug fixes
|
||||
|
|
|
|||
|
|
@ -399,6 +399,16 @@ class ChooserBlock(FieldBlock):
|
|||
except self.target_model.DoesNotExist:
|
||||
return None
|
||||
|
||||
def bulk_to_python(self, values):
|
||||
"""Return the model instances for the given list of primary keys.
|
||||
|
||||
The instances must be returned in the same order as the values and keep None values.
|
||||
"""
|
||||
initial = {key: None for key in values}
|
||||
objects = self.target_model.objects.in_bulk(values)
|
||||
initial.update(objects)
|
||||
return [initial[id] for id in values] # Keeps the ordering the same as in values.
|
||||
|
||||
def get_prep_value(self, value):
|
||||
# the native value (a model instance or None) should serialise to a PK or None
|
||||
if value is None:
|
||||
|
|
|
|||
|
|
@ -308,7 +308,11 @@ class StreamValue(collections.Sequence):
|
|||
raw_value = self.stream_data[i]
|
||||
type_name = raw_value['type']
|
||||
child_block = self.stream_block.child_blocks[type_name]
|
||||
value = child_block.to_python(raw_value['value'])
|
||||
if hasattr(child_block, 'bulk_to_python'):
|
||||
self._prefetch_blocks(type_name, child_block)
|
||||
return self._bound_blocks[i]
|
||||
else:
|
||||
value = child_block.to_python(raw_value['value'])
|
||||
else:
|
||||
type_name, value = self.stream_data[i]
|
||||
child_block = self.stream_block.child_blocks[type_name]
|
||||
|
|
@ -317,6 +321,21 @@ class StreamValue(collections.Sequence):
|
|||
|
||||
return self._bound_blocks[i]
|
||||
|
||||
def _prefetch_blocks(self, type_name, child_block):
|
||||
"""Prefetch all child blocks for the given `type_name` using the
|
||||
given `child_blocks`.
|
||||
|
||||
This prevents n queries for n blocks of a specific type.
|
||||
"""
|
||||
raw_values = collections.OrderedDict(
|
||||
(i, item['value']) for i, item in enumerate(self.stream_data)
|
||||
if item['type'] == type_name
|
||||
)
|
||||
converted_values = child_block.bulk_to_python(raw_values.values())
|
||||
|
||||
for i, value in zip(raw_values.keys(), converted_values):
|
||||
self._bound_blocks[i] = StreamValue.StreamChild(child_block, value)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.stream_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,36 @@ class TestLazyStreamField(TestCase):
|
|||
with self.assertNumQueries(0):
|
||||
instances_lookup[self.no_image.pk].body[0]
|
||||
|
||||
def test_lazy_load_queryset_bulk(self):
|
||||
"""
|
||||
Ensure that lazy loading StreamField works when gotten as part of a
|
||||
queryset list
|
||||
"""
|
||||
file_obj = get_test_image_file()
|
||||
image_1 = Image.objects.create(title='Test image 1', file=file_obj)
|
||||
image_3 = Image.objects.create(title='Test image 3', file=file_obj)
|
||||
|
||||
with_image = StreamModel.objects.create(body=json.dumps([
|
||||
{'type': 'image', 'value': image_1.pk},
|
||||
{'type': 'image', 'value': None},
|
||||
{'type': 'image', 'value': image_3.pk},
|
||||
{'type': 'text', 'value': 'foo'}]))
|
||||
|
||||
with self.assertNumQueries(1):
|
||||
instance = StreamModel.objects.get(pk=with_image.pk)
|
||||
|
||||
# Prefetch all image blocks
|
||||
with self.assertNumQueries(1):
|
||||
instance.body[0]
|
||||
|
||||
# 1. Further image block access should not execute any db lookups
|
||||
# 2. The blank block '1' should be None.
|
||||
# 3. The values should be in the original order.
|
||||
with self.assertNumQueries(0):
|
||||
assert instance.body[0].value.title == 'Test image 1'
|
||||
assert instance.body[1].value is None
|
||||
assert instance.body[2].value.title == 'Test image 3'
|
||||
|
||||
|
||||
class TestSystemCheck(TestCase):
|
||||
def tearDown(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue