Skip to content

Commit d7881d2

Browse files
jdufresnetimgraham
authored andcommitted
Fixed #22229 -- Added primary key validation to BaseModelFormSet._construct_form().
1 parent 988309a commit d7881d2

File tree

2 files changed

+72
-11
lines changed

2 files changed

+72
-11
lines changed

django/forms/models.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -590,20 +590,37 @@ def _get_to_python(self, field):
590590
return field.to_python
591591

592592
def _construct_form(self, i, **kwargs):
593-
if self.is_bound and i < self.initial_form_count():
594-
pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name)
595-
pk = self.data[pk_key]
596-
pk_field = self.model._meta.pk
597-
to_python = self._get_to_python(pk_field)
598-
pk = to_python(pk)
599-
kwargs['instance'] = self._existing_object(pk)
600-
if i < self.initial_form_count() and 'instance' not in kwargs:
601-
kwargs['instance'] = self.get_queryset()[i]
602-
if i >= self.initial_form_count() and self.initial_extra:
593+
pk_required = False
594+
if i < self.initial_form_count():
595+
pk_required = True
596+
if self.is_bound:
597+
pk_key = '%s-%s' % (self.add_prefix(i), self.model._meta.pk.name)
598+
try:
599+
pk = self.data[pk_key]
600+
except KeyError:
601+
# The primary key is missing. The user may have tampered
602+
# with POST data.
603+
pass
604+
else:
605+
to_python = self._get_to_python(self.model._meta.pk)
606+
try:
607+
pk = to_python(pk)
608+
except ValidationError:
609+
# The primary key exists but is an invalid value. The
610+
# user may have tampered with POST data.
611+
pass
612+
else:
613+
kwargs['instance'] = self._existing_object(pk)
614+
else:
615+
kwargs['instance'] = self.get_queryset()[i]
616+
elif self.initial_extra:
603617
# Set initial values for extra forms
604618
with suppress(IndexError):
605619
kwargs['initial'] = self.initial_extra[i - self.initial_form_count()]
606-
return super()._construct_form(i, **kwargs)
620+
form = super()._construct_form(i, **kwargs)
621+
if pk_required:
622+
form.fields[self.model._meta.pk.name].required = True
623+
return form
607624

608625
def get_queryset(self):
609626
if not hasattr(self, '_queryset'):

tests/model_formsets/tests.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,50 @@ def test_prevent_change_outer_model_and_create_invalid_data(self):
16551655
# created.
16561656
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Charles>', '<Author: Walt>'])
16571657

1658+
def test_validation_without_id(self):
1659+
AuthorFormSet = modelformset_factory(Author, fields='__all__')
1660+
data = {
1661+
'form-TOTAL_FORMS': '1',
1662+
'form-INITIAL_FORMS': '1',
1663+
'form-MAX_NUM_FORMS': '',
1664+
'form-0-name': 'Charles',
1665+
}
1666+
formset = AuthorFormSet(data)
1667+
self.assertEqual(
1668+
formset.errors,
1669+
[{'id': ['This field is required.']}],
1670+
)
1671+
1672+
def test_validation_with_child_model_without_id(self):
1673+
BetterAuthorFormSet = modelformset_factory(BetterAuthor, fields='__all__')
1674+
data = {
1675+
'form-TOTAL_FORMS': '1',
1676+
'form-INITIAL_FORMS': '1',
1677+
'form-MAX_NUM_FORMS': '',
1678+
'form-0-name': 'Charles',
1679+
'form-0-write_speed': '10',
1680+
}
1681+
formset = BetterAuthorFormSet(data)
1682+
self.assertEqual(
1683+
formset.errors,
1684+
[{'author_ptr': ['This field is required.']}],
1685+
)
1686+
1687+
def test_validation_with_invalid_id(self):
1688+
AuthorFormSet = modelformset_factory(Author, fields='__all__')
1689+
data = {
1690+
'form-TOTAL_FORMS': '1',
1691+
'form-INITIAL_FORMS': '1',
1692+
'form-MAX_NUM_FORMS': '',
1693+
'form-0-id': 'abc',
1694+
'form-0-name': 'Charles',
1695+
}
1696+
formset = AuthorFormSet(data)
1697+
self.assertEqual(
1698+
formset.errors,
1699+
[{'id': ['Select a valid choice. That choice is not one of the available choices.']}],
1700+
)
1701+
16581702
def test_validation_with_nonexistent_id(self):
16591703
AuthorFormSet = modelformset_factory(Author, fields='__all__')
16601704
data = {

0 commit comments

Comments
 (0)