Skip to content

Commit fa2a3de

Browse files
rodbvjacobtylerwalls
authored andcommitted
Fixed django#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.
1 parent a586f03 commit fa2a3de

14 files changed

Lines changed: 150 additions & 9 deletions

File tree

django/contrib/admin/actions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def delete_selected(modeladmin, request, queryset):
7070
"subtitle": None,
7171
"objects_name": str(objects_name),
7272
"deletable_objects": [deletable_objects],
73+
"delete_confirmation_max_display": modeladmin.delete_confirmation_max_display,
7374
"model_count": dict(model_count).items(),
7475
"queryset": queryset,
7576
"perms_lacking": perms_needed,

django/contrib/admin/checks.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,7 @@ def check(self, admin_obj, **kwargs):
842842
*self._check_search_fields(admin_obj),
843843
*self._check_date_hierarchy(admin_obj),
844844
*self._check_actions(admin_obj),
845+
*self._check_delete_confirmation_max_display(admin_obj),
845846
]
846847

847848
def _check_save_as(self, obj):
@@ -1089,6 +1090,25 @@ def _check_list_select_related(self, obj):
10891090
else:
10901091
return []
10911092

1093+
def _check_delete_confirmation_max_display(self, obj):
1094+
"""Check that delete_confirmation_max_display is
1095+
a non-negative integer or None."""
1096+
1097+
if obj.delete_confirmation_max_display is None:
1098+
return []
1099+
if (
1100+
not isinstance(obj.delete_confirmation_max_display, int)
1101+
or obj.delete_confirmation_max_display < 0
1102+
):
1103+
return must_be(
1104+
"a non-negative integer or None",
1105+
option="delete_confirmation_max_display",
1106+
obj=obj,
1107+
id="admin.E131",
1108+
)
1109+
else:
1110+
return []
1111+
10921112
def _check_list_per_page(self, obj):
10931113
"""Check that list_per_page is an integer."""
10941114

django/contrib/admin/options.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ class ModelAdmin(BaseModelAdmin):
662662
add_form_template = None
663663
change_form_template = None
664664
change_list_template = None
665+
delete_confirmation_max_display = None
665666
delete_confirmation_template = None
666667
delete_selected_confirmation_template = None
667668
object_history_template = None
@@ -2287,6 +2288,7 @@ def _delete_view(self, request, object_id, extra_context):
22872288
"object": obj,
22882289
"escaped_object": display_for_value(str(obj), EMPTY_VALUE_STRING),
22892290
"deleted_objects": deleted_objects,
2291+
"delete_confirmation_max_display": self.delete_confirmation_max_display,
22902292
"model_count": dict(model_count).items(),
22912293
"perms_lacking": perms_needed,
22922294
"protected": protected,

django/contrib/admin/templates/admin/delete_confirmation.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,21 @@
2323
{% if perms_lacking %}
2424
{% block delete_forbidden %}
2525
<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>
26-
<ul id="deleted-objects">{{ perms_lacking|unordered_list }}</ul>
26+
<ul id="deleted-objects">{{ perms_lacking|truncated_unordered_list:delete_confirmation_max_display }}</ul>
2727
{% endblock %}
2828
{% elif protected %}
2929
{% block delete_protected %}
3030
<p>{% blocktranslate %}Deleting the {{ object_name }} “{{ escaped_object }}” would require deleting the following protected related objects:{% endblocktranslate %}</p>
31-
<ul id="deleted-objects">{{ protected|unordered_list }}</ul>
31+
<ul id="deleted-objects">{{ protected|truncated_unordered_list:delete_confirmation_max_display }}</ul>
3232
{% endblock %}
3333
{% else %}
3434
{% block delete_confirm %}
3535
<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>
3636
{% include "admin/includes/object_delete_summary.html" %}
37+
{% if delete_confirmation_max_display is None or delete_confirmation_max_display > 0 %}
3738
<h2>{% translate "Objects" %}</h2>
38-
<ul id="deleted-objects">{{ deleted_objects|unordered_list }}</ul>
39+
<ul id="deleted-objects">{{ deleted_objects|truncated_unordered_list:delete_confirmation_max_display }}</ul>
40+
{% endif %}
3941
<form method="post">{% csrf_token %}
4042
<div>
4143
<input type="hidden" name="post" value="yes">

django/contrib/admin/templates/admin/delete_selected_confirmation.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends "admin/base_site.html" %}
2-
{% load i18n l10n admin_urls static %}
2+
{% load i18n l10n admin_urls static admin_filters %}
33

