From 283ea9e9e014adf0013c18700c36b98efa2f0aac Mon Sep 17 00:00:00 2001 From: SiHyunLee Date: Fri, 20 Feb 2026 23:43:41 +0900 Subject: Fixed #36127 -- Applied default empty display value to links otherwise containing only whitespace in admin. --- tests/admin_changelist/tests.py | 11 --- tests/admin_utils/tests.py | 13 +++ tests/admin_views/admin.py | 2 + tests/admin_views/tests.py | 184 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 196 insertions(+), 14 deletions(-) (limited to 'tests') diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index b067bc9660..f051849449 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1193,17 +1193,6 @@ class ChangeListTests(TestCase): "http://blues_history.com" % g.pk, ) - def test_blank_str_display_links(self): - self.client.force_login(self.superuser) - gc = GrandChild.objects.create(name=" ") - response = self.client.get( - reverse("admin:admin_changelist_grandchild_changelist") - ) - self.assertContains( - response, - '-' % gc.pk, - ) - def test_clear_all_filters_link(self): self.client.force_login(self.superuser) url = reverse("admin:auth_user_changelist") diff --git a/tests/admin_utils/tests.py b/tests/admin_utils/tests.py index 81c6f495f8..eced9d206e 100644 --- a/tests/admin_utils/tests.py +++ b/tests/admin_utils/tests.py @@ -313,6 +313,19 @@ class UtilsTests(SimpleTestCase): display_value = display_for_value(value, self.empty_value) self.assertEqual(display_value, self.empty_value) + def test_list_display_for_value_consecutive_whitespace(self): + cases = [ + (" ", "-empty-"), + (" cheeze", "cheeze"), + ("pizza ", "pizza"), + (" chicken ", "chicken"), + (mark_safe(" soy chicken "), " soy chicken "), + ] + for value, expect_display_value in cases: + with self.subTest(value=value): + display_value = display_for_value(value, self.empty_value) + self.assertEqual(display_value, expect_display_value) + def test_label_for_field(self): """ Tests for label_for_field diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 8e8f7a32cc..6f7cd79e50 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -709,6 +709,8 @@ class CoverLetterAdmin(admin.ModelAdmin): For testing fix for ticket #14529. """ + formfield_overrides = {models.CharField: {"strip": False}} + def get_queryset(self, request): return super().get_queryset(request).defer("date_written") diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index fa9d9a2dc6..ea657fbf9f 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -2862,7 +2862,9 @@ class AdminViewPermissionsTest(TestCase): self.assertContains(response, "

Select article to view

") self.assertEqual(response.context["title"], "Select article to view") response = self.client.get(article_change_url) - self.assertContains(response, "View article | Django site admin") + self.assertContains( + response, "- | View article | Django site admin" + ) self.assertContains(response, "

View article

") self.assertContains(response, "") self.assertContains( @@ -2891,7 +2893,7 @@ class AdminViewPermissionsTest(TestCase): self.assertEqual(response.context["title"], "Change article") self.assertContains( response, - "Change article | Django site admin", + "- | Change article | Django site admin", ) self.assertContains(response, "

Change article

") post = self.client.post(article_change_url, change_dict) @@ -3016,7 +3018,9 @@ class AdminViewPermissionsTest(TestCase): self.client.force_login(self.viewuser) response = self.client.get(change_url) self.assertEqual(response.context["title"], "View article") - self.assertContains(response, "View article | Django site admin") + self.assertContains( + response, "- | View article | Django site admin" + ) self.assertContains(response, "

View article

