From 37a5e8dabcccd83f8d9d9e00181d539cc8acbebf Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Mon, 5 Aug 2024 15:38:10 +0100 Subject: [PATCH] Handle `child_block` being passed as a kwarg in ListBlock.deconstruct_with_lookup (#12208) Fixes #12202 --- CHANGELOG.txt | 2 +- docs/releases/6.2.1.md | 2 +- wagtail/blocks/list_block.py | 30 +++++++++++++++++--------- wagtail/tests/test_streamfield.py | 35 +++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8f2bc2eb08..7c6d62a6fe 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,7 +4,7 @@ Changelog 6.2.1 (xx.xx.20xx) - IN DEVELOPMENT ~~~~~~~~~~~~~~~~~~ - * ... + * Fix: Handle `child_block` being passed as a kwarg in ListBlock migrations (Matt Westcott) 6.2 (01.08.2024) diff --git a/docs/releases/6.2.1.md b/docs/releases/6.2.1.md index 9f74ba54f7..502dc0e679 100644 --- a/docs/releases/6.2.1.md +++ b/docs/releases/6.2.1.md @@ -14,4 +14,4 @@ depth: 1 ### Bug fixes - * ... + * Handle `child_block` being passed as a kwarg in ListBlock migrations (Matt Westcott) diff --git a/wagtail/blocks/list_block.py b/wagtail/blocks/list_block.py index 152dda0f51..ebc99faf69 100644 --- a/wagtail/blocks/list_block.py +++ b/wagtail/blocks/list_block.py @@ -159,11 +159,16 @@ class ListBlock(Block): @classmethod def construct_from_lookup(cls, lookup, *args, **kwargs): - if getattr(cls.__init__, "has_child_block_arg", False) and isinstance( - args[0], int - ): - child_block = lookup.get_block(args[0]) - args = (child_block, *args[1:]) + if getattr(cls.__init__, "has_child_block_arg", False): + if args and isinstance(args[0], int): + child_block = lookup.get_block(args[0]) + args = (child_block, *args[1:]) + else: + child_block_kwarg = kwargs.get("child_block") + if isinstance(child_block_kwarg, int): + child_block = lookup.get_block(child_block_kwarg) + kwargs["child_block"] = child_block + return cls(*args, **kwargs) def value_from_datadict(self, data, files, prefix): @@ -413,11 +418,16 @@ class ListBlock(Block): def deconstruct_with_lookup(self, lookup): path, args, kwargs = super().deconstruct_with_lookup(lookup) - if getattr(self.__init__, "has_child_block_arg", False) and isinstance( - args[0], Block - ): - block_id = lookup.add_block(args[0]) - args = (block_id, *args[1:]) + if getattr(self.__init__, "has_child_block_arg", False): + if args and isinstance(args[0], Block): + block_id = lookup.add_block(args[0]) + args = (block_id, *args[1:]) + else: + child_block = kwargs.get("child_block") + if isinstance(child_block, Block): + block_id = lookup.add_block(child_block) + kwargs["child_block"] = block_id + return path, args, kwargs class Meta: diff --git a/wagtail/tests/test_streamfield.py b/wagtail/tests/test_streamfield.py index 727847a678..8ea629f678 100644 --- a/wagtail/tests/test_streamfield.py +++ b/wagtail/tests/test_streamfield.py @@ -914,6 +914,41 @@ class TestDeconstructStreamFieldWithLookup(TestCase): }, ) + def test_deconstruct_with_listblock_with_child_block_kwarg(self): + field = StreamField( + [ + ("heading", blocks.CharBlock(required=True)), + ( + "bullets", + blocks.ListBlock(child_block=blocks.CharBlock(required=True)), + ), + ], + blank=True, + ) + field.set_attributes_from_name("body") + name, path, args, kwargs = field.deconstruct() + self.assertEqual(name, "body") + self.assertEqual(path, "wagtail.fields.StreamField") + self.assertEqual( + args, + [ + [ + ("heading", 0), + ("bullets", 1), + ] + ], + ) + self.assertEqual( + kwargs, + { + "blank": True, + "block_lookup": { + 0: ("wagtail.blocks.CharBlock", (), {"required": True}), + 1: ("wagtail.blocks.ListBlock", (), {"child_block": 0}), + }, + }, + ) + def test_deconstruct_with_listblock_subclass(self): # See https://github.com/wagtail/wagtail/issues/12164 - unlike StructBlock and StreamBlock, # ListBlock's deconstruct method doesn't reduce subclasses to the base ListBlock class.