diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index a4aa8e40e3..d28a382814 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -509,6 +509,11 @@ class InlineAdminForm(AdminForm):
# Auto fields are editable, so check for auto or non-editable pk.
self.form._meta.model._meta.auto_field
or not self.form._meta.model._meta.pk.editable
+ # The pk can be editable, but excluded from the inline.
+ or (
+ self.form._meta.exclude
+ and self.form._meta.model._meta.pk.name in self.form._meta.exclude
+ )
or
# Also search any parents for an auto field. (The pk info is
# propagated to child models so that does not need to be checked
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py
index 3cdaee22df..578142d192 100644
--- a/tests/admin_inlines/admin.py
+++ b/tests/admin_inlines/admin.py
@@ -57,6 +57,8 @@ from .models import (
Teacher,
Title,
TitleCollection,
+ UUIDChild,
+ UUIDParent,
)
site = admin.AdminSite(name="admin")
@@ -471,6 +473,16 @@ class ShowInlineChildInline(admin.StackedInline):
model = ShowInlineChild
+class UUIDChildInline(admin.StackedInline):
+ model = UUIDChild
+ exclude = ("id",)
+
+
+class UUIDParentModelAdmin(admin.ModelAdmin):
+ model = UUIDParent
+ inlines = [UUIDChildInline]
+
+
class ShowInlineParentAdmin(admin.ModelAdmin):
def get_inlines(self, request, obj):
if obj is not None and obj.show_inlines:
@@ -513,6 +525,7 @@ site.register(CourseProxy, ClassAdminStackedVertical)
site.register(CourseProxy1, ClassAdminTabularVertical)
site.register(CourseProxy2, ClassAdminTabularHorizontal)
site.register(ShowInlineParent, ShowInlineParentAdmin)
+site.register(UUIDParent, UUIDParentModelAdmin)
# Used to test hidden fields in tabular and stacked inlines.
site2 = admin.AdminSite(name="tabular_inline_hidden_field_admin")
site2.register(SomeParentModel, inlines=[ChildHiddenFieldTabularInline])
diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py
index 5a85556a55..64aaca8d14 100644
--- a/tests/admin_inlines/models.py
+++ b/tests/admin_inlines/models.py
@@ -3,6 +3,7 @@ Testing of admin inline formsets.
"""
import random
+import uuid
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
@@ -399,3 +400,13 @@ class BothVerboseNameProfile(Profile):
class Meta:
verbose_name = "Model with both - name"
verbose_name_plural = "Model with both - plural name"
+
+
+class UUIDParent(models.Model):
+ pass
+
+
+class UUIDChild(models.Model):
+ id = models.UUIDField(default=uuid.uuid4, primary_key=True)
+ title = models.CharField(max_length=128)
+ parent = models.ForeignKey(UUIDParent, on_delete=models.CASCADE)
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index dee703825d..25512aede4 100644
--- a/tests/admin_inlines/tests.py
+++ b/tests/admin_inlines/tests.py
@@ -44,6 +44,8 @@ from .models import (
SomeChildModel,
SomeParentModel,
Teacher,
+ UUIDChild,
+ UUIDParent,
VerboseNamePluralProfile,
VerboseNameProfile,
)
@@ -115,6 +117,19 @@ class TestInline(TestDataMixin, TestCase):
)
self.assertContains(response, "")
+ def test_excluded_id_for_inlines_uses_hidden_field(self):
+ parent = UUIDParent.objects.create()
+ child = UUIDChild.objects.create(title="foo", parent=parent)
+ response = self.client.get(
+ reverse("admin:admin_inlines_uuidparent_change", args=(parent.id,))
+ )
+ self.assertContains(
+ response,
+ f'',
+ html=True,
+ )
+
def test_many_to_many_inlines(self):
"Autogenerated many-to-many inlines are displayed correctly (#13407)"
response = self.client.get(reverse("admin:admin_inlines_author_add"))