From 6d01356ec667116a11ef912337a569c5abd2edf9 Mon Sep 17 00:00:00 2001 From: Matt Westcott <matt@west.co.tt> Date: Mon, 10 Mar 2025 20:55:50 +0000 Subject: [PATCH] Rework deferring validation on StreamField Overwriting a block's `required` option is not safe, as a block object is part of a class-level definition and is thus shared by all block instances, not just the one currently being validated. Instead, introduce a flag to Block.clean to skip 'required' validation. --- wagtail/blocks/base.py | 20 ++++++++++++++++---- wagtail/blocks/stream_block.py | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/wagtail/blocks/base.py b/wagtail/blocks/base.py index 5c4ce2f2c3..cd73c8098e 100644 --- a/wagtail/blocks/base.py +++ b/wagtail/blocks/base.py @@ -756,10 +756,22 @@ class BlockField(forms.Field): super().__init__(**kwargs) def clean(self, value): - # Pass required flag to the top-level block, so that dynamically setting it on the - # field (e.g. by defer_required_fields) is respected by the block. - self.block.set_meta_options({"required": self.required}) - return self.block.clean(value) + from wagtail.blocks.stream_block import StreamBlock + + if isinstance(self.block, StreamBlock): + # StreamBlock is the only block type that is formally-supported as the top level block + # of a BlockField, but it's possible that other block types could be used, so check + # this explicitly. + # self.block has a `required` attribute that is consistent with the StreamField's `blank` + # attribute and thus the `required` attribute of BlockField - but if the latter has been + # assigned dynamically (e.g. by defer_required_fields) we want this to take precedence. + # We do this through the `ignore_required_constraints` flag recognised by + # StreamBlock.clean. + return self.block.clean( + value, ignore_required_constraints=not self.required + ) + else: + return self.block.clean(value) def has_changed(self, initial_value, data_value): return self.block.get_prep_value(initial_value) != self.block.get_prep_value( diff --git a/wagtail/blocks/stream_block.py b/wagtail/blocks/stream_block.py index 95991ff28a..343754b76d 100644 --- a/wagtail/blocks/stream_block.py +++ b/wagtail/blocks/stream_block.py @@ -160,7 +160,7 @@ class BaseStreamBlock(Block): def required(self): return self.meta.required - def clean(self, value): + def clean(self, value, ignore_required_constraints=False): cleaned_data = [] errors = {} non_block_errors = ErrorList() @@ -179,7 +179,7 @@ class BaseStreamBlock(Block): % {"min_num": self.meta.min_num} ) ) - elif self.required and len(value) == 0: + elif self.required and not ignore_required_constraints and len(value) == 0: non_block_errors.append(ValidationError(_("This field is required."))) if self.meta.max_num is not None and self.meta.max_num < len(value):