Fixed #22229 -- Added primary key validation to BaseModelFormSet._construct_form().

This commit is contained in:
Jon Dufresne 2017-06-26 15:00:30 -07:00 committed by Tim Graham
parent 988309a1ae
commit d7881d2020
2 changed files with 72 additions and 11 deletions

View File

@ -590,20 +590,37 @@ class BaseModelFormSet(BaseFormSet):
return field.to_python return field.to_python
def _construct_form(self, i, **kwargs): def _construct_form(self, i, **kwargs):
if self.is_bound and i < self.initial_form_count(): pk_required = False
pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name) if i < self.initial_form_count():
pk = self.data[pk_key] pk_required = True
pk_field = self.model._meta.pk if self.is_bound:
to_python = self._get_to_python(pk_field) pk_key = '%s-%s' % (self.add_prefix(i), self.model._meta.pk.name)
pk = to_python(pk) try:
kwargs['instance'] = self._existing_object(pk) pk = self.data[pk_key]
if i < self.initial_form_count() and 'instance' not in kwargs: except KeyError:
kwargs['instance'] = self.get_queryset()[i] # The primary key is missing. The user may have tampered
if i >= self.initial_form_count() and self.initial_extra: # with POST data.
pass
else:
to_python = self._get_to_python(self.model._meta.pk)
try:
pk = to_python(pk)
except ValidationError:
# The primary key exists but is an invalid value. The
# user may have tampered with POST data.
pass
else:
kwargs['instance'] = self._existing_object(pk)
else:
kwargs['instance'] = self.get_queryset()[i]
elif self.initial_extra:
# Set initial values for extra forms # Set initial values for extra forms
with suppress(IndexError): with suppress(IndexError):
kwargs['initial'] = self.initial_extra[i - self.initial_form_count()] kwargs['initial'] = self.initial_extra[i - self.initial_form_count()]
return super()._construct_form(i, **kwargs) form = super()._construct_form(i, **kwargs)
if pk_required:
form.fields[self.model._meta.pk.name].required = True
return form
def get_queryset(self): def get_queryset(self):
if not hasattr(self, '_queryset'): if not hasattr(self, '_queryset'):

View File

@ -1655,6 +1655,50 @@ class ModelFormsetTest(TestCase):
# created. # created.
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Charles>', '<Author: Walt>']) self.assertQuerysetEqual(Author.objects.all(), ['<Author: Charles>', '<Author: Walt>'])
def test_validation_without_id(self):
AuthorFormSet = modelformset_factory(Author, fields='__all__')
data = {
'form-TOTAL_FORMS': '1',
'form-INITIAL_FORMS': '1',
'form-MAX_NUM_FORMS': '',
'form-0-name': 'Charles',
}
formset = AuthorFormSet(data)
self.assertEqual(
formset.errors,
[{'id': ['This field is required.']}],
)
def test_validation_with_child_model_without_id(self):
BetterAuthorFormSet = modelformset_factory(BetterAuthor, fields='__all__')
data = {
'form-TOTAL_FORMS': '1',
'form-INITIAL_FORMS': '1',
'form-MAX_NUM_FORMS': '',
'form-0-name': 'Charles',
'form-0-write_speed': '10',
}
formset = BetterAuthorFormSet(data)
self.assertEqual(
formset.errors,
[{'author_ptr': ['This field is required.']}],
)
def test_validation_with_invalid_id(self):
AuthorFormSet = modelformset_factory(Author, fields='__all__')
data = {
'form-TOTAL_FORMS': '1',
'form-INITIAL_FORMS': '1',
'form-MAX_NUM_FORMS': '',
'form-0-id': 'abc',
'form-0-name': 'Charles',
}
formset = AuthorFormSet(data)
self.assertEqual(
formset.errors,
[{'id': ['Select a valid choice. That choice is not one of the available choices.']}],
)
def test_validation_with_nonexistent_id(self): def test_validation_with_nonexistent_id(self):
AuthorFormSet = modelformset_factory(Author, fields='__all__') AuthorFormSet = modelformset_factory(Author, fields='__all__')
data = { data = {