diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/admin_changelist/admin.py | 4 | ||||
| -rw-r--r-- | tests/admin_changelist/models.py | 5 | ||||
| -rw-r--r-- | tests/admin_changelist/tests.py | 58 | ||||
| -rw-r--r-- | tests/admin_checks/tests.py | 23 | ||||
| -rw-r--r-- | tests/admin_utils/tests.py | 12 | ||||
| -rw-r--r-- | tests/modeladmin/test_checks.py | 19 |
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", ) |
