summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/admin_changelist/admin.py4
-rw-r--r--tests/admin_changelist/models.py5
-rw-r--r--tests/admin_changelist/tests.py58
-rw-r--r--tests/admin_checks/tests.py23
-rw-r--r--tests/admin_utils/tests.py12
-rw-r--r--tests/modeladmin/test_checks.py19
6 files changed, 118 insertions, 3 deletions
diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py
index 8ffc45e391..3e6009b0c5 100644
--- a/tests/admin_changelist/admin.py
+++ b/tests/admin_changelist/admin.py
@@ -53,6 +53,10 @@ class ChildAdmin(admin.ModelAdmin):
return super().get_queryset(request).select_related("parent")
+class GrandChildAdmin(admin.ModelAdmin):
+ list_display = ["name", "parent__name", "parent__parent__name"]
+
+
class CustomPaginationAdmin(ChildAdmin):
paginator = CustomPaginator
diff --git a/tests/admin_changelist/models.py b/tests/admin_changelist/models.py
index aa4656e93e..290a3ea4ec 100644
--- a/tests/admin_changelist/models.py
+++ b/tests/admin_changelist/models.py
@@ -19,6 +19,11 @@ 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)
+
+
class Genre(models.Model):
name = models.CharField(max_length=20)
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index b4739b572d..72fac8cd61 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -42,6 +42,7 @@ from .admin import (
EmptyValueChildAdmin,
EventAdmin,
FilteredChildAdmin,
+ GrandChildAdmin,
GroupAdmin,
InvitationAdmin,
NoListDisplayLinksParentAdmin,
@@ -61,6 +62,7 @@ from .models import (
CustomIdUser,
Event,
Genre,
+ GrandChild,
Group,
Invitation,
Membership,
@@ -1634,6 +1636,62 @@ class ChangeListTests(TestCase):
response, f'0 results (<a href="{href}">1 total</a>)'
)
+ def test_list_display_related_field(self):
+ parent = Parent.objects.create(name="I am your father")
+ child = Child.objects.create(name="I am your child", parent=parent)
+ GrandChild.objects.create(name="I am your grandchild", parent=child)
+ request = self._mocked_authenticated_request("/grandchild/", self.superuser)
+
+ m = GrandChildAdmin(GrandChild, custom_site)
+ response = m.changelist_view(request)
+ self.assertContains(response, parent.name)
+ self.assertContains(response, child.name)
+
+ def test_list_display_related_field_null(self):
+ GrandChild.objects.create(name="I am parentless", parent=None)
+ request = self._mocked_authenticated_request("/grandchild/", self.superuser)
+
+ m = GrandChildAdmin(GrandChild, custom_site)
+ response = m.changelist_view(request)
+ self.assertContains(response, '<td class="field-parent__name">-</td>')
+ self.assertContains(response, '<td class="field-parent__parent__name">-</td>')
+
+ def test_list_display_related_field_ordering(self):
+ parent_a = Parent.objects.create(name="Alice")
+ parent_z = Parent.objects.create(name="Zara")
+ Child.objects.create(name="Alice's child", parent=parent_a)
+ Child.objects.create(name="Zara's child", parent=parent_z)
+
+ class ChildAdmin(admin.ModelAdmin):
+ list_display = ["name", "parent__name"]
+ list_per_page = 1
+
+ m = ChildAdmin(Child, custom_site)
+
+ # Order ascending.
+ request = self._mocked_authenticated_request("/grandchild/?o=1", self.superuser)
+ response = m.changelist_view(request)
+ self.assertContains(response, parent_a.name)
+ self.assertNotContains(response, parent_z.name)
+
+ # Order descending.
+ request = self._mocked_authenticated_request(
+ "/grandchild/?o=-1", self.superuser
+ )
+ response = m.changelist_view(request)
+ self.assertNotContains(response, parent_a.name)
+ self.assertContains(response, parent_z.name)
+
+ def test_list_display_related_field_ordering_fields(self):
+ class ChildAdmin(admin.ModelAdmin):
+ list_display = ["name", "parent__name"]
+ ordering = ["parent__name"]
+
+ m = ChildAdmin(Child, custom_site)
+ request = self._mocked_authenticated_request("/", self.superuser)
+ cl = m.get_changelist_instance(request)
+ self.assertEqual(cl.get_ordering_field_columns(), {2: "asc"})
+
class GetAdminLogTests(TestCase):
def test_custom_user_pk_not_named_id(self):
diff --git a/tests/admin_checks/tests.py b/tests/admin_checks/tests.py
index d2d1eb219e..6ca5d6d925 100644
--- a/tests/admin_checks/tests.py
+++ b/tests/admin_checks/tests.py
@@ -1009,3 +1009,26 @@ class SystemChecksTestCase(SimpleTestCase):
self.assertEqual(errors, [])
finally:
Book._meta.apps.ready = True
+
+ def test_related_field_list_display(self):
+ class SongAdmin(admin.ModelAdmin):
+ list_display = ["pk", "original_release", "album__title"]
+
+ errors = SongAdmin(Song, AdminSite()).check()
+ self.assertEqual(errors, [])
+
+ def test_related_field_list_display_wrong_field(self):
+ class SongAdmin(admin.ModelAdmin):
+ list_display = ["pk", "original_release", "album__hello"]
+
+ errors = SongAdmin(Song, AdminSite()).check()
+ expected = [
+ checks.Error(
+ "The value of 'list_display[2]' refers to 'album__hello', which is not "
+ "a callable or attribute of 'SongAdmin', or an attribute, method, or "
+ "field on 'admin_checks.Song'.",
+ obj=SongAdmin,
+ id="admin.E108",
+ )
+ ]
+ self.assertEqual(errors, expected)
diff --git a/tests/admin_utils/tests.py b/tests/admin_utils/tests.py
index 067b47198d..56d46324e0 100644
--- a/tests/admin_utils/tests.py
+++ b/tests/admin_utils/tests.py
@@ -137,6 +137,7 @@ class UtilsTests(SimpleTestCase):
(simple_function, SIMPLE_FUNCTION),
("test_from_model", article.test_from_model()),
("non_field", INSTANCE_ATTRIBUTE),
+ ("site__domain", SITE_NAME),
)
mock_admin = MockModelAdmin()
@@ -294,6 +295,17 @@ class UtilsTests(SimpleTestCase):
self.assertEqual(label_for_field(lambda x: "nothing", Article), "--")
self.assertEqual(label_for_field("site_id", Article), "Site id")
+ # The correct name and attr are returned when `__` is in the field name.
+ self.assertEqual(label_for_field("site__domain", Article), "Site domain")
+ self.assertEqual(
+ label_for_field("site__domain", Article, return_attr=True),
+ ("Site domain", Site._meta.get_field("domain")),
+ )
+
+ def test_label_for_field_failed_lookup(self):
+ msg = "Unable to lookup 'site__unknown' on Article"
+ with self.assertRaisesMessage(AttributeError, msg):
+ label_for_field("site__unknown", Article)
class MockModelAdmin:
@admin.display(description="not Really the Model")
diff --git a/tests/modeladmin/test_checks.py b/tests/modeladmin/test_checks.py
index 73777f05ab..f767a6c92b 100644
--- a/tests/modeladmin/test_checks.py
+++ b/tests/modeladmin/test_checks.py
@@ -69,7 +69,7 @@ class RawIdCheckTests(CheckTestCase):
def test_missing_field(self):
class TestModelAdmin(ModelAdmin):
- raw_id_fields = ("non_existent_field",)
+ raw_id_fields = ["non_existent_field"]
self.assertIsInvalid(
TestModelAdmin,
@@ -602,8 +602,21 @@ class ListDisplayTests(CheckTestCase):
TestModelAdmin,
ValidationTestModel,
"The value of 'list_display[0]' refers to 'non_existent_field', "
- "which is not a callable, an attribute of 'TestModelAdmin', "
- "or an attribute or method on 'modeladmin.ValidationTestModel'.",
+ "which is not a callable or attribute of 'TestModelAdmin', "
+ "or an attribute, method, or field on 'modeladmin.ValidationTestModel'.",
+ "admin.E108",
+ )
+
+ def test_missing_related_field(self):
+ class TestModelAdmin(ModelAdmin):
+ list_display = ("band__non_existent_field",)
+
+ self.assertIsInvalid(
+ TestModelAdmin,
+ ValidationTestModel,
+ "The value of 'list_display[0]' refers to 'band__non_existent_field', "
+ "which is not a callable or attribute of 'TestModelAdmin', "
+ "or an attribute, method, or field on 'modeladmin.ValidationTestModel'.",
"admin.E108",
)