diff --git a/django/forms/models.py b/django/forms/models.py index 3d74225434b..a0f3ff53b31 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -755,12 +755,13 @@ class BaseModelFormSet(BaseFormSet): forms_to_delete = self.deleted_forms for form in self.initial_forms: obj = form.instance + # If the pk is None, it means either: + # 1. The object is an unexpected empty model, created by invalid + # POST data such as an object outside the formset's queryset. + # 2. The object was already deleted from the database. + if obj.pk is None: + continue if form in forms_to_delete: - # If the pk is None, it means that the object can't be - # deleted again. Possible reason for this is that the - # object was already deleted from the DB. Refs #14877. - if obj.pk is None: - continue self.deleted_objects.append(obj) self.delete_existing(obj, commit=commit) elif form.has_changed(): diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index fc0dff8304a..4387fbf52d2 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -1631,6 +1631,28 @@ class ModelFormsetTest(TestCase): ['Please correct the duplicate data for subtitle which must be unique for the month in posted.'] ) + def test_prevent_change_outer_model_and_create_invalid_data(self): + author = Author.objects.create(name='Charles') + other_author = Author.objects.create(name='Walt') + AuthorFormSet = modelformset_factory(Author, fields='__all__') + data = { + 'form-TOTAL_FORMS': '2', + 'form-INITIAL_FORMS': '2', + 'form-MAX_NUM_FORMS': '', + 'form-0-id': str(author.id), + 'form-0-name': 'Charles', + 'form-1-id': str(other_author.id), # A model not in the formset's queryset. + 'form-1-name': 'Changed name', + } + # This formset is only for Walt Whitman and shouldn't accept data for + # other_author. + formset = AuthorFormSet(data=data, queryset=Author.objects.filter(id__in=(author.id,))) + self.assertTrue(formset.is_valid()) + formset.save() + # The name of other_author shouldn't be changed and new models aren't + # created. + self.assertQuerysetEqual(Author.objects.all(), ['', '']) + class TestModelFormsetOverridesTroughFormMeta(TestCase): def test_modelformset_factory_widgets(self):