diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index 158c436c71..3a841bb6c8 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -33,7 +33,7 @@ def delete_selected(modeladmin, request, queryset): # Populate deletable_objects, a data structure of all related objects that # will also be deleted. - deletable_objects, perms_needed, protected = get_deleted_objects( + deletable_objects, model_count, perms_needed, protected = get_deleted_objects( queryset, opts, request.user, modeladmin.admin_site, using) # The user has already confirmed the deletion. @@ -67,6 +67,7 @@ def delete_selected(modeladmin, request, queryset): "title": title, "objects_name": objects_name, "deletable_objects": [deletable_objects], + "model_count": dict(model_count), 'queryset': queryset, "perms_lacking": perms_needed, "protected": protected, diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index f304be0b81..3b429fbaed 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1606,7 +1606,7 @@ class ModelAdmin(BaseModelAdmin): # Populate deleted_objects, a data structure of all related objects that # will also be deleted. - (deleted_objects, perms_needed, protected) = get_deleted_objects( + (deleted_objects, model_count, perms_needed, protected) = get_deleted_objects( [obj], opts, request.user, self.admin_site, using) if request.POST: # The user has already confirmed the deletion. @@ -1631,6 +1631,7 @@ class ModelAdmin(BaseModelAdmin): object_name=object_name, object=obj, deleted_objects=deleted_objects, + model_count=dict(model_count), perms_lacking=perms_needed, protected=protected, opts=opts, diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index 6487280d3a..c3816408df 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -30,6 +30,8 @@ {% else %}

{% blocktrans with escaped_object=object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_object }}"? All of the following related items will be deleted:{% endblocktrans %}

+ {% include "admin/includes/object_delete_summary.html" %} +

{% trans "Objects" %}

{% csrf_token %}
diff --git a/django/contrib/admin/templates/admin/delete_selected_confirmation.html b/django/contrib/admin/templates/admin/delete_selected_confirmation.html index 731c66061d..154e779c49 100644 --- a/django/contrib/admin/templates/admin/delete_selected_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_selected_confirmation.html @@ -29,6 +29,8 @@ {% else %}

{% blocktrans %}Are you sure you want to delete the selected {{ objects_name }}? All of the following objects and their related items will be deleted:{% endblocktrans %}

+ {% include "admin/includes/object_delete_summary.html" %} +

{% trans "Objects" %}

{% for deletable_object in deletable_objects %} {% endfor %} diff --git a/django/contrib/admin/templates/admin/includes/object_delete_summary.html b/django/contrib/admin/templates/admin/includes/object_delete_summary.html new file mode 100644 index 0000000000..a2e78ed7a1 --- /dev/null +++ b/django/contrib/admin/templates/admin/includes/object_delete_summary.html @@ -0,0 +1,7 @@ +{% load i18n %} +

{% trans "Summary" %}

+ \ No newline at end of file diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index 3c3ef3bbd9..d6756332d3 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from collections import defaultdict import datetime import decimal @@ -154,7 +155,7 @@ def get_deleted_objects(objs, opts, user, admin_site, using): protected = [format_callback(obj) for obj in collector.protected] - return to_delete, perms_needed, protected + return to_delete, collector.model_count, perms_needed, protected class NestedObjects(Collector): @@ -162,6 +163,7 @@ class NestedObjects(Collector): super(NestedObjects, self).__init__(*args, **kwargs) self.edges = {} # {from_instance: [to_instances]} self.protected = set() + self.model_count = defaultdict(int) def add_edge(self, source, target): self.edges.setdefault(source, []).append(target) @@ -176,6 +178,7 @@ class NestedObjects(Collector): self.add_edge(getattr(obj, related_name), obj) else: self.add_edge(None, obj) + self.model_count[obj._meta.verbose_name_plural] += 1 try: return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) except models.ProtectedError as e: diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 5979d79d1d..ae8c1e4d91 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -44,6 +44,10 @@ Minor features to limit the ``list_filter`` choices to foreign objects which are attached to those from the ``ModelAdmin``. +* The :meth:`ModelAdmin.delete_view() + ` displays a summary of objects + to be deleted on the deletion confirmation page. + :mod:`django.contrib.auth` ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index dfa5a85ae6..adc49a5db0 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1425,10 +1425,15 @@ class AdminViewPermissionsTest(TestCase): self.client.get('/test_admin/admin/') self.client.post(login_url, self.deleteuser_login) response = self.client.get('/test_admin/admin/admin_views/section/1/delete/') + self.assertContains(response, "

Summary

") + self.assertContains(response, "
  • Articles: 3
  • ") # test response contains link to related Article self.assertContains(response, "admin_views/article/1/") response = self.client.get('/test_admin/admin/admin_views/article/1/delete/') + self.assertContains(response, "admin_views/article/1/") + self.assertContains(response, "

    Summary

    ") + self.assertContains(response, "
  • Articles: 1
  • ") self.assertEqual(response.status_code, 200) post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict) self.assertRedirects(post, '/test_admin/admin/') @@ -2547,6 +2552,9 @@ class AdminActionsTest(TestCase): confirmation = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data) self.assertIsInstance(confirmation, TemplateResponse) self.assertContains(confirmation, "Are you sure you want to delete the selected subscribers?") + self.assertContains(confirmation, "

    Summary

    ") + self.assertContains(confirmation, "
  • Subscribers: 3
  • ") + self.assertContains(confirmation, "
  • External subscribers: 1
  • ") self.assertContains(confirmation, ACTION_CHECKBOX_NAME, count=2) self.client.post('/test_admin/admin/admin_views/subscriber/', delete_confirmation_data) self.assertEqual(Subscriber.objects.count(), 0)