From 6df8fe3bc1879265958b8e59c637a4145995e93c Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 10 Aug 2026 16:21:42 -0400 Subject: [PATCH 1/2] Fixed #24580 -- Tested FK values with __html__() in ModelAdmin.list_display. --- tests/admin_changelist/models.py | 1 + tests/admin_changelist/tests.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/tests/admin_changelist/models.py b/tests/admin_changelist/models.py index d5d0633122cc..a6d3acfcc809 100644 --- a/tests/admin_changelist/models.py +++ b/tests/admin_changelist/models.py @@ -23,6 +23,7 @@ class Child(models.Model): class GrandChild(models.Model): parent = models.ForeignKey(Child, models.SET_NULL, editable=False, null=True) name = models.CharField(max_length=30, blank=True) + sibling = models.ForeignKey("self", models.SET_NULL, editable=False, null=True) def __str__(self): return self.name diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index f88982e10ba7..13f771ed9d90 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1793,6 +1793,20 @@ class ChildAdmin(admin.ModelAdmin): cl = m.get_changelist_instance(request) self.assertEqual(cl.get_ordering_field_columns(), {2: "asc"}) + def test_list_display_first_degree_relation(self): + parent = Parent.objects.create(name="I am your parent") + child = Child.objects.create(name="I am your child", parent=parent) + grand = GrandChild.objects.create(name="I am your grandchild", parent=child) + GrandChild.objects.create(name="has sibling", parent=child, sibling=grand) + + class GrandChildAdmin(admin.ModelAdmin): + list_display = ["name", "sibling"] + + m = GrandChildAdmin(GrandChild, custom_site) + request = self._mocked_authenticated_request("/grandchild/", self.superuser) + response = m.changelist_view(request) + self.assertContains(response, '

I am your grandchild') + def test_list_display_related_field_boolean_display(self): """ Related boolean fields (parent__is_active) display boolean icons. From 082b3df4067c3899dd4d57e8c2eca5baea9d07bb Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 10 Aug 2026 16:22:11 -0400 Subject: [PATCH 2/2] Fixed #37270 -- Fixed incorrect values for second-degree relations in ModelAdmin.list_display. --- .../contrib/admin/templatetags/admin_list.py | 7 ++-- docs/releases/6.1.1.txt | 4 +++ tests/admin_changelist/models.py | 1 + tests/admin_changelist/tests.py | 33 +++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 211a2533b3cd..8c9823ee200a 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -228,7 +228,7 @@ def link_in_col(is_first, field_name, cl): empty_value_display = getattr( attr, "empty_value_display", empty_value_display ) - # Find boolean fields on relations. + # Find a terminal field from a chain of relations. if f is None and isinstance(field_name, str) and LOOKUP_SEP in field_name: try: f = get_fields_from_path(cl.model, field_name)[-1] @@ -246,11 +246,10 @@ def link_in_col(is_first, field_name, cl): row_classes.append("nowrap") else: if isinstance(f.remote_field, models.ManyToOneRel): - field_val = getattr(result, f.name) - if field_val is None: + if value is None: result_repr = empty_value_display else: - result_repr = field_val + result_repr = value else: result_repr = display_for_field( value, diff --git a/docs/releases/6.1.1.txt b/docs/releases/6.1.1.txt index ec7b8d80899e..d5dbef5e790d 100644 --- a/docs/releases/6.1.1.txt +++ b/docs/releases/6.1.1.txt @@ -12,3 +12,7 @@ Bugfixes * Fixed a regression in Django 6.1 where the deprecation of double-dot variable lookups incorrectly applied to string and translated template literals containing two consecutive dots, such as ``{{ "a..b" }}`` (:ticket:`37257`). + +* Fixed a regression in Django 6.1 where :attr:`.ModelAdmin.list_display` + entries that traverse multiple relations using ``__`` could crash or display + incorrect values (:ticket:`37270`). diff --git a/tests/admin_changelist/models.py b/tests/admin_changelist/models.py index a6d3acfcc809..f805c738b7a6 100644 --- a/tests/admin_changelist/models.py +++ b/tests/admin_changelist/models.py @@ -47,6 +47,7 @@ class Band(models.Model): class Musician(models.Model): name = models.CharField(max_length=30) age = models.IntegerField(null=True, blank=True) + genre = models.ForeignKey(Genre, null=True, on_delete=models.SET_NULL) def __str__(self): return self.name diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 13f771ed9d90..fd1adb10b243 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1807,6 +1807,39 @@ class GrandChildAdmin(admin.ModelAdmin): response = m.changelist_view(request) self.assertContains(response, '

I am your grandchild') + def test_list_display_second_degree_relation(self): + parent = Parent.objects.create(name="I am your parent") + child = Child.objects.create(name="I am your child", parent=parent) + GrandChild.objects.create(name="I am your grandchild", parent=child) + + class GrandChildAdmin(admin.ModelAdmin): + list_display = ["name", "parent__parent"] + + m = GrandChildAdmin(GrandChild, custom_site) + request = self._mocked_authenticated_request("/grandchild/", self.superuser) + response = m.changelist_view(request) + self.assertNotContains(response, "Child object") + self.assertContains(response, "Parent object") + + def test_list_display_related_field_uses_display_for_field(self): + # Related fields must be rendered with display_for_field(), not + # display_for_value(), so field-specific formatting, such as FileField + # links, is preserved. + genre = Genre.objects.create(name="Rock", file="documents/test.txt") + Musician.objects.create(name="John", genre=genre) + + class MusicianAdmin(admin.ModelAdmin): + list_display = ["name", "genre__file"] + + m = MusicianAdmin(Musician, custom_site) + request = self._mocked_authenticated_request("/musician/", self.superuser) + response = m.changelist_view(request) + self.assertContains( + response, + 'documents/test.txt', + html=True, + ) + def test_list_display_related_field_boolean_display(self): """ Related boolean fields (parent__is_active) display boolean icons.