Fixed #20133 -- Added summary to admin deletion confirmation pages.
Thanks jonash for the suggestion and initial patch.
This commit is contained in:
parent
2a4492aecb
commit
3021453285
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
|
@ -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:
|
||||||
|
|
|
@ -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`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue