Browse Source

Fixed #15522 -- Added ModelAdmin.delete_queryset() to customize "delete selected objects" deletion.

Vasilis Aggelou 7 years ago
parent
commit
777f216d55

+ 1 - 1
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)

+ 4 - 0
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.

+ 3 - 2
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

+ 9 - 0
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
+    <actions>`.
+
 .. method:: ModelAdmin.save_formset(request, form, formset, change)
 
     The ``save_formset`` method is given the ``HttpRequest``, the parent

+ 3 - 0
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`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 4 - 0
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',

+ 14 - 0
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, '<li>Unchangeable object: %s</li>' % 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 = {