diff --git a/src/wtforms/fields/choices.py b/src/wtforms/fields/choices.py index e911bab9..66a9719d 100644 --- a/src/wtforms/fields/choices.py +++ b/src/wtforms/fields/choices.py @@ -389,8 +389,8 @@ def iter_groups(self): ], ) - def post_process(self): - super().post_process() + def post_process(self, formdata=None): + super().post_process(formdata) if self._choices_callable is not None: self.choices = self._invoke_choices_callback(self._choices_callable) diff --git a/src/wtforms/fields/core.py b/src/wtforms/fields/core.py index c0ca9888..2305b9de 100644 --- a/src/wtforms/fields/core.py +++ b/src/wtforms/fields/core.py @@ -350,13 +350,16 @@ def process(self, formdata, data=unset_value, extra_filters=None): except ValueError as e: self.process_errors.append(e.args[0]) - def post_process(self): + def post_process(self, formdata=None): """Hook called after every field in the enclosing form has been processed. Override this when a field needs to read other fields' processed data, for example to resolve dynamic choices that depend on the form state. The base implementation resolves any inline :class:`~wtforms.DataList` attached to the field. + + :param formdata: The resolved formdata the enclosing form was bound + with, or ``None`` for non-formdata cycles. """ if self._datalist is not None and not isinstance(self._datalist, str): self._datalist._resolve(self) diff --git a/src/wtforms/fields/form.py b/src/wtforms/fields/form.py index 61992e60..6dc5249f 100644 --- a/src/wtforms/fields/form.py +++ b/src/wtforms/fields/form.py @@ -70,8 +70,8 @@ def process(self, formdata, data=unset_value, extra_filters=None): meta={"_parent_form": self._form}, ) - def post_process(self): - self.form.post_process() + def post_process(self, formdata=None): + self.form.post_process(formdata) def validate(self, form, extra_validators=()): if extra_validators: diff --git a/src/wtforms/fields/list.py b/src/wtforms/fields/list.py index 45888bae..3118a544 100644 --- a/src/wtforms/fields/list.py +++ b/src/wtforms/fields/list.py @@ -114,9 +114,9 @@ def _extract_indices(self, prefix, formdata): if k.isdigit(): yield int(k) - def post_process(self): + def post_process(self, formdata=None): for entry in self.entries: - entry.post_process() + entry.post_process(formdata) def validate(self, form, extra_validators=()): """ diff --git a/src/wtforms/form.py b/src/wtforms/form.py index bf5551e0..c44f579a 100644 --- a/src/wtforms/form.py +++ b/src/wtforms/form.py @@ -131,15 +131,20 @@ def process(self, formdata=None, obj=None, data=None, extra_filters=None, **kwar field.process(formdata, data, extra_filters=field_extra_filters) if self._parent_form is None: - self.post_process() + self.post_process(formdata) - def post_process(self): + def post_process(self, formdata=None): """Hook called at the end of :meth:`process` on the root form. Runs the :meth:`~fields.Field.post_process` hook on every field, after all fields have been processed. Override this on a form subclass to add - cross-field finalization logic; call ``super().post_process()`` to keep - per-field hooks running. + cross-field finalization logic; call ``super().post_process(formdata)`` + to keep per-field hooks running. + + :param formdata: The resolved formdata the form was bound with + (already passed through :meth:`~meta.DefaultMeta.wrap_formdata`), + or ``None`` for non-formdata cycles. Forwarded to every nested + ``post_process``. ``post_process`` is only triggered automatically on the root form. Forms nested inside a :class:`~fields.FormField` (or via :class:`~fields.FieldList` @@ -148,7 +153,7 @@ def post_process(self): ``post_process`` runs exactly once per processing cycle. """ for field in self._fields.values(): - field.post_process() + field.post_process(formdata) def validate(self, extra_validators=None): """ diff --git a/tests/fields/test_form.py b/tests/fields/test_form.py index 776e24c7..96fbb051 100644 --- a/tests/fields/test_form.py +++ b/tests/fields/test_form.py @@ -118,15 +118,15 @@ def test_post_process_propagates_through_form_field(): class Inner(Form): x = StringField() - def post_process(self): - super().post_process() + def post_process(self, formdata=None): + super().post_process(formdata) captured.append(("inner", self.x.data)) class Outer(Form): block = FormField(Inner) - def post_process(self): - super().post_process() + def post_process(self, formdata=None): + super().post_process(formdata) captured.append(("outer", self.block.form.x.data)) Outer(DummyPostData({"block-x": "v"})) @@ -182,9 +182,9 @@ class Outer(Form): tenant = StringField() block = FormField(Inner) - def post_process(self): + def post_process(self, formdata=None): self.tenant.data = (self.tenant.data or "").upper() - super().post_process() + super().post_process(formdata) Outer(DummyPostData({"tenant": "acme", "block-item": "a"})) assert captured == ["ACME"]