﻿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
36251	BaseInlineFormSet modifies Meta.fields of passed form class if Meta.fields is type list	ifeomi	jengziyi	"When you make an inline formset, `BaseInlineFormset` adds the foreign key name to the list of fields. However, it only creates a copy of the original form’s fields if the form defines its fields in a tuple; otherwise, it modifies the original reference to fields. If you pass in a form class that has defined `Meta.fields` as a list, `BaseInlineFormset` changes the fields on that form class. This means that if that form is used for other things besides the inline formset, it now has an extra field that has been erroneously added. 

Here is a minimally reproducible example. We have a `UserActionForm` that defines `Meta.fields` as a list. Notice that after initializing an instance of the `FormsetClass`, the fields on `UserActionForm` have been modified to include `user` (the foreign key of the inline formset). Expected behavior is that `UserActionForm.Meta.fields` is not modified by instantiation of a formset, and instead that the formset modifies a copy of the fields.

{{{#!python
## models.py ###
class User(AbstractUser):
    email = models.EmailField(unique=True)

class UserAction(models.Model):
    user = models.ForeignKey(User,  on_delete=models.PROTECT)
    url = models.URLField(max_length=2083)
    
## forms.py ###
from django import forms

class UserActionForm(forms.ModelForm):
    class Meta:
        model = UserAction
        fields = [""url""]
		   
### Shell ###
from common.models import User, UserAction
from django import forms

FormsetClass = forms.inlineformset_factory(User, UserAction, UserActionForm)
print(UserActionForm.Meta.fields)
# ['url']
FormsetClass()
print(UserActionForm.Meta.fields)
# ['url', 'user'] --> (should just be ['url'])
}}}

Here’s the line in `BaseInlineFormset`'s `__init__` that modifies the form’s fields - in the event that the form’s fields are not a `tuple`, the init appends directly to `fields` without making a copy.

{{{#!python
# django/forms/models.py:L1115
class BaseInlineFormSet(BaseModelFormSet):
	def __init__(...):
		...
		if isinstance(self.form._meta.fields, tuple):
			self.form._meta.fields = list(self.form._meta.fields)
			self.form._meta.fields.append(self.fk.name)
}}}

The fix for this should be fairly straightforward: rather than only copying `_meta.fields` if it’s a tuple, `BaseInlineFormset` should always make a copy of `_meta.fields` regardless of the type of the iterable. `BaseInlineFormset` already works with a copy in the case that the original form’s fields are a tuple, so this change will maintain the current behavior while preventing modifications to the original form’s fields.

**Proposed Patch:**

{{{#!diff
diff --git a/django/forms/models.py b/django/forms/models.py
index d220e3c90f..4e8ef39c0d 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -1113,8 +1113,7 @@ class BaseInlineFormSet(BaseModelFormSet):
         # Add the generated field to form._meta.fields if it's defined to make
         # sure validation isn't skipped on that field.
         if self.form._meta.fields and self.fk.name not in self.form._meta.fields:
-            if isinstance(self.form._meta.fields, tuple):
-                self.form._meta.fields = list(self.form._meta.fields)
+            self.form._meta.fields = list(self.form._meta.fields)
             self.form._meta.fields.append(self.fk.name)

     def initial_form_count(self):
}}}
"	Bug	assigned	Forms	dev	Normal		inline formset		Accepted	0	0	0	0	0	0
