diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a112586e94..0e4ac80630 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -22,6 +22,7 @@ Changelog * Add a new switch input widget as an alternative to checkboxes (Karl Hobley) * Allow `{% pageurl %}` fallback to be a direct URL or an object with a `get_absolute_url` method (Andy Babic) * Add support for exporting redirects (Martin Sandström) + * Support slicing on StreamField / StreamBlock values (Matt Westcott) * Fix: StreamField required status is now consistently handled by the `blank` keyword argument (Matt Westcott) * Fix: Show 'required' asterisks for blocks inside required StreamFields (Matt Westcott) * Fix: Make image chooser "Select format" fields translatable (Helen Chapman, Thibaud Colas) diff --git a/docs/releases/2.13.rst b/docs/releases/2.13.rst index 2d7b49f1bd..7e22697882 100644 --- a/docs/releases/2.13.rst +++ b/docs/releases/2.13.rst @@ -45,6 +45,8 @@ Other features * Add a new switch input widget as an alternative to checkboxes (Karl Hobley) * Allow ``{% pageurl %}`` fallback to be a direct URL or an object with a ``get_absolute_url`` method (Andy Babic) * Add support for exporting redirects (Martin Sandström) +* Support slicing on StreamField / StreamBlock values (Matt Westcott) + Bug fixes ~~~~~~~~~ diff --git a/wagtail/core/blocks/stream_block.py b/wagtail/core/blocks/stream_block.py index ca1218bfb8..e96189f5fa 100644 --- a/wagtail/core/blocks/stream_block.py +++ b/wagtail/core/blocks/stream_block.py @@ -505,6 +505,10 @@ class StreamValue(MutableSequence): return StreamValue.StreamChild(block_def, value, id=block_id) def __getitem__(self, i): + if isinstance(i, slice): + start, stop, step = i.indices(len(self._bound_blocks)) + return [self[j] for j in range(start, stop, step)] + if self._bound_blocks[i] is None: raw_value = self._raw_data[i] self._prefetch_blocks(raw_value['type']) diff --git a/wagtail/core/tests/test_streamfield.py b/wagtail/core/tests/test_streamfield.py index 8dfb70858d..f8651e610b 100644 --- a/wagtail/core/tests/test_streamfield.py +++ b/wagtail/core/tests/test_streamfield.py @@ -27,6 +27,11 @@ class TestLazyStreamField(TestCase): self.no_image = StreamModel.objects.create(body=json.dumps([ {'type': 'text', 'value': 'foo'}])) self.nonjson_body = StreamModel.objects.create(body="

hello world

") + self.three_items = StreamModel.objects.create(body=json.dumps([ + {'type': 'text', 'value': 'foo'}, + {'type': 'image', 'value': self.image.pk}, + {'type': 'text', 'value': 'bar'}, + ])) def test_lazy_load(self): """ @@ -50,6 +55,29 @@ class TestLazyStreamField(TestCase): self.assertEqual(body[0].value, self.image) self.assertEqual(body[1].value, 'foo') + def test_slice(self): + with self.assertNumQueries(1): + instance = StreamModel.objects.get(pk=self.three_items.pk) + + with self.assertNumQueries(1): + # Access the image item from the stream. The image is fetched now + instance.body[1].value + + with self.assertNumQueries(0): + # taking a slice of a StreamValue should re-use already-fetched values + values = [block.value for block in instance.body[1:3]] + self.assertEqual(values, [self.image, 'bar']) + + with self.assertNumQueries(0): + # test slicing with negative indexing + values = [block.value for block in instance.body[-2:]] + self.assertEqual(values, [self.image, 'bar']) + + with self.assertNumQueries(0): + # test slicing with skips + values = [block.value for block in instance.body[0:3:2]] + self.assertEqual(values, ['foo', 'bar']) + def test_lazy_load_no_images(self): """ Getting a single item whose StreamField never accesses the database