0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-12-01 11:41:20 +01:00

Support constructing StreamField with a block_lookup argument

This commit is contained in:
Matt Westcott 2024-06-17 18:38:17 +01:00 committed by Matt Westcott
parent 478c3cc2dc
commit 38fcf19168
2 changed files with 136 additions and 6 deletions

View File

@ -8,6 +8,7 @@ from django.utils.encoding import force_str
from django.utils.functional import cached_property
from wagtail.blocks import Block, BlockField, StreamBlock, StreamValue
from wagtail.blocks.definition_lookup import BlockDefinitionLookup
from wagtail.rich_text import (
RichTextMaxLengthValidator,
extract_references_from_rich_text,
@ -83,9 +84,18 @@ class Creator:
class StreamField(models.Field):
def __init__(self, block_types, use_json_field=True, **kwargs):
# use_json_field no longer has any effect but is recognised to support historical
# migrations
def __init__(self, block_types, use_json_field=True, block_lookup=None, **kwargs):
"""
Construct a StreamField.
:param block_types: Either a list of block types that are allowed in this StreamField
(as a list of tuples of block name and block instance) or a StreamBlock to use as
the top level block (as a block instance or class).
:param use_json_field: Ignored, but retained for compatibility with historical migrations.
:param block_lookup: Used in migrations to provide a more compact block definition -
see `wagtail.blocks.definition_lookup.BlockDefinitionLookup`. If passed, `block_types`
can contain integer indexes into this lookup table, in place of actual block instances.
"""
# extract kwargs that are to be passed on to the block, not handled by super
self.block_opts = {}
@ -98,22 +108,41 @@ class StreamField(models.Field):
# that the field and block have consistent definitions
self.block_opts["required"] = not kwargs.get("blank", False)
# Store the `block_types` argument to be handled in the `stream_block` property
# Store the `block_types` and `block_lookup` arguments to be handled in the `stream_block`
# property
self.block_types_arg = block_types
self.block_lookup = block_lookup
super().__init__(**kwargs)
@cached_property
def stream_block(self):
has_block_lookup = self.block_lookup is not None
if has_block_lookup:
lookup = BlockDefinitionLookup(self.block_lookup)
if isinstance(self.block_types_arg, Block):
# use the passed block as the top-level block
block = self.block_types_arg
elif isinstance(self.block_types_arg, int) and has_block_lookup:
# retrieve block from lookup table to use as the top-level block
block = lookup.get_block(self.block_types_arg)
elif isinstance(self.block_types_arg, type):
# block passed as a class - instantiate it
block = self.block_types_arg()
else:
# construct a top-level StreamBlock from the list of block types
block = StreamBlock(self.block_types_arg)
# construct a top-level StreamBlock from the list of block types.
# If an integer is found in place of a block instance, and block_lookup is
# provided, it will be replaced with the corresponding block definition.
child_blocks = []
for name, child_block in self.block_types_arg:
if isinstance(child_block, int) and has_block_lookup:
child_blocks.append((name, lookup.get_block(child_block)))
else:
child_blocks.append((name, child_block))
block = StreamBlock(child_blocks)
block.set_meta_options(self.block_opts)
return block

View File

@ -721,3 +721,104 @@ class TestGetBlockByContentPath(TestCase):
self.assertEqual(bound_block.value, "Barnaby Rudge")
bound_block = field.get_block_by_content_path(self.page.body, ["456", "999"])
self.assertIsNone(bound_block)
class TestConstructStreamFieldFromLookup(TestCase):
def test_construct_block_list_from_lookup(self):
field = StreamField(
[
("heading", 0),
("paragraph", 1),
("button", 3),
],
block_lookup=[
("wagtail.blocks.CharBlock", [], {"required": True}),
("wagtail.blocks.RichTextBlock", [], {}),
("wagtail.blocks.PageChooserBlock", [], {}),
(
"wagtail.blocks.StructBlock",
[
[
("page", 2),
("link_text", 0),
]
],
{},
),
],
)
stream_block = field.stream_block
self.assertIsInstance(stream_block, blocks.StreamBlock)
self.assertEqual(len(stream_block.child_blocks), 3)
heading_block = stream_block.child_blocks["heading"]
self.assertIsInstance(heading_block, blocks.CharBlock)
self.assertTrue(heading_block.required)
self.assertEqual(heading_block.name, "heading")
paragraph_block = stream_block.child_blocks["paragraph"]
self.assertIsInstance(paragraph_block, blocks.RichTextBlock)
self.assertEqual(paragraph_block.name, "paragraph")
button_block = stream_block.child_blocks["button"]
self.assertIsInstance(button_block, blocks.StructBlock)
self.assertEqual(button_block.name, "button")
self.assertEqual(len(button_block.child_blocks), 2)
page_block = button_block.child_blocks["page"]
self.assertIsInstance(page_block, blocks.PageChooserBlock)
link_text_block = button_block.child_blocks["link_text"]
self.assertIsInstance(link_text_block, blocks.CharBlock)
self.assertEqual(link_text_block.name, "link_text")
def test_construct_top_level_block_from_lookup(self):
field = StreamField(
4,
block_lookup=[
("wagtail.blocks.CharBlock", [], {"required": True}),
("wagtail.blocks.RichTextBlock", [], {}),
("wagtail.blocks.PageChooserBlock", [], {}),
(
"wagtail.blocks.StructBlock",
[
[
("page", 2),
("link_text", 0),
]
],
{},
),
(
"wagtail.blocks.StreamBlock",
[
[
("heading", 0),
("paragraph", 1),
("button", 3),
]
],
{},
),
],
)
stream_block = field.stream_block
self.assertIsInstance(stream_block, blocks.StreamBlock)
self.assertEqual(len(stream_block.child_blocks), 3)
heading_block = stream_block.child_blocks["heading"]
self.assertIsInstance(heading_block, blocks.CharBlock)
self.assertTrue(heading_block.required)
self.assertEqual(heading_block.name, "heading")
paragraph_block = stream_block.child_blocks["paragraph"]
self.assertIsInstance(paragraph_block, blocks.RichTextBlock)
self.assertEqual(paragraph_block.name, "paragraph")
button_block = stream_block.child_blocks["button"]
self.assertIsInstance(button_block, blocks.StructBlock)
self.assertEqual(button_block.name, "button")
self.assertEqual(len(button_block.child_blocks), 2)
page_block = button_block.child_blocks["page"]
self.assertIsInstance(page_block, blocks.PageChooserBlock)
link_text_block = button_block.child_blocks["link_text"]
self.assertIsInstance(link_text_block, blocks.CharBlock)
self.assertEqual(link_text_block.name, "link_text")