Fixed #17962 -- Added ModelAdmin.get_deleted_objects().

This commit is contained in:
Becky Smith 2017-03-30 10:13:15 +01:00 committed by Tim Graham
parent 9822d88ca0
commit 8116e588db
9 changed files with 80 additions and 8 deletions

View File

@ -677,6 +677,7 @@ answer newbie questions, and generally made Django that much better:
Raphaël Barrois <raphael.barrois@m4x.org>
Raphael Michel <mail@raphaelmichel.de>
Raúl Cumplido <raulcumplido@gmail.com>
Rebecca Smith <rebkwok@gmail.com>
Remco Wendt <remco.wendt@gmail.com>
Renaud Parent <renaud.parent@gmail.com>
Renbi Yu <averybigant@gmail.com>

View File

@ -4,7 +4,7 @@ Built-in, globally-available admin actions.
from django.contrib import messages
from django.contrib.admin import helpers
from django.contrib.admin.utils import get_deleted_objects, model_ngettext
from django.contrib.admin.utils import model_ngettext
from django.core.exceptions import PermissionDenied
from django.template.response import TemplateResponse
from django.utils.translation import gettext as _, gettext_lazy
@ -29,9 +29,7 @@ def delete_selected(modeladmin, request, queryset):
# Populate deletable_objects, a data structure of all related objects that
# will also be deleted.
deletable_objects, model_count, perms_needed, protected = get_deleted_objects(
queryset, request.user, modeladmin.admin_site,
)
deletable_objects, model_count, perms_needed, protected = modeladmin.get_deleted_objects(queryset, request)
# The user has already confirmed the deletion.
# Do the deletion and return None to display the change list view again.

View File

@ -1728,6 +1728,13 @@ class ModelAdmin(BaseModelAdmin):
'admin/change_list.html'
], context)
def get_deleted_objects(self, objs, request):
"""
Hook for customizing the delete process for the delete view and the
"delete selected" action.
"""
return get_deleted_objects(objs, request.user, self.admin_site)
@csrf_protect_m
def delete_view(self, request, object_id, extra_context=None):
with transaction.atomic(using=router.db_for_write(self.model)):
@ -1752,9 +1759,7 @@ class ModelAdmin(BaseModelAdmin):
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
deleted_objects, model_count, perms_needed, protected = get_deleted_objects(
[obj], request.user, self.admin_site,
)
deleted_objects, model_count, perms_needed, protected = self.get_deleted_objects([obj], request)
if request.POST and not protected: # The user has confirmed the deletion.
if perms_needed:

View File

@ -1998,6 +1998,36 @@ templates used by the :class:`ModelAdmin` views:
def get_changeform_initial_data(self, request):
return {'name': 'custom_initial_value'}
.. method:: ModelAdmin.get_deleted_objects(objs, request)
.. versionadded:: 2.1
A hook for customizing the deletion process of the :meth:`delete_view` and
the "delete selected" :doc:`action <actions>`.
The ``objs`` argument is a homogeneous iterable of objects (a ``QuerySet``
or a list of model instances) to be deleted, and ``request`` is the
:class:`~django.http.HttpRequest`.
This method must return a 4-tuple of
``(deleted_objects, model_count, perms_needed, protected)``.
``deleted_objects`` is a list of strings representing all the objects that
will be deleted. If there are any related objects to be deleted, the list
is nested and includes those related objects. The list is formatted in the
template using the :tfilter:`unordered_list` filter.
``model_count`` is a dictionary mapping each model's
:attr:`~django.db.models.Options.verbose_name_plural` to the number of
objects that will be deleted.
``perms_needed`` is a set of :attr:`~django.db.models.Options.verbose_name`\s
of the models that the user doesn't have permission to delete.
``protected`` is a list of strings representing of all the protected
related objects that can't be deleted. The list is displayed in the
template.
Other methods
~~~~~~~~~~~~~

View File

@ -50,6 +50,9 @@ Minor features
* The ``admin_order_field`` attribute for elements in
:attr:`.ModelAdmin.list_display` may now be a query expression.
* The new :meth:`.ModelAdmin.get_deleted_objects()` method allows customizing
the deletion process of the delete view and the "delete selected" action.
:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -46,9 +46,15 @@ class CustomPwdTemplateUserAdmin(UserAdmin):
change_user_password_template = ['admin/auth/user/change_password.html'] # a list, to test fix for #18697
class BookAdmin(admin.ModelAdmin):
def get_deleted_objects(self, objs, request):
return ['a deletable object'], {'books': 1}, set(), []
site = Admin2(name="admin2")
site.register(models.Article, base_admin.ArticleAdmin)
site.register(models.Book, BookAdmin)
site.register(models.Section, inlines=[base_admin.ArticleInline], search_fields=['name'])
site.register(models.Thing, base_admin.ThingAdmin)
site.register(models.Fabric, base_admin.FabricAdmin)

View File

@ -12,7 +12,7 @@ from django.urls import reverse
from .admin import SubscriberAdmin
from .forms import MediaActionForm
from .models import (
Actor, Answer, ExternalSubscriber, Question, Subscriber,
Actor, Answer, Book, ExternalSubscriber, Question, Subscriber,
UnchangeableObject,
)
@ -153,6 +153,18 @@ class AdminActionsTest(TestCase):
self.assertIs(SubscriberAdmin.overridden, True)
self.assertEqual(Subscriber.objects.all().count(), 0)
def test_delete_selected_uses_get_deleted_objects(self):
"""The delete_selected action uses ModelAdmin.get_deleted_objects()."""
book = Book.objects.create(name='Test Book')
data = {
ACTION_CHECKBOX_NAME: [book.pk],
'action': 'delete_selected',
'index': 0,
}
response = self.client.post(reverse('admin2:admin_views_book_changelist'), data)
# BookAdmin.get_deleted_objects() returns custom text.
self.assertContains(response, 'a deletable object')
def test_custom_function_mail_action(self):
"""A custom action may be defined in a function."""
action_data = {

View File

@ -2357,6 +2357,13 @@ class AdminViewDeletedObjectsTest(TestCase):
response = self.client.get(reverse('admin:admin_views_bookmark_delete', args=(bookmark.pk,)))
self.assertContains(response, should_contain)
def test_delete_view_uses_get_deleted_objects(self):
"""The delete view uses ModelAdmin.get_deleted_objects()."""
book = Book.objects.create(name='Test Book')
response = self.client.get(reverse('admin2:admin_views_book_delete', args=(book.pk,)))
# BookAdmin.get_deleted_objects() returns custom text.
self.assertContains(response, 'a deletable object')
@override_settings(ROOT_URLCONF='admin_views.urls')
class TestGenericRelations(TestCase):

View File

@ -666,6 +666,16 @@ class ModelAdminTests(TestCase):
finally:
self.site.unregister(Band)
def test_get_deleted_objects(self):
mock_request = MockRequest()
mock_request.user = User.objects.create_superuser(username='bob', email='bob@test.com', password='test')
ma = ModelAdmin(Band, self.site)
deletable_objects, model_count, perms_needed, protected = ma.get_deleted_objects([self.band], request)
self.assertEqual(deletable_objects, ['Band: The Doors'])
self.assertEqual(model_count, {'bands': 1})
self.assertEqual(perms_needed, set())
self.assertEqual(protected, [])
class ModelAdminPermissionTests(SimpleTestCase):