Test 1
fail to passedit only (model formsets.tests.ModelFormsetTest)
Django Model Formsets: Add edit_only Mode to Prevent New Object Creation
django__django-14725 · cardboard
You are working in the django/django repository at commit 0af9a5fc7d765aa05ea784e2c3237675f3bb4b49 (Django 4.1 alpha).
modelformset_factory() and inlineformset_factory() do not support an edit_only parameter. This is a security concern: a view that intends to let users edit existing model instances can be tricked into creating new ones if an attacker crafts a POST payload with extra forms that have no primary key.
Even setting extra=0 is not a complete defence, because a browser or attacker can raise the INITIAL_FORMS management-form value to make the formset treat extra submitted forms as if they were existing objects whose primary key merely got wiped — which causes save() to treat them as new instances and call INSERT.
The missing feature is an edit_only=False keyword argument on both factory functions. When edit_only=True the formset's save() must silently skip creating any new objects (it should only call save_existing_objects()), regardless of what the submitted data contains.
All changes must be made inside django/forms/models.py in the cloned django/django repository at the pinned commit.
1. `modelformset_factory()` — add an edit_only=False keyword parameter. The factory must set FormSet.edit_only = edit_only on the dynamically-created formset class.
2. `inlineformset_factory()` — add an edit_only=False keyword parameter. Relay it to the underlying modelformset_factory() call so the same class attribute is set.
3. `BaseModelFormSet.save()` — when self.edit_only is True, skip the save_new_objects() call entirely. Existing objects that are already in the queryset must still be saved (or deleted if marked for deletion), just as without edit_only.
4. `edit_only` with `extra` — when edit_only=True, the factory should also enforce extra=0 (raise ValueError if a caller explicitly passes extra > 0 together with edit_only=True), to prevent the INITIAL_FORMS tamper vector described above.
The public surface the grader checks is:
# modelformset_factory path
EditFormSet = modelformset_factory(MyModel, fields='__all__', edit_only=True)
# inlineformset_factory path
EditInlineFormSet = inlineformset_factory(Parent, Child, fields='__all__', edit_only=True)
# Saving an edit_only formset must not INSERT new rows
formset = EditFormSet(data=..., queryset=MyModel.objects.all())
assert formset.is_valid()
saved = formset.save() # must not create objects beyond those in the queryset
# Attempting to create with data outside the queryset is silently skippedfrom django.forms import modelformset_factory
from myapp.models import Author
AuthorEditFormSet = modelformset_factory(Author, fields=["name"], edit_only=True)
# Two existing authors in the DB; POST data contains a third form with no pk.
formset = AuthorEditFormSet(
data={
"form-TOTAL_FORMS": "3",
"form-INITIAL_FORMS": "2",
"form-MIN_NUM_FORMS": "0",
"form-MAX_NUM_FORMS": "1000",
"form-0-id": "1",
"form-0-name": "Alice",
"form-1-id": "2",
"form-1-name": "Bob",
"form-2-id": "",
"form-2-name": "Mallory", # attacker's new record
},
queryset=Author.objects.filter(pk__in=[1, 2]),
)
assert formset.is_valid()
saved = formset.save()
# Only Alice and Bob are updated; Mallory is NOT created.
assert Author.objects.count() == 2When a POST payload references a primary key that is not in the formset's queryset, edit_only=True must not create a new object for that pk either.
from django.forms import inlineformset_factory
from myapp.models import Author, Book
BookInlineFormSet = inlineformset_factory(Author, Book, fields=["title"], edit_only=True)
# edit_only is forwarded; extra defaults to 0; new books are never INSERTed.# This should raise ValueError because extra > 0 is incompatible with edit_only.
modelformset_factory(Author, fields=["name"], edit_only=True, extra=1)django/forms/models.py. Do not modify the test files or any other module.edit_only=False (the default).save_existing_objects() and deletion logic must continue to work normally.model_formsets tests.Your submission is accepted if:
test_edit_only* tests pass.model_formsets regression tests continue to pass.Leaderboards additionally rank accepted runs by tokens consumed, estimated cost, and wall-clock time.
Container
not started
Visible tests
60
Hidden tests
0
Last run
Not run
Test 1
fail to passedit only (model formsets.tests.ModelFormsetTest)
Test 2
fail to passedit only inlineformset factory (model formsets.tests.ModelFormsetTest)
Test 3
fail to passedit only object outside of queryset (model formsets.tests.ModelFormsetTest)
Test 4
pass to passdeletion (model formsets.tests.DeletionTests)
Test 5
pass to passoutdated deletion (model formsets.tests.DeletionTests)
Test 6
pass to passcallable defaults (model formsets.tests.ModelFormsetTest)
Test 7
pass to passcommit false (model formsets.tests.ModelFormsetTest)
Test 8
pass to passcustom pk (model formsets.tests.ModelFormsetTest)
Test 9
pass to passcustom save method (model formsets.tests.ModelFormsetTest)
Test 10
pass to passforeign keys in parents (model formsets.tests.ModelFormsetTest)
Test 11
pass to passinitial form count empty data (model formsets.tests.ModelFormsetTest)
Test 12
pass to passinline formsets (model formsets.tests.ModelFormsetTest)
Test 13
pass to passinline formsets save as new (model formsets.tests.ModelFormsetTest)
Test 14
pass to passinline formsets with custom pk (model formsets.tests.ModelFormsetTest)
Test 15
pass to passinline formsets with custom save method (model formsets.tests.ModelFormsetTest)
Test 16
pass to passinline formsets with multi table inheritance (model formsets.tests.ModelFormsetTest)
Test 17
pass to passinline formsets with nullable unique together (model formsets.tests.ModelFormsetTest)
Test 18
pass to passinlineformset factory with null fk (model formsets.tests.ModelFormsetTest)
Test 19
pass to passinlineformset with arrayfield (model formsets.tests.ModelFormsetTest)
Test 20
pass to passmax num (model formsets.tests.ModelFormsetTest)
Test 21
pass to passmin num (model formsets.tests.ModelFormsetTest)
Test 22
pass to passmin num with existing (model formsets.tests.ModelFormsetTest)
Test 23
pass to passmodel formset with custom pk (model formsets.tests.ModelFormsetTest)
Test 24
pass to passmodel formset with initial model instance (model formsets.tests.ModelFormsetTest)
Test 25
pass to passmodel formset with initial queryset (model formsets.tests.ModelFormsetTest)
Test 26
pass to passmodel inheritance (model formsets.tests.ModelFormsetTest)
Test 27
pass to passmodelformset min num equals max num less than (model formsets.tests.ModelFormsetTest)
Test 28
pass to passmodelformset min num equals max num more than (model formsets.tests.ModelFormsetTest)
Test 29
pass to passmodelformset validate max flag (model formsets.tests.ModelFormsetTest)
Test 30
pass to passprevent change outer model and create invalid data (model formsets.tests.ModelFormsetTest)
Test 31
pass to passprevent duplicates from with the same formset (model formsets.tests.ModelFormsetTest)
Test 32
pass to passsimple save (model formsets.tests.ModelFormsetTest)
Test 33
pass to passunique together validation (model formsets.tests.ModelFormsetTest)
Test 34
pass to passunique together with inlineformset factory (model formsets.tests.ModelFormsetTest)
Test 35
pass to passunique true enforces max num one (model formsets.tests.ModelFormsetTest)
Test 36
pass to passunique validation (model formsets.tests.ModelFormsetTest)
Test 37
pass to passvalidation with child model without id (model formsets.tests.ModelFormsetTest)
Test 38
pass to passvalidation with invalid id (model formsets.tests.ModelFormsetTest)
Test 39
pass to passvalidation with nonexistent id (model formsets.tests.ModelFormsetTest)
Test 40
pass to passvalidation without id (model formsets.tests.ModelFormsetTest)
Test 41
pass to passinlineformset factory absolute max (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 42
pass to passinlineformset factory absolute max with max num (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 43
pass to passinlineformset factory can delete extra (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 44
pass to passinlineformset factory can not delete extra (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 45
pass to passinlineformset factory error messages overrides (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 46
pass to passinlineformset factory field class overrides (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 47
pass to passinlineformset factory help text overrides (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 48
pass to passinlineformset factory labels overrides (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 49
pass to passinlineformset factory passes renderer (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 50
pass to passinlineformset factory widgets (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 51
pass to passmodelformset factory absolute max (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 52
pass to passmodelformset factory absolute max with max num (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 53
pass to passmodelformset factory can delete extra (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 54
pass to passmodelformset factory disable delete extra (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 55
pass to passmodelformset factory error messages overrides (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 56
pass to passmodelformset factory field class overrides (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 57
pass to passmodelformset factory help text overrides (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 58
pass to passmodelformset factory labels overrides (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 59
pass to passmodelformset factory passes renderer (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
Test 60
pass to passmodelformset factory widgets (model formsets.tests.TestModelFormsetOverridesTroughFormMeta)
README.md
django/django