summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorRodrigo Vieira <rodrigo.vieira@gmail.com>2026-04-22 18:53:27 -0300
committerJacob Walls <jacobtylerwalls@gmail.com>2026-04-22 22:22:55 -0400
commitfa2a3de6ede10b005fc2c1d23f4cffb53eaec425 (patch)
tree2fbb49592272a9b5b54ae9457ba0e2072dabe74f /tests
parenta586f03f36f511064f171c0e30f4ca2ebfd60085 (diff)
Fixed #10919 -- Added delete_confirmation_max_display to ModelAdmin.
The new ModelAdmin.delete_confirmation_max_display attribute allows limiting the number of related objects shown on the delete confirmation page. When the limit is reached, a "…and N more objects." message is shown. The feature relies on a new truncated_unordered_list template filter added to django.contrib.admin.templatetags.admin_filters. Thanks Jacob Tyler Walls for the review and guidance, Tobias McNulty for the report, and terminator14 for the solution suggested.
Diffstat (limited to 'tests')
-rw-r--r--tests/admin_views/customadmin.py16
-rw-r--r--tests/admin_views/test_actions.py13
-rw-r--r--tests/admin_views/tests.py20
-rw-r--r--tests/admin_views/urls.py1
-rw-r--r--tests/modeladmin/test_checks.py44
5 files changed, 93 insertions, 1 deletions
diff --git a/tests/admin_views/customadmin.py b/tests/admin_views/customadmin.py
index a63d24a9ee..9621cb4d52 100644
--- a/tests/admin_views/customadmin.py
+++ b/tests/admin_views/customadmin.py
@@ -60,8 +60,19 @@ class CustomPwdTemplateUserAdmin(UserAdmin):
class BookAdmin(admin.ModelAdmin):
+ delete_confirmation_max_display = 1
+
def get_deleted_objects(self, objs, request):
- return ["a deletable object"], {"books": 1}, set(), []
+ return (
+ ["a deletable object", "another object", "last object"],
+ {"books": 1},
+ set(),
+ [],
+ )
+
+
+class BookAdminZeroDisplay(BookAdmin):
+ delete_confirmation_max_display = 0
site = Admin2(name="admin2")
@@ -80,3 +91,6 @@ site.register(models.Simple, base_admin.AttributeErrorRaisingAdmin)
simple_site = Admin2(name="admin4")
simple_site.register(User, CustomPwdTemplateUserAdmin)
+
+zero_display_site = Admin2(name="admin_zero_display")
+zero_display_site.register(models.Book, BookAdminZeroDisplay)
diff --git a/tests/admin_views/test_actions.py b/tests/admin_views/test_actions.py
index c2c18e0b74..de5f5462a3 100644
--- a/tests/admin_views/test_actions.py
+++ b/tests/admin_views/test_actions.py
@@ -220,6 +220,19 @@ class AdminActionsTest(TestCase):
# BookAdmin.get_deleted_objects() returns custom text.
self.assertContains(response, "a deletable object")
+ def test_delete_selected_uses_delete_confirmation_max_display(self):
+ book = Book.objects.create(name="Test Book")
+ data = {
+ ACTION_CHECKBOX_NAME: [book.pk],
+ "action": "delete_selected",
+ "index": 0,
+ }
+ response = self.client.post(reverse("admin2:admin_views_book_changelist"), data)
+ self.assertContains(response, "a deletable object")
+ self.assertContains(response, "…and 2 more objects.")
+ self.assertNotContains(response, "another object")
+ self.assertNotContains(response, "last object")
+
def test_custom_function_mail_action(self):
"""A custom action may be defined in a function."""
action_data = {
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 399c485fea..1fdb90822c 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -4198,6 +4198,26 @@ class AdminViewDeletedObjectsTest(TestCase):
# BookAdmin.get_deleted_objects() returns custom text.
self.assertContains(response, "a deletable object")
+ def test_delete_view_uses_delete_confirmation_max_display(self):
+ book = Book.objects.create(name="Test Book")
+ response = self.client.get(
+ reverse("admin2:admin_views_book_delete", args=(book.pk,))
+ )
+ self.assertContains(response, "a deletable object")
+ self.assertContains(response, "…and 2 more objects.")
+ self.assertNotContains(response, "another object")
+ self.assertNotContains(response, "last object")
+
+ def test_delete_view_hides_objects_when_delete_confirmation_max_display_is_zero(
+ self,
+ ):
+ book = Book.objects.create(name="Test Book")
+ response = self.client.get(
+ reverse("admin_zero_display:admin_views_book_delete", args=(book.pk,))
+ )
+ self.assertNotContains(response, "<h2>Objects</h2>")
+ self.assertNotContains(response, 'id="deleted-objects"')
+
@override_settings(ROOT_URLCONF="admin_views.urls")
class TestGenericRelations(TestCase):
diff --git a/tests/admin_views/urls.py b/tests/admin_views/urls.py
index 3c43b8721d..0ebe688cfc 100644
--- a/tests/admin_views/urls.py
+++ b/tests/admin_views/urls.py
@@ -35,6 +35,7 @@ urlpatterns = [
path("test_admin/admin11/", admin.site11.urls),
path("test_admin/admin12/", admin.site12.urls),
path("test_admin/admin13/", admin.site13.urls),
+ path("test_admin/admin_zero_display/", customadmin.zero_display_site.urls),
path("test_admin/has_permission_admin/", custom_has_permission_admin.site.urls),
path("test_admin/autocomplete_admin/", autocomplete_site.urls),
# Shares the admin URL prefix.
diff --git a/tests/modeladmin/test_checks.py b/tests/modeladmin/test_checks.py
index c493148eb9..496e46e8b0 100644
--- a/tests/modeladmin/test_checks.py
+++ b/tests/modeladmin/test_checks.py
@@ -1020,6 +1020,50 @@ class ListMaxShowAllCheckTests(CheckTestCase):
self.assertIsValid(TestModelAdmin, ValidationTestModel)
+class DeleteConfirmationMaxObjectsCheckTests(CheckTestCase):
+ def test_not_integer(self):
+ class TestModelAdmin(ModelAdmin):
+ delete_confirmation_max_display = "hello"
+
+ self.assertIsInvalid(
+ TestModelAdmin,
+ ValidationTestModel,
+ (
+ "The value of "
+ "'delete_confirmation_max_display'"
+ " must be a non-negative integer or None."
+ ),
+ "admin.E131",
+ )
+
+ def test_negative_integer(self):
+ class TestModelAdmin(ModelAdmin):
+ delete_confirmation_max_display = -1
+
+ self.assertIsInvalid(
+ TestModelAdmin,
+ ValidationTestModel,
+ (
+ "The value of "
+ "'delete_confirmation_max_display'"
+ " must be a non-negative integer or None."
+ ),
+ "admin.E131",
+ )
+
+ def test_valid_case(self):
+ class TestModelAdmin(ModelAdmin):
+ delete_confirmation_max_display = 100
+
+ self.assertIsValid(TestModelAdmin, ValidationTestModel)
+
+ def test_valid_none(self):
+ class TestModelAdmin(ModelAdmin):
+ delete_confirmation_max_display = None
+
+ self.assertIsValid(TestModelAdmin, ValidationTestModel)
+
+
class SearchFieldsCheckTests(CheckTestCase):
def test_not_iterable(self):
class TestModelAdmin(ModelAdmin):