diff --git a/django/forms/models.py b/django/forms/models.py index adc31081b7..b58d8e5b99 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -884,12 +884,17 @@ class BaseInlineFormSet(BaseModelFormSet): def _construct_form(self, i, **kwargs): form = super()._construct_form(i, **kwargs) if self.save_as_new: + mutable = getattr(form.data, '_mutable', None) + # Allow modifying an immutable QueryDict. + if mutable is not None: + form.data._mutable = True # Remove the primary key from the form's data, we are only # creating new instances form.data[form.add_prefix(self._pk_field.name)] = None - # Remove the foreign key from the form's data form.data[form.add_prefix(self.fk.name)] = None + if mutable is not None: + form.data._mutable = mutable # Set the fk value here so that the form can do its validation. fk_value = self.instance.pk diff --git a/docs/releases/1.11.1.txt b/docs/releases/1.11.1.txt index 9a9d96bf46..727ce94751 100644 --- a/docs/releases/1.11.1.txt +++ b/docs/releases/1.11.1.txt @@ -81,3 +81,6 @@ Bugfixes * Fixed a regression in choice ordering in form fields with grouped and non-grouped options (:ticket:`28157`). + +* Fixed crash in ``BaseInlineFormSet._construct_form()`` when using + ``save_as_new`` (:ticket:`28159`). diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index 3873cb3b27..c440631ba7 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -10,6 +10,7 @@ from django.forms.models import ( BaseModelFormSet, _get_foreign_key, inlineformset_factory, modelformset_factory, ) +from django.http import QueryDict from django.test import TestCase, skipUnlessDBFeature from .models import ( @@ -699,7 +700,9 @@ class ModelFormsetTest(TestCase): AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__") Author.objects.create(name='Charles Baudelaire') - data = { + # An immutable QueryDict simulates request.POST. + data = QueryDict(mutable=True) + data.update({ 'book_set-TOTAL_FORMS': '3', # the number of forms rendered 'book_set-INITIAL_FORMS': '2', # the number of forms with initial data 'book_set-MAX_NUM_FORMS': '', # the max number of forms @@ -708,10 +711,12 @@ class ModelFormsetTest(TestCase): 'book_set-1-id': '2', 'book_set-1-title': 'Les Paradis Artificiels', 'book_set-2-title': '', - } + }) + data._mutable = False formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True) self.assertTrue(formset.is_valid()) + self.assertIs(data._mutable, False) new_author = Author.objects.create(name='Charles Baudelaire') formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True)