Fixed #20133 -- Added summary to admin deletion confirmation pages.

Thanks jonash for the suggestion and initial patch.
This commit is contained in:
areski 2014-08-14 13:17:52 +02:00 committed by Tim Graham
parent 2a4492aecb
commit 3021453285
8 changed files with 31 additions and 3 deletions

View File

@ -33,7 +33,7 @@ def delete_selected(modeladmin, request, queryset):
# Populate deletable_objects, a data structure of all related objects that # Populate deletable_objects, a data structure of all related objects that
# will also be deleted. # 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) queryset, opts, request.user, modeladmin.admin_site, using)
# The user has already confirmed the deletion. # The user has already confirmed the deletion.
@ -67,6 +67,7 @@ def delete_selected(modeladmin, request, queryset):
"title": title, "title": title,
"objects_name": objects_name, "objects_name": objects_name,
"deletable_objects": [deletable_objects], "deletable_objects": [deletable_objects],
"model_count": dict(model_count),
'queryset': queryset, 'queryset': queryset,
"perms_lacking": perms_needed, "perms_lacking": perms_needed,
"protected": protected, "protected": protected,

View File

@ -1606,7 +1606,7 @@ class ModelAdmin(BaseModelAdmin):
# Populate deleted_objects, a data structure of all related objects that # Populate deleted_objects, a data structure of all related objects that
# will also be deleted. # 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) [obj], opts, request.user, self.admin_site, using)
if request.POST: # The user has already confirmed the deletion. if request.POST: # The user has already confirmed the deletion.
@ -1631,6 +1631,7 @@ class ModelAdmin(BaseModelAdmin):
object_name=object_name, object_name=object_name,
object=obj, object=obj,
deleted_objects=deleted_objects, deleted_objects=deleted_objects,
model_count=dict(model_count),
perms_lacking=perms_needed, perms_lacking=perms_needed,
protected=protected, protected=protected,
opts=opts, opts=opts,

View File

@ -30,6 +30,8 @@
</ul> </ul>
{% else %} {% else %}
<p>{% 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 %}</p> <p>{% 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 %}</p>
{% include "admin/includes/object_delete_summary.html" %}
<h2>{% trans "Objects" %}</h2>
<ul>{{ deleted_objects|unordered_list }}</ul> <ul>{{ deleted_objects|unordered_list }}</ul>
<form action="" method="post">{% csrf_token %} <form action="" method="post">{% csrf_token %}
<div> <div>

View File

@ -29,6 +29,8 @@
</ul> </ul>
{% else %} {% else %}
<p>{% 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 %}</p> <p>{% 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 %}</p>
{% include "admin/includes/object_delete_summary.html" %}
<h2>{% trans "Objects" %}</h2>
{% for deletable_object in deletable_objects %} {% for deletable_object in deletable_objects %}
<ul>{{ deletable_object|unordered_list }}</ul> <ul>{{ deletable_object|unordered_list }}</ul>
{% endfor %} {% endfor %}

View File

@ -0,0 +1,7 @@
{% load i18n %}
<h2>{% trans "Summary" %}</h2>
<ul>
{% for model_name, object_count in model_count.items %}
<li>{{ model_name|capfirst }}: {{ object_count }}</li>
{% endfor %}
</ul>

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import defaultdict
import datetime import datetime
import decimal 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] 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): class NestedObjects(Collector):
@ -162,6 +163,7 @@ class NestedObjects(Collector):
super(NestedObjects, self).__init__(*args, **kwargs) super(NestedObjects, self).__init__(*args, **kwargs)
self.edges = {} # {from_instance: [to_instances]} self.edges = {} # {from_instance: [to_instances]}
self.protected = set() self.protected = set()
self.model_count = defaultdict(int)
def add_edge(self, source, target): def add_edge(self, source, target):
self.edges.setdefault(source, []).append(target) self.edges.setdefault(source, []).append(target)
@ -176,6 +178,7 @@ class NestedObjects(Collector):
self.add_edge(getattr(obj, related_name), obj) self.add_edge(getattr(obj, related_name), obj)
else: else:
self.add_edge(None, obj) self.add_edge(None, obj)
self.model_count[obj._meta.verbose_name_plural] += 1
try: try:
return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs)
except models.ProtectedError as e: except models.ProtectedError as e:

View File

@ -44,6 +44,10 @@ Minor features
to limit the ``list_filter`` choices to foreign objects which are attached to to limit the ``list_filter`` choices to foreign objects which are attached to
those from the ``ModelAdmin``. those from the ``ModelAdmin``.
* The :meth:`ModelAdmin.delete_view()
<django.contrib.admin.ModelAdmin.delete_view>` displays a summary of objects
to be deleted on the deletion confirmation page.
:mod:`django.contrib.auth` :mod:`django.contrib.auth`
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -1425,10 +1425,15 @@ class AdminViewPermissionsTest(TestCase):
self.client.get('/test_admin/admin/') self.client.get('/test_admin/admin/')
self.client.post(login_url, self.deleteuser_login) self.client.post(login_url, self.deleteuser_login)
response = self.client.get('/test_admin/admin/admin_views/section/1/delete/') response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
self.assertContains(response, "<h2>Summary</h2>")
self.assertContains(response, "<li>Articles: 3</li>")
# test response contains link to related Article # test response contains link to related Article
self.assertContains(response, "admin_views/article/1/") self.assertContains(response, "admin_views/article/1/")
response = self.client.get('/test_admin/admin/admin_views/article/1/delete/') response = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
self.assertContains(response, "admin_views/article/1/")
self.assertContains(response, "<h2>Summary</h2>")
self.assertContains(response, "<li>Articles: 1</li>")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict) post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
self.assertRedirects(post, '/test_admin/admin/') 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) confirmation = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
self.assertIsInstance(confirmation, TemplateResponse) self.assertIsInstance(confirmation, TemplateResponse)
self.assertContains(confirmation, "Are you sure you want to delete the selected subscribers?") self.assertContains(confirmation, "Are you sure you want to delete the selected subscribers?")
self.assertContains(confirmation, "<h2>Summary</h2>")
self.assertContains(confirmation, "<li>Subscribers: 3</li>")
self.assertContains(confirmation, "<li>External subscribers: 1</li>")
self.assertContains(confirmation, ACTION_CHECKBOX_NAME, count=2) self.assertContains(confirmation, ACTION_CHECKBOX_NAME, count=2)
self.client.post('/test_admin/admin/admin_views/subscriber/', delete_confirmation_data) self.client.post('/test_admin/admin/admin_views/subscriber/', delete_confirmation_data)
self.assertEqual(Subscriber.objects.count(), 0) self.assertEqual(Subscriber.objects.count(), 0)