diff --git a/django/forms/models.py b/django/forms/models.py index 92906eb1fa..48fe2f4ac2 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -788,7 +788,10 @@ class BaseModelFormSet(BaseFormSet): or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk))) if pk_is_not_editable(pk) or pk.name not in form.fields: if form.is_bound: - pk_value = form.instance.pk + # If we're adding the related instance, ignore its primary key + # as it could be an auto-generated default which isn't actually + # in the database. + pk_value = None if form.instance._state.adding else form.instance.pk else: try: if index is not None: @@ -915,6 +918,11 @@ class BaseInlineFormSet(BaseModelFormSet): if self.fk.rel.field_name != self.fk.rel.to._meta.pk.name: kwargs['to_field'] = self.fk.rel.field_name + # If we're adding a new object, ignore a parent's auto-generated pk + # as it will be regenerated on the save request. + if self.instance._state.adding and form._meta.model._meta.pk.has_default(): + self.instance.pk = None + form.fields[name] = InlineForeignKeyField(self.instance, **kwargs) # Add the generated field to form._meta.fields if it's defined to make diff --git a/tests/model_formsets/models.py b/tests/model_formsets/models.py index da79c6a573..0b4963208f 100644 --- a/tests/model_formsets/models.py +++ b/tests/model_formsets/models.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import datetime +import uuid from django.db import models from django.utils import six @@ -241,3 +242,15 @@ class Post(models.Model): def __str__(self): return self.name + + +# Models for testing UUID primary keys +class UUIDPKParent(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=255) + + +class UUIDPKChild(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=255) + parent = models.ForeignKey(UUIDPKParent) diff --git a/tests/model_formsets/test_uuid.py b/tests/model_formsets/test_uuid.py new file mode 100644 index 0000000000..8025f758c9 --- /dev/null +++ b/tests/model_formsets/test_uuid.py @@ -0,0 +1,32 @@ +from django.forms.models import inlineformset_factory +from django.test import TestCase + +from .models import UUIDPKChild, UUIDPKParent + + +class InlineFormsetTests(TestCase): + def test_inlineformset_factory_nulls_default_pks(self): + """ + #24377 - If we're adding a new object, a parent's auto-generated pk + from the model field default should be ignored as it's regenerated on + the save request. + """ + FormSet = inlineformset_factory(UUIDPKParent, UUIDPKChild, fields='__all__') + formset = FormSet() + self.assertIsNone(formset.forms[0].fields['parent'].initial) + + def test_inlineformset_factory_ignores_default_pks_on_submit(self): + """ + #24377 - Inlines with a model field default should ignore that default + value to avoid triggering validation on empty forms. + """ + FormSet = inlineformset_factory(UUIDPKParent, UUIDPKChild, fields='__all__') + formset = FormSet({ + 'uuidpkchild_set-TOTAL_FORMS': 3, + 'uuidpkchild_set-INITIAL_FORMS': 0, + 'uuidpkchild_set-MAX_NUM_FORMS': '', + 'uuidpkchild_set-0-name': 'Foo', + 'uuidpkchild_set-1-name': '', + 'uuidpkchild_set-2-name': '', + }) + self.assertTrue(formset.is_valid())