From 3eb9127678e292ef2645b632199f3e9c876ad999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henk=20Kahlfu=C3=9F?= Date: Sat, 26 May 2018 15:00:16 +0200 Subject: [PATCH] Fixed #23869 -- Made ModelAdmin.get_deleted_objects() use has_delete_permission() for permissions checking. --- AUTHORS | 1 + django/contrib/admin/options.py | 2 +- django/contrib/admin/utils.py | 12 +++++------- tests/modeladmin/tests.py | 23 ++++++++++++++++++++++- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/AUTHORS b/AUTHORS index 257f8f2c5df..4ac99db75e8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -590,6 +590,7 @@ answer newbie questions, and generally made Django that much better: Mikhail Korobov Mikko Hellsing MikoĊ‚aj Siedlarek + milkomeda Milton Waddams mitakummaa@gmail.com mmarshall diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index bc7a28a45d4..ff21abbc796 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1812,7 +1812,7 @@ class ModelAdmin(BaseModelAdmin): 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) + return get_deleted_objects(objs, request, self.admin_site) @csrf_protect_m def delete_view(self, request, object_id, extra_context=None): diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index add7faba561..eae09b2238d 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -2,7 +2,6 @@ import datetime import decimal from collections import defaultdict -from django.contrib.auth import get_permission_codename from django.core.exceptions import FieldDoesNotExist from django.db import models, router from django.db.models.constants import LOOKUP_SEP @@ -117,7 +116,7 @@ def flatten_fieldsets(fieldsets): return field_names -def get_deleted_objects(objs, user, admin_site): +def get_deleted_objects(objs, request, admin_site): """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogeneous iterable of objects (e.g. a QuerySet). @@ -136,12 +135,15 @@ def get_deleted_objects(objs, user, admin_site): perms_needed = set() def format_callback(obj): - has_admin = obj.__class__ in admin_site._registry + model = obj.__class__ + has_admin = model in admin_site._registry opts = obj._meta no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), obj) if has_admin: + if not admin_site._registry[model].has_delete_permission(request, obj): + perms_needed.add(opts.verbose_name) try: admin_url = reverse('%s:%s_%s_change' % (admin_site.name, @@ -152,10 +154,6 @@ def get_deleted_objects(objs, user, admin_site): # Change url doesn't exist -- don't display link to edit return no_edit_link - if 'delete' in opts.default_permissions: - p = '%s.%s' % (opts.app_label, get_permission_codename('delete', opts)) - if not user.has_perm(p): - perms_needed.add(opts.verbose_name) # Display a link to the admin page. return format_html('{}: {}', capfirst(opts.verbose_name), diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index 03fd5ef2beb..de216cbb118 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -669,13 +669,34 @@ class ModelAdminTests(TestCase): 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) + self.site.register(Band, ModelAdmin) + ma = self.site._registry[Band] 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, []) + def test_get_deleted_objects_with_custom_has_delete_permission(self): + """ + ModelAdmin.get_deleted_objects() uses ModelAdmin.has_delete_permission() + for permissions checking. + """ + mock_request = MockRequest() + mock_request.user = User.objects.create_superuser(username='bob', email='bob@test.com', password='test') + + class TestModelAdmin(ModelAdmin): + def has_delete_permission(self, request, obj=None): + return False + + self.site.register(Band, TestModelAdmin) + ma = self.site._registry[Band] + 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, {'band'}) + self.assertEqual(protected, []) + class ModelAdminPermissionTests(SimpleTestCase):