") self.assertContains( response, @@ -3608,6 +3612,180 @@ class AdminViewPermissionsTest(TestCase): ) +@override_settings(ROOT_URLCONF="admin_views.urls") +class AdminConsecutiveWhiteSpaceObjectDisplayTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create_superuser( + username=" ", password="secret", email="super@example.com" + ) + cls.obj = CoverLetter.objects.create(author=" ") + cls.change_link = reverse( + "admin:admin_views_coverletter_change", args=(cls.obj.pk,) + ) + + def setUp(self): + self.client.force_login(self.user) + + def test_display_consecutive_whitespace_object_in_breadcrumbs(self): + user_change_link = reverse("admin:auth_user_change", args=(self.user.pk,)) + cases = [ + ( + self.change_link, + '
  • ' + 'Cover letters
  • -
  • ', + ), + ( + reverse("admin:admin_views_coverletter_delete", args=(self.obj.pk,)), + f'
  • -
  • ' + "Delete
  • ", + ), + ( + reverse("admin:admin_views_coverletter_history", args=(self.obj.pk,)), + f'
  • -
  • ' + "History
  • ", + ), + ( + reverse("admin:auth_user_password_change", args=(self.user.pk,)), + f'
  • -
  • ' + "Change password
  • ", + ), + ] + for url, expected_breadcrumbs in cases: + with self.subTest(url=url, expected_breadcrumbs=expected_breadcrumbs): + response = self.client.get(url) + self.assertContains(response, expected_breadcrumbs, html=True) + + def test_display_consecutive_whitespace_object_in_delete_confirmation_page(self): + response = self.client.get( + reverse("admin:admin_views_coverletter_delete", args=(self.obj.pk,)) + ) + self.assertContains( + response, + "Are you sure you want to delete the cover letter “-”?", + ) + + # delete protected case + q = Question.objects.create(question=" ") + Answer.objects.create(question=q, answer="Because.") + response = self.client.get( + reverse("admin:admin_views_question_delete", args=(q.pk,)) + ) + self.assertContains( + response, + "Deleting the question “-” would require deleting the following protected " + "related objects", + ) + + # delete forbidden case + no_perms_user = User.objects.create_user( + username="no-perm", password="secret", is_staff=True + ) + no_perms_user.user_permissions.add( + get_perm(Question, get_permission_codename("view", Question._meta)) + ) + no_perms_user.user_permissions.add( + get_perm(Question, get_permission_codename("delete", Question._meta)) + ) + self.client.force_login(no_perms_user) + response = self.client.get( + reverse("admin:admin_views_question_delete", args=(q.pk,)) + ) + self.assertContains( + response, + "Deleting the question “-” would result in deleting related objects, " + "but your account doesn't have permission to delete " + "the following types of objects", + ) + + def test_display_consecutive_whitespace_object_in_changelist(self): + response = self.client.get(reverse("admin:admin_views_coverletter_changelist")) + self.assertContains(response, f'-') + + def test_display_consecutive_whitespace_object_in_deleted_object(self): + response = self.client.get( + reverse("admin:admin_views_coverletter_delete", args=(self.obj.pk,)) + ) + self.assertContains( + response, + '', + html=True, + ) + + def test_display_consecutive_whitespace_object_in_recent_action(self): + for action in [ADDITION, DELETION]: + LogEntry.objects.log_actions( + user_id=self.user.pk, + queryset=[self.obj], + action_flag=action, + change_message=[], + single_object=True, + ) + + response = self.client.get(reverse("admin:index")) + self.assertContains( + response, + '", + html=True, + ) + self.assertContains( + response, + '', + html=True, + ) + + def test_display_consecutive_whitespace_object_in_messages(self): + buttons = ["_save", "_continue", "_addanother"] + for button in buttons: + body = {"author": self.obj.author, button: "1"} + with self.subTest(obj=self.obj, button=button): + response = self.client.post( + reverse("admin:admin_views_coverletter_add"), body, follow=True + ) + latest_cl = CoverLetter.objects.latest("id") + change_link = reverse( + "admin:admin_views_coverletter_change", args=(latest_cl.pk,) + ) + self.assertContains( + response, + f'The cover letter “-” ' + "was added successfully.", + ) + response = self.client.post( + reverse( + "admin:admin_views_coverletter_change", args=(latest_cl.pk,) + ), + {**body, "author": " "}, + follow=True, + ) + self.assertContains( + response, + f'The cover letter “-” ' + "was changed successfully.", + ) + + new_obj = CoverLetter.objects.create(author=self.obj.author) + response = self.client.post( + reverse("admin:admin_views_coverletter_delete", args=(new_obj.pk,)), + {"post": "yes"}, + follow=True, + ) + self.assertContains(response, "The cover letter “-” was deleted successfully.") + + def test_display_consecutive_whitespace_object_in_sub_title(self): + response = self.client.get(self.change_link) + self.assertContains(response, "

    -

    ") + response = self.client.get( + reverse("admin:admin_views_coverletter_history", args=(self.obj.pk,)) + ) + self.assertContains(response, "

    Change history: -

    ") + + @override_settings( ROOT_URLCONF="admin_views.urls", TEMPLATES=[ -- cgit v1.3