diff options
| author | Rodrigo Vieira <rodrigo.vieira@gmail.com> | 2026-04-22 18:53:27 -0300 |
|---|---|---|
| committer | Jacob Walls <jacobtylerwalls@gmail.com> | 2026-04-22 22:22:55 -0400 |
| commit | fa2a3de6ede10b005fc2c1d23f4cffb53eaec425 (patch) | |
| tree | 2fbb49592272a9b5b54ae9457ba0e2072dabe74f /django | |
| parent | a586f03f36f511064f171c0e30f4ca2ebfd60085 (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')
| -rw-r--r-- | django/contrib/admin/actions.py | 1 | ||||
| -rw-r--r-- | django/contrib/admin/checks.py | 20 | ||||
| -rw-r--r-- | django/contrib/admin/options.py | 2 | ||||
| -rw-r--r-- | django/contrib/admin/templates/admin/delete_confirmation.html | 8 | ||||
| -rw-r--r-- | django/contrib/admin/templates/admin/delete_selected_confirmation.html | 8 | ||||
| -rw-r--r-- | django/contrib/admin/utils.py | 3 |
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 |
