diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html
index 6cfd9aff8e..891268faba 100644
--- a/django/contrib/admin/templates/admin/change_list_results.html
+++ b/django/contrib/admin/templates/admin/change_list_results.html
@@ -1,5 +1,5 @@
{% if result_hidden_fields %}
-
{# DIV for HTML validation #}
+
{# DIV for HTML validation #}
{% for item in result_hidden_fields %}{{ item }}{% endfor %}
{% endif %}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 371b6a2b5a..fdf082be70 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -188,7 +188,9 @@ def items_for_result(cl, result, form):
# By default the fields come from ModelAdmin.list_editable, but if we pull
# the fields out of the form instead of list_editable custom admins
# can provide fields on a per request basis
- if form and field_name in form.fields:
+ if (form and field_name in form.fields and not (
+ field_name == cl.model._meta.pk.name and
+ form[cl.model._meta.pk.name].is_hidden)):
bf = form[field_name]
result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf))
else:
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
index 3b44f94d8b..4c4e2907d7 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -735,6 +735,28 @@ class CoverLetterAdmin(admin.ModelAdmin):
#return super(CoverLetterAdmin, self).queryset(request).only('author')
return super(CoverLetterAdmin, self).queryset(request).defer('date')
+class Story(models.Model):
+ title = models.CharField(max_length=100)
+ content = models.TextField()
+
+class StoryForm(forms.ModelForm):
+ class Meta:
+ widgets = {'title': forms.HiddenInput}
+
+class StoryAdmin(admin.ModelAdmin):
+ list_display = ('id', 'title', 'content')
+ list_display_links = ('title',) # 'id' not in list_display_links
+ list_editable = ('content', )
+ form = StoryForm
+
+class OtherStory(models.Model):
+ title = models.CharField(max_length=100)
+ content = models.TextField()
+
+class OtherStoryAdmin(admin.ModelAdmin):
+ list_display = ('id', 'title', 'content')
+ list_display_links = ('title', 'id') # 'id' in list_display_links
+ list_editable = ('content', )
admin.site.register(Article, ArticleAdmin)
admin.site.register(CustomArticle, CustomArticleAdmin)
@@ -776,6 +798,8 @@ admin.site.register(FoodDelivery, FoodDeliveryAdmin)
admin.site.register(RowLevelChangePermissionModel, RowLevelChangePermissionModelAdmin)
admin.site.register(Paper, PaperAdmin)
admin.site.register(CoverLetter, CoverLetterAdmin)
+admin.site.register(Story, StoryAdmin)
+admin.site.register(OtherStory, OtherStoryAdmin)
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
# That way we cover all four cases:
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index ff97acd878..eca8adedc9 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -36,7 +36,7 @@ from models import (Article, BarAccount, CustomArticle, EmptyModel,
Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit,
Category, Post, Plot, FunkyTag, Chapter, Book, Promo, WorkHour, Employee,
Question, Answer, Inquisition, Actor, FoodDelivery,
- RowLevelChangePermissionModel, Paper, CoverLetter)
+ RowLevelChangePermissionModel, Paper, CoverLetter, Story, OtherStory)
class AdminViewBasicTest(TestCase):
@@ -1678,6 +1678,36 @@ class AdminViewListEditable(TestCase):
response = self.client.get('/test_admin/admin/admin_views/person/?%s' % IS_POPUP_VAR)
self.assertEqual(response.context['cl'].list_editable, ())
+ def test_pk_hidden_fields(self):
+ """ Ensure that hidden pk fields aren't displayed in the table body and
+ that their corresponding human-readable value is displayed instead.
+ Note that the hidden pk fields are in fact be displayed but
+ separately (not in the table), and only once.
+ Refs #12475.
+ """
+ Story.objects.create(title='The adventures of Guido', content='Once upon a time in Djangoland...')
+ Story.objects.create(title='Crouching Tiger, Hidden Python', content='The Python was sneaking into...')
+ response = self.client.get('/test_admin/admin/admin_views/story/')
+ self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
+ self.assertContains(response, 'id="id_form-1-id"', 1)
+ self.assertContains(response, '
\n\n
')
+ self.assertContains(response, '
1
', 1)
+ self.assertContains(response, '
2
', 1)
+
+ def test_pk_hidden_fields_with_list_display_links(self):
+ """ Similarly as test_pk_hidden_fields, but when the hidden pk fields are
+ referenced in list_display_links.
+ Refs #12475.
+ """
+ OtherStory.objects.create(title='The adventures of Guido', content='Once upon a time in Djangoland...')
+ OtherStory.objects.create(title='Crouching Tiger, Hidden Python', content='The Python was sneaking into...')
+ response = self.client.get('/test_admin/admin/admin_views/otherstory/')
+ self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
+ self.assertContains(response, 'id="id_form-1-id"', 1)
+ self.assertContains(response, '