kopia lustrzana https://github.com/wagtail/wagtail
Prevent deconstruct_with_lookup from breaking on ListBlock subclasses with custom constructors
Fixes #12164pull/12173/head
rodzic
42c566b19b
commit
4bed63dbb7
|
@ -151,11 +151,20 @@ class ListBlock(Block):
|
|||
# Default to a list consisting of one empty (i.e. default-valued) child item
|
||||
self.meta.default = [self.child_block.get_default()]
|
||||
|
||||
# If a subclass of ListBlock overrides __init__, we cannot assume that the first argument is
|
||||
# the child block, and thus we cannot rely on the conversion applied in construct_from_lookup /
|
||||
# deconstruct_with_lookup to be valid. We set a flag attribute on the __init__ method so that
|
||||
# we can spot this case.
|
||||
__init__.has_child_block_arg = True
|
||||
|
||||
@classmethod
|
||||
def construct_from_lookup(cls, lookup, child_block, **kwargs):
|
||||
if isinstance(child_block, int):
|
||||
child_block = lookup.get_block(child_block)
|
||||
return cls(child_block, **kwargs)
|
||||
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:])
|
||||
return cls(*args, **kwargs)
|
||||
|
||||
def value_from_datadict(self, data, files, prefix):
|
||||
count = int(data["%s-count" % prefix])
|
||||
|
@ -404,8 +413,11 @@ class ListBlock(Block):
|
|||
|
||||
def deconstruct_with_lookup(self, lookup):
|
||||
path, args, kwargs = super().deconstruct_with_lookup(lookup)
|
||||
if isinstance(args[0], Block):
|
||||
args = (lookup.add_block(args[0]),)
|
||||
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:])
|
||||
return path, args, kwargs
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -824,6 +824,13 @@ class TestConstructStreamFieldFromLookup(TestCase):
|
|||
self.assertEqual(link_text_block.name, "link_text")
|
||||
|
||||
|
||||
# Used by TestDeconstructStreamFieldWithLookup.test_deconstruct_with_listblock_subclass -
|
||||
# needs to be a module-level definition so that the path returned from deconstruct is valid
|
||||
class BulletListBlock(blocks.ListBlock):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(blocks.CharBlock(required=True), **kwargs)
|
||||
|
||||
|
||||
class TestDeconstructStreamFieldWithLookup(TestCase):
|
||||
def test_deconstruct(self):
|
||||
class ButtonBlock(blocks.StructBlock):
|
||||
|
@ -874,3 +881,73 @@ class TestDeconstructStreamFieldWithLookup(TestCase):
|
|||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_deconstruct_with_listblock(self):
|
||||
field = StreamField(
|
||||
[
|
||||
("heading", blocks.CharBlock(required=True)),
|
||||
("bullets", blocks.ListBlock(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", (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.
|
||||
# Therefore, if a ListBlock subclass defines its own __init__ method with an incompatible
|
||||
# signature to the base ListBlock, this custom signature will be preserved in the result of
|
||||
# deconstruct(), and we cannot rely on the first argument being the child block.
|
||||
|
||||
field = StreamField(
|
||||
[
|
||||
("heading", blocks.CharBlock(required=True)),
|
||||
("bullets", BulletListBlock()),
|
||||
],
|
||||
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.tests.test_streamfield.BulletListBlock", (), {}),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
Ładowanie…
Reference in New Issue