Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/wtforms/fields/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
5 changes: 4 additions & 1 deletion src/wtforms/fields/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/wtforms/fields/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions src/wtforms/fields/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=()):
"""
Expand Down
15 changes: 10 additions & 5 deletions src/wtforms/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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):
"""
Expand Down
12 changes: 6 additions & 6 deletions tests/fields/test_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}))
Expand Down Expand Up @@ -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"]
Expand Down
Loading