mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #17962 -- Added ModelAdmin.get_deleted_objects().
This commit is contained in:
parent
9822d88ca0
commit
8116e588db
1
AUTHORS
1
AUTHORS
@ -677,6 +677,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Raphaël Barrois <raphael.barrois@m4x.org>
|
Raphaël Barrois <raphael.barrois@m4x.org>
|
||||||
Raphael Michel <mail@raphaelmichel.de>
|
Raphael Michel <mail@raphaelmichel.de>
|
||||||
Raúl Cumplido <raulcumplido@gmail.com>
|
Raúl Cumplido <raulcumplido@gmail.com>
|
||||||
|
Rebecca Smith <rebkwok@gmail.com>
|
||||||
Remco Wendt <remco.wendt@gmail.com>
|
Remco Wendt <remco.wendt@gmail.com>
|
||||||
Renaud Parent <renaud.parent@gmail.com>
|
Renaud Parent <renaud.parent@gmail.com>
|
||||||
Renbi Yu <averybigant@gmail.com>
|
Renbi Yu <averybigant@gmail.com>
|
||||||
|
@ -4,7 +4,7 @@ Built-in, globally-available admin actions.
|
|||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.admin import helpers
|
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.core.exceptions import PermissionDenied
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.translation import gettext as _, gettext_lazy
|
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
|
# Populate deletable_objects, a data structure of all related objects that
|
||||||
# will also be deleted.
|
# will also be deleted.
|
||||||
deletable_objects, model_count, perms_needed, protected = get_deleted_objects(
|
deletable_objects, model_count, perms_needed, protected = modeladmin.get_deleted_objects(queryset, request)
|
||||||
queryset, request.user, modeladmin.admin_site,
|
|
||||||
)
|
|
||||||
|
|
||||||
# The user has already confirmed the deletion.
|
# The user has already confirmed the deletion.
|
||||||
# Do the deletion and return None to display the change list view again.
|
# Do the deletion and return None to display the change list view again.
|
||||||
|
@ -1728,6 +1728,13 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
'admin/change_list.html'
|
'admin/change_list.html'
|
||||||
], context)
|
], 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
|
@csrf_protect_m
|
||||||
def delete_view(self, request, object_id, extra_context=None):
|
def delete_view(self, request, object_id, extra_context=None):
|
||||||
with transaction.atomic(using=router.db_for_write(self.model)):
|
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
|
# Populate deleted_objects, a data structure of all related objects that
|
||||||
# will also be deleted.
|
# will also be deleted.
|
||||||
deleted_objects, model_count, perms_needed, protected = get_deleted_objects(
|
deleted_objects, model_count, perms_needed, protected = self.get_deleted_objects([obj], request)
|
||||||
[obj], request.user, self.admin_site,
|
|
||||||
)
|
|
||||||
|
|
||||||
if request.POST and not protected: # The user has confirmed the deletion.
|
if request.POST and not protected: # The user has confirmed the deletion.
|
||||||
if perms_needed:
|
if perms_needed:
|
||||||
|
@ -1998,6 +1998,36 @@ templates used by the :class:`ModelAdmin` views:
|
|||||||
def get_changeform_initial_data(self, request):
|
def get_changeform_initial_data(self, request):
|
||||||
return {'name': 'custom_initial_value'}
|
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
|
Other methods
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -50,6 +50,9 @@ Minor features
|
|||||||
* The ``admin_order_field`` attribute for elements in
|
* The ``admin_order_field`` attribute for elements in
|
||||||
:attr:`.ModelAdmin.list_display` may now be a query expression.
|
: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`
|
:mod:`django.contrib.admindocs`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -46,9 +46,15 @@ class CustomPwdTemplateUserAdmin(UserAdmin):
|
|||||||
change_user_password_template = ['admin/auth/user/change_password.html'] # a list, to test fix for #18697
|
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 = Admin2(name="admin2")
|
||||||
|
|
||||||
site.register(models.Article, base_admin.ArticleAdmin)
|
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.Section, inlines=[base_admin.ArticleInline], search_fields=['name'])
|
||||||
site.register(models.Thing, base_admin.ThingAdmin)
|
site.register(models.Thing, base_admin.ThingAdmin)
|
||||||
site.register(models.Fabric, base_admin.FabricAdmin)
|
site.register(models.Fabric, base_admin.FabricAdmin)
|
||||||
|
@ -12,7 +12,7 @@ from django.urls import reverse
|
|||||||
from .admin import SubscriberAdmin
|
from .admin import SubscriberAdmin
|
||||||
from .forms import MediaActionForm
|
from .forms import MediaActionForm
|
||||||
from .models import (
|
from .models import (
|
||||||
Actor, Answer, ExternalSubscriber, Question, Subscriber,
|
Actor, Answer, Book, ExternalSubscriber, Question, Subscriber,
|
||||||
UnchangeableObject,
|
UnchangeableObject,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -153,6 +153,18 @@ class AdminActionsTest(TestCase):
|
|||||||
self.assertIs(SubscriberAdmin.overridden, True)
|
self.assertIs(SubscriberAdmin.overridden, True)
|
||||||
self.assertEqual(Subscriber.objects.all().count(), 0)
|
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):
|
def test_custom_function_mail_action(self):
|
||||||
"""A custom action may be defined in a function."""
|
"""A custom action may be defined in a function."""
|
||||||
action_data = {
|
action_data = {
|
||||||
|
@ -2357,6 +2357,13 @@ class AdminViewDeletedObjectsTest(TestCase):
|
|||||||
response = self.client.get(reverse('admin:admin_views_bookmark_delete', args=(bookmark.pk,)))
|
response = self.client.get(reverse('admin:admin_views_bookmark_delete', args=(bookmark.pk,)))
|
||||||
self.assertContains(response, should_contain)
|
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')
|
@override_settings(ROOT_URLCONF='admin_views.urls')
|
||||||
class TestGenericRelations(TestCase):
|
class TestGenericRelations(TestCase):
|
||||||
|
@ -666,6 +666,16 @@ class ModelAdminTests(TestCase):
|
|||||||
finally:
|
finally:
|
||||||
self.site.unregister(Band)
|
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):
|
class ModelAdminPermissionTests(SimpleTestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user