From 777f216d555496798a1e65fd899b0f8a0349aeca Mon Sep 17 00:00:00 2001 From: Vasilis Aggelou Date: Mon, 4 Sep 2017 12:24:34 +0300 Subject: [PATCH] Fixed #15522 -- Added ModelAdmin.delete_queryset() to customize "delete selected objects" deletion. --- django/contrib/admin/actions.py | 2 +- django/contrib/admin/options.py | 4 ++++ docs/ref/contrib/admin/actions.txt | 5 +++-- docs/ref/contrib/admin/index.txt | 9 +++++++++ docs/releases/2.1.txt | 3 +++ tests/admin_views/admin.py | 4 ++++ tests/admin_views/test_actions.py | 14 ++++++++++++++ 7 files changed, 38 insertions(+), 3 deletions(-) diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index 34e809bebcb..49f969f6686 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -45,7 +45,7 @@ def delete_selected(modeladmin, request, queryset): for obj in queryset: obj_display = str(obj) modeladmin.log_deletion(request, obj, obj_display) - queryset.delete() + modeladmin.delete_queryset(request, queryset) modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % { "count": n, "items": model_ngettext(modeladmin.opts, n) }, messages.SUCCESS) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index d37615b1e54..090a9f6bc68 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1043,6 +1043,10 @@ class ModelAdmin(BaseModelAdmin): """ obj.delete() + def delete_queryset(self, request, queryset): + """Given a queryset, delete it from the database.""" + queryset.delete() + def save_formset(self, request, form, formset, change): """ Given an inline formset save it to the database. diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index c6f210b1de9..c23c647a72d 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -27,8 +27,9 @@ models. For example, here's the user module from Django's built-in has an important caveat: your model's ``delete()`` method will not be called. - If you wish to override this behavior, simply write a custom action which - accomplishes deletion in your preferred manner -- for example, by calling + If you wish to override this behavior, you can override + :meth:`.ModelAdmin.delete_queryset` or write a custom action which does + deletion in your preferred manner -- for example, by calling ``Model.delete()`` for each of the selected items. For more background on bulk deletion, see the documentation on :ref:`object diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 1f7b7f152e9..76224770368 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1391,6 +1391,15 @@ templates used by the :class:`ModelAdmin` views: operations. Call ``super().delete_model()`` to delete the object using :meth:`.Model.delete`. +.. method:: ModelAdmin.delete_queryset(request, queryset) + + .. versionadded:: 2.1 + + The ``delete_queryset()`` method is given the ``HttpRequest`` and a + ``QuerySet`` of objects to be deleted. Override this method to customize + the deletion process for the "delete selected objects" :doc:`action + `. + .. method:: ModelAdmin.save_formset(request, form, formset, change) The ``save_formset`` method is given the ``HttpRequest``, the parent diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index f349d1ed9a6..4d863801025 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -37,6 +37,9 @@ Minor features * jQuery is upgraded from version 2.2.3 to 3.2.1. +* The new :meth:`.ModelAdmin.delete_queryset` method allows customizing the + deletion process of the "delete selected objects" action. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 12087865b44..3cfefb74e42 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -232,6 +232,10 @@ class SubscriberAdmin(admin.ModelAdmin): actions = ['mail_admin'] action_form = MediaActionForm + def delete_queryset(self, request, queryset): + SubscriberAdmin.overridden = True + super().delete_queryset(request, queryset) + def mail_admin(self, request, selected): EmailMessage( 'Greetings from a ModelAdmin action', diff --git a/tests/admin_views/test_actions.py b/tests/admin_views/test_actions.py index 453de691ce6..b77714b32d3 100644 --- a/tests/admin_views/test_actions.py +++ b/tests/admin_views/test_actions.py @@ -9,6 +9,7 @@ from django.template.response import TemplateResponse from django.test import TestCase, override_settings from django.urls import reverse +from .admin import SubscriberAdmin from .forms import MediaActionForm from .models import ( Actor, Answer, ExternalSubscriber, Question, Subscriber, @@ -128,6 +129,19 @@ class AdminActionsTest(TestCase): # The page doesn't display a link to the nonexistent change page. self.assertContains(response, '
  • Unchangeable object: %s
  • ' % obj, 1, html=True) + def test_delete_queryset_hook(self): + delete_confirmation_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk], + 'action': 'delete_selected', + 'post': 'yes', + 'index': 0, + } + SubscriberAdmin.overridden = False + self.client.post(reverse('admin:admin_views_subscriber_changelist'), delete_confirmation_data) + # SubscriberAdmin.delete_queryset() sets overridden to True. + self.assertIs(SubscriberAdmin.overridden, True) + self.assertEqual(Subscriber.objects.all().count(), 0) + def test_custom_function_mail_action(self): """A custom action may be defined in a function.""" action_data = {