#35531: BaseModelFormSet.clean() recursively triggers is_valid() which may cause
problems when is_valid() was overriden
--------------------------------------------+------------------------
Reporter: Christophe Henry | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: dev
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 1
UI/UX: 0 |
--------------------------------------------+------------------------
The current implementation of
[
https://github.com/django/django/blob/main/django/forms/models.py#L800-L801
`BaseModelFormSet.clean()`] calls
[
https://github.com/django/django/blob/main/django/forms/models.py#L800-L801
`BaseModelFormSet.validate_unique()`] which itself calls
[
https://github.com/django/django/blob/main/django/forms/formsets.py#L286
`self.is_valid()`] though
[
https://github.com/django/django/blob/main/django/forms/models.py#L807
`self.deleted_forms`].
This recursive call of `is_valid()` may mess up the validation if
`is_valid()` was overriden and `self.data` is altered during the process.
For instance:
```
{{{#!python
from django.forms import BaseModelFormSet
from django.forms.formsets import TOTAL_FORM_COUNT
class ExampleFormSet(BaseModelFormSet):
def __init__(self, extra_data_provider, **kwargs):
self.extra_data_provider = extra_data_provider
super().__init__(**kwargs)
def is_valid(self):
if not self.is_bound:
return False
next_idx =
self.data[self.management_form.add_prefix(TOTAL_FORM_COUNT)]
old_data = self.data
self.data = self.data.copy()
self.data[self.management_form.add_prefix(TOTAL_FORM_COUNT)] =
f"{next_idx+1}"
self.management_form.full_clean()
# Retreiving form data from somewhere else (this is an example)
next_form_data = self.extra_data_provider.retreive()
self.data.update(next_form_data)
self.forms.append(
self._construct_form(next_idx,
**self.get_form_kwargs(next_idx))
)
result = super().is_valid()
self._validate_empty_form = False
if not result:
# Reverting our changes
self.data = old_data
self.management_form.full_clean()
self.forms.pop()
return result
}}}
Let's assume `super().is_valid()` is `False` because `self.clean()`
failed. Then `self.forms.append()` will be called twice, but the cleaning
in the `if not result` block ` will perform only once.
In my opinion, this is a bug that produces unexpected behavior and is
difficult to debug.
This could be solved
[
https://github.com/django/django/blob/main/django/forms/formsets.py#L286
by not calling `self.is_valid()` in `BaseFormSet.deleted_forms`] and
instead perform only a partial validation here. I enclosed a patch to this
ticket to show my solution.
--
Ticket URL: <
https://code.djangoproject.com/ticket/35531>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.