Browse Source

[5.0.x] Fixed #35606, Refs #34045 -- Fixed rendering of ModelAdmin.action_checkbox for models with a __html__ method.

Thank you Claude Paroz for the report.

Regression in 85366fbca723c9b37d0ac9db1d44e3f1cb188db2.

Backport of 182f262b15882649bbc39d769f9b721cf3660f6f from main.
Hisham Mahmood 8 months ago
parent
commit
c3d3af8ea3

+ 1 - 0
AUTHORS

@@ -405,6 +405,7 @@ answer newbie questions, and generally made Django that much better:
     Himanshu Chauhan <hchauhan1404@outlook.com>
     hipertracker@gmail.com
     Hiroki Kiyohara <hirokiky@gmail.com>
+    Hisham Mahmood <hishammahmood41@gmail.com>
     Honza Král <honza.kral@gmail.com>
     Horst Gutmann <zerok@zerokspot.com>
     Hugo Osvaldo Barrera <hugo@barrera.io>

+ 3 - 1
django/contrib/admin/options.py

@@ -996,7 +996,9 @@ class ModelAdmin(BaseModelAdmin):
         """
         attrs = {
             "class": "action-select",
-            "aria-label": format_html(_("Select this object for an action - {}"), obj),
+            "aria-label": format_html(
+                _("Select this object for an action - {}"), str(obj)
+            ),
         }
         checkbox = forms.CheckboxInput(attrs, lambda value: False)
         return checkbox.render(helpers.ACTION_CHECKBOX_NAME, str(obj.pk))

+ 4 - 0
docs/releases/5.0.8.txt

@@ -11,3 +11,7 @@ Bugfixes
 
 * Added missing validation for ``UniqueConstraint(nulls_distinct=False)`` when
   using ``*expressions`` (:ticket:`35594`).
+
+* Fixed a regression in Django 5.0 where ``ModelAdmin.action_checkbox`` could
+  break the admin changelist HTML page when rendering a model instance with a
+  ``__html__`` method (:ticket:`35606`).

+ 8 - 1
tests/admin_changelist/admin.py

@@ -3,7 +3,7 @@ from django.contrib.auth.admin import UserAdmin
 from django.contrib.auth.models import User
 from django.core.paginator import Paginator
 
-from .models import Band, Child, Event, Parent, ProxyUser, Swallow
+from .models import Band, Child, Event, GrandChild, Parent, ProxyUser, Swallow
 
 site = admin.AdminSite(name="admin")
 
@@ -53,6 +53,13 @@ class ChildAdmin(admin.ModelAdmin):
         return super().get_queryset(request).select_related("parent")
 
 
+class GrandChildAdmin(admin.ModelAdmin):
+    list_display = ["name"]
+
+
+site.register(GrandChild, GrandChildAdmin)
+
+
 class CustomPaginationAdmin(ChildAdmin):
     paginator = CustomPaginator
 

+ 11 - 0
tests/admin_changelist/models.py

@@ -19,6 +19,17 @@ class Child(models.Model):
     age = models.IntegerField(null=True, blank=True)
 
 
+class GrandChild(models.Model):
+    parent = models.ForeignKey(Child, models.SET_NULL, editable=False, null=True)
+    name = models.CharField(max_length=30, blank=True)
+
+    def __str__(self):
+        return self.name
+
+    def __html__(self):
+        return f'<h2 class="main">{self.name}</h2>'
+
+
 class Genre(models.Model):
     name = models.CharField(max_length=20)
 

+ 23 - 0
tests/admin_changelist/tests.py

@@ -43,6 +43,7 @@ from .admin import (
     EmptyValueChildAdmin,
     EventAdmin,
     FilteredChildAdmin,
+    GrandChildAdmin,
     GroupAdmin,
     InvitationAdmin,
     NoListDisplayLinksParentAdmin,
@@ -62,6 +63,7 @@ from .models import (
     CustomIdUser,
     Event,
     Genre,
+    GrandChild,
     Group,
     Invitation,
     Membership,
@@ -341,6 +343,27 @@ class ChangeListTests(TestCase):
             table_output,
         )
 
+    def test_action_checkbox_for_model_with_dunder_html(self):
+        grandchild = GrandChild.objects.create(name="name")
+        request = self._mocked_authenticated_request("/grandchild/", self.superuser)
+        m = GrandChildAdmin(GrandChild, custom_site)
+        cl = m.get_changelist_instance(request)
+        cl.formset = None
+        template = Template(
+            "{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}"
+        )
+        context = Context({"cl": cl, "opts": GrandChild._meta})
+        table_output = template.render(context)
+        link = reverse(
+            "admin:admin_changelist_grandchild_change", args=(grandchild.id,)
+        )
+        row_html = build_tbody_html(grandchild, link, "")
+        self.assertNotEqual(
+            table_output.find(row_html),
+            -1,
+            "Failed to find expected row element: %s" % table_output,
+        )
+
     def test_result_list_editable_html(self):
         """
         Regression tests for #11791: Inclusion tag result_list generates a