﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
35531	BaseModelFormSet.clean() recursively triggers is_valid() which may cause problems when is_valid() was overriden	Christophe Henry	Rish	"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."	Bug	closed	Forms	dev	Normal	invalid		Christophe Henry	Unreviewed	0	0	0	0	1	0