44
{% block extrahead %}
55
{{ block.super }}
@@ -21,16 +21,16 @@
2121
{% block content %}
2222
{% if perms_lacking %}
2323
<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>
24-
<ul>{{ perms_lacking|unordered_list }}</ul>
24+
<ul>{{ perms_lacking|truncated_unordered_list:delete_confirmation_max_display }}</ul>
2525
{% elif protected %}
2626
<p>{% blocktranslate %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktranslate %}</p>
27-
<ul>{{ protected|unordered_list }}</ul>
27+
<ul>{{ protected|truncated_unordered_list:delete_confirmation_max_display }}</ul>
2828
{% else %}
2929
<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>
3030
{% include "admin/includes/object_delete_summary.html" %}
3131
<h2>{% translate "Objects" %}</h2>
3232
{% for deletable_object in deletable_objects %}
33-
<ul>{{ deletable_object|unordered_list }}</ul>
33+
<ul>{{ deletable_object|truncated_unordered_list:delete_confirmation_max_display }}</ul>
3434
{% endfor %}
3535
<form method="post">{% csrf_token %}
3636
<div>

django/contrib/admin/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ def get_deleted_objects(objs, request, admin_site):
130130
must be a homogeneous iterable of objects (e.g. a QuerySet).
131131
132132
Return a nested list of strings suitable for display in the
133-
template with the ``unordered_list`` filter.
133+
template with the ``unordered_list``
134+
and ``truncated_unordered_list`` filters.
134135
"""
135136
from django.contrib.admin.options import EMPTY_VALUE_STRING
136137

docs/ref/checks.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,8 @@ with the admin site:
816816
method for the ``<action>`` action.
817817
* **admin.E130**: ``__name__`` attributes of actions defined in
818818
``<modeladmin>`` must be unique. Name ``<name>`` is not unique.
819+
* **admin.E131**: The value of ``delete_confirmation_max_display`` must be a
820+
non-negative integer or ``None``.
819821

820822
``InlineModelAdmin``
821823
~~~~~~~~~~~~~~~~~~~~

docs/ref/contrib/admin/index.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,21 @@ default templates used by the :class:`ModelAdmin` views:
13861386
for displaying a confirmation page when deleting one or more objects. See
13871387
the :doc:`actions documentation</ref/contrib/admin/actions>`.
13881388

1389+
.. attribute:: ModelAdmin.delete_confirmation_max_display
1390+
1391+
.. versionadded:: 6.1
1392+
1393+
Set ``delete_confirmation_max_display`` to control how many objects are
1394+
displayed on the delete confirmation pages before truncating the remainder
1395+
with an ellipsis.
1396+
1397+
The limit applies to the total number of displayed objects across the
1398+
relationship hierarchy. This is purely a display setting and does not
1399+
affect the total number of objects retrieved from the database.
1400+
1401+
This applies to both :meth:`delete_view` and the ``delete_selected``
1402+
action. By default, this is ``None`` (no truncation).
1403+
13891404
.. attribute:: ModelAdmin.object_history_template
13901405

13911406
Path to a custom template, used by :meth:`history_view`.
@@ -2283,6 +2298,7 @@ adds some of its own (the shared features are actually defined in the
22832298
- :attr:`~ModelAdmin.fieldsets`
22842299
- :attr:`~ModelAdmin.fields`
22852300
- :attr:`~ModelAdmin.formfield_overrides`
2301+
- :attr:`~ModelAdmin.delete_confirmation_max_display`
22862302
- :attr:`~ModelAdmin.exclude`
22872303
- :attr:`~ModelAdmin.filter_horizontal`
22882304
- :attr:`~ModelAdmin.filter_vertical`

docs/releases/6.1.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ Minor features
9999
preserve :ref:`named groups <field-choices-named-groups>` (e.g.
100100
``choices=[("Group", [("1", "Item")]), ...]``).
101101

102+
* The :attr:`~django.contrib.admin.ModelAdmin.delete_confirmation_max_display`
103+
option allows customizing how many objects are displayed on admin delete
104+
confirmation pages before the remainder is truncated. The default is
105+
``None`` (no truncation).
106+
102107
* In order to improve accessibility of the admin change forms:
103108

104109
* Form fields are now shown below their respective labels instead of next to

tests/admin_views/customadmin.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,19 @@ class CustomPwdTemplateUserAdmin(UserAdmin):
6060

6161

6262
class BookAdmin(admin.ModelAdmin):
63+
delete_confirmation_max_display = 1
64+
6365
def get_deleted_objects(self, objs, request):
64-
return ["a deletable object"], {"books": 1}, set(), []
66+
return (
67+
["a deletable object", "another object", "last object"],
68+
{"books": 1},
69+
set(),
70+
[],
71+
)
72+
73+
74+
class BookAdminZeroDisplay(BookAdmin):
75+
delete_confirmation_max_display = 0
6576

6677

6778
site = Admin2(name="admin2")
@@ -80,3 +91,6 @@ def get_deleted_objects(self, objs, request):
8091

8192
simple_site = Admin2(name="admin4")
8293
simple_site.register(User, CustomPwdTemplateUserAdmin)
94+
95+
zero_display_site = Admin2(name="admin_zero_display")
96+
zero_display_site.register(models.Book, BookAdminZeroDisplay)

0 commit comments

Comments
 (0)