summaryrefslogtreecommitdiff
path: root/django/contrib
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 /django/contrib
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 'django/contrib')
-rw-r--r--django/contrib/admin/actions.py1
-rw-r--r--django/contrib/admin/checks.py20
-rw-r--r--django/contrib/admin/options.py2
-rw-r--r--django/contrib/admin/templates/admin/delete_confirmation.html8
-rw-r--r--django/contrib/admin/templates/admin/delete_selected_confirmation.html8
-rw-r--r--django/contrib/admin/utils.py3
6 files changed, 34 insertions, 8 deletions
diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py
index 04a906542a..a2b92e492d 100644
--- a/django/contrib/admin/actions.py
+++ b/django/contrib/admin/actions.py
@@ -70,6 +70,7 @@ def delete_selected(modeladmin, request, queryset):
"subtitle": None,
"objects_name": str(objects_name),
"deletable_objects": [deletable_objects],
+ "delete_confirmation_max_display": modeladmin.delete_confirmation_max_display,
"model_count": dict(model_count).items(),
"queryset": queryset,
"perms_lacking": perms_needed,
diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py
index d14515e8a4..7baaa38a29 100644
--- a/django/contrib/admin/checks.py
+++ b/django/contrib/admin/checks.py
@@ -842,6 +842,7 @@ class ModelAdminChecks(BaseModelAdminChecks):
*self._check_search_fields(admin_obj),
*self._check_date_hierarchy(admin_obj),
*self._check_actions(admin_obj),
+ *self._check_delete_confirmation_max_display(admin_obj),
]
def _check_save_as(self, obj):
@@ -1089,6 +1090,25 @@ class ModelAdminChecks(BaseModelAdminChecks):
else:
return []
+ def _check_delete_confirmation_max_display(self, obj):
+ """Check that delete_confirmation_max_display is
+ a non-negative integer or None."""
+
+ if obj.delete_confirmation_max_display is None:
+ return []
+ if (
+ not isinstance(obj.delete_confirmation_max_display, int)
+ or obj.delete_confirmation_max_display < 0
+ ):
+ return must_be(
+ "a non-negative integer or None",
+ option="delete_confirmation_max_display",
+ obj=obj,
+ id="admin.E131",
+ )
+ else:
+ return []
+
def _check_list_per_page(self, obj):
"""Check that list_per_page is an integer."""
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 71d4a2d55c..e5502c42d5 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -662,6 +662,7 @@ class ModelAdmin(BaseModelAdmin):
add_form_template = None
change_form_template = None
change_list_template = None
+ delete_confirmation_max_display = None
delete_confirmation_template = None
delete_selected_confirmation_template = None
object_history_template = None
@@ -2287,6 +2288,7 @@ class ModelAdmin(BaseModelAdmin):
"object": obj,
"escaped_object": display_for_value(str(obj), EMPTY_VALUE_STRING),
"deleted_objects": deleted_objects,
+ "delete_confirmation_max_display": self.delete_confirmation_max_display,
"model_count": dict(model_count).items(),
"perms_lacking": perms_needed,
"protected": protected,
diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html
index 07823db373..1c34a84d95 100644
--- a/django/contrib/admin/templates/admin/delete_confirmation.html
+++ b/django/contrib/admin/templates/admin/delete_confirmation.html
@@ -23,19 +23,21 @@
{% if perms_lacking %}
{% block delete_forbidden %}
<p>{% blocktranslate %}Deleting the {{ object_name }} “{{ escaped_object }}” would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktranslate %}</p>
- <ul id="deleted-objects">{{ perms_lacking|unordered_list }}</ul>
+ <ul id="deleted-objects">{{ perms_lacking|truncated_unordered_list:delete_confirmation_max_display }}</ul>
{% endblock %}
{% elif protected %}
{% block delete_protected %}
<p>{% blocktranslate %}Deleting the {{ object_name }} “{{ escaped_object }}” would require deleting the following protected related objects:{% endblocktranslate %}</p>
- <ul id="deleted-objects">{{ protected|unordered_list }}</ul>
+ <ul id="deleted-objects">{{ protected|truncated_unordered_list:delete_confirmation_max_display }}</ul>
{% endblock %}
{% else %}
{% block delete_confirm %}
<p>{% blocktranslate %}Are you sure you want to delete the {{ object_name }} “{{ escaped_object }}”? All of the following related items will be deleted:{% endblocktranslate %}</p>
{% include "admin/includes/object_delete_summary.html" %}
+ {% if delete_confirmation_max_display is None or delete_confirmation_max_display > 0 %}
<h2>{% translate "Objects" %}</h2>
- <ul id="deleted-objects">{{ deleted_objects|unordered_list }}</ul>
+ <ul id="deleted-objects">{{ deleted_objects|truncated_unordered_list:delete_confirmation_max_display }}</ul>
+ {% endif %}
<form method="post">{% csrf_token %}
<div>
<input type="hidden" name="post" value="yes">
diff --git a/django/contrib/admin/templates/admin/delete_selected_confirmation.html b/django/contrib/admin/templates/admin/delete_selected_confirmation.html
index c6d0566883..40cfdbcc4c 100644
--- a/django/contrib/admin/templates/admin/delete_selected_confirmation.html
+++ b/django/contrib/admin/templates/admin/delete_selected_confirmation.html
@@ -1,5 +1,5 @@
{% extends "admin/base_site.html" %}
-{% load i18n l10n admin_urls static %}
+{% load i18n l10n admin_urls static admin_filters %}
{% block extrahead %}
{{ block.super }}
@@ -21,16 +21,16 @@
{% block content %}
{% if perms_lacking %}
<p>{% blocktranslate %}Deleting the selected {{ objects_name }} would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktranslate %}</p>
- <ul>{{ perms_lacking|unordered_list }}</ul>
+ <ul>{{ perms_lacking|truncated_unordered_list:delete_confirmation_max_display }}</ul>
{% elif protected %}
<p>{% blocktranslate %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktranslate %}</p>
- <ul>{{ protected|unordered_list }}</ul>
+ <ul>{{ protected|truncated_unordered_list:delete_confirmation_max_display }}</ul>
{% else %}
<p>{% blocktranslate %}Are you sure you want to delete the selected {{ objects_name }}? All of the following objects and their related items will be deleted:{% endblocktranslate %}</p>
{% include "admin/includes/object_delete_summary.html" %}
<h2>{% translate "Objects" %}</h2>
{% for deletable_object in deletable_objects %}
- <ul>{{ deletable_object|unordered_list }}</ul>
+ <ul>{{ deletable_object|truncated_unordered_list:delete_confirmation_max_display }}</ul>
{% endfor %}
<form method="post">{% csrf_token %}
<div>
diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py
index 3d5b023dea..ed6c41f52e 100644
--- a/django/contrib/admin/utils.py
+++ b/django/contrib/admin/utils.py
@@ -130,7 +130,8 @@ def get_deleted_objects(objs, request, admin_site):
must be a homogeneous iterable of objects (e.g. a QuerySet).
Return a nested list of strings suitable for display in the
- template with the ``unordered_list`` filter.
+ template with the ``unordered_list``
+ and ``truncated_unordered_list`` filters.
"""
from django.contrib.admin.options import EMPTY_VALUE_STRING