From f9ff1df1daac8ae1fc22b27f48735148cb5488dd Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 10 Nov 2018 00:52:30 +0100 Subject: [PATCH] Fixed #29917 -- Stopped collecting ModelAdmin.actions from base ModelAdmins. --- django/contrib/admin/options.py | 9 ++------- docs/ref/contrib/admin/actions.txt | 6 ++---- docs/releases/2.2.txt | 21 +++++++++++++++++++++ tests/modeladmin/test_actions.py | 21 +++++++++++++++++++++ 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 241d22e82a..3a228516cc 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -859,13 +859,8 @@ class ModelAdmin(BaseModelAdmin): for (name, func) in self.admin_site.actions: description = getattr(func, 'short_description', name.replace('_', ' ')) actions.append((func, name, description)) - - # Then gather them from the model admin and all parent classes, - # starting with self and working back up. - for klass in self.__class__.mro()[::-1]: - class_actions = getattr(klass, 'actions', []) or [] - actions.extend(self.get_action(action) for action in class_actions) - + # Add actions from this ModelAdmin. + actions.extend(self.get_action(action) for action in self.actions or []) # get_action might have returned None, so filter any of those out. return filter(None, actions) diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index 82ce1e6f92..015427054a 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -343,10 +343,8 @@ Conditionally enabling or disabling actions This returns a dictionary of actions allowed. The keys are action names, and the values are ``(function, name, short_description)`` tuples. - Most of the time you'll use this method to conditionally remove actions from - the list gathered by the superclass. For example, if I only wanted users - whose names begin with 'J' to be able to delete objects in bulk, I could do - the following:: + For example, if you only want users whose names begin with 'J' to be able + to delete objects in bulk:: class MyModelAdmin(admin.ModelAdmin): ... diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index 49fa9b0ba4..37ef7fd3a8 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -293,6 +293,27 @@ Database backend API * Third party database backends must implement support for partial indexes or set ``DatabaseFeatures.supports_partial_indexes`` to ``False``. +Admin actions are no longer collected from base ``ModelAdmin`` classes +---------------------------------------------------------------------- + +For example, in older versions of Django:: + + from django.contrib import admin + + class BaseAdmin(admin.ModelAdmin): + actions = ['a'] + + class SubAdmin(BaseAdmin): + actions = ['b'] + +``SubAdmin`` will have actions ``'a'`` and ``'b'``. + +Now ``actions`` follows standard Python inheritance. To get the same result as +before:: + + class SubAdmin(BaseAdmin): + actions = BaseAdmin.actions + ['b'] + :mod:`django.contrib.gis` ------------------------- diff --git a/tests/modeladmin/test_actions.py b/tests/modeladmin/test_actions.py index 17b3c324d2..a33c1811b2 100644 --- a/tests/modeladmin/test_actions.py +++ b/tests/modeladmin/test_actions.py @@ -55,3 +55,24 @@ class AdminActionsTests(TestCase): mock_request.user = user actions = ma.get_actions(mock_request) self.assertEqual(list(actions.keys()), expected) + + def test_actions_inheritance(self): + class AdminBase(admin.ModelAdmin): + actions = ['custom_action'] + + def custom_action(modeladmin, request, queryset): + pass + + class AdminA(AdminBase): + pass + + class AdminB(AdminBase): + actions = None + + ma1 = AdminA(Band, admin.AdminSite()) + action_names = [name for _, name, _ in ma1._get_base_actions()] + self.assertEqual(action_names, ['delete_selected', 'custom_action']) + # `actions = None` removes actions from superclasses. + ma2 = AdminB(Band, admin.AdminSite()) + action_names = [name for _, name, _ in ma2._get_base_actions()] + self.assertEqual(action_names, ['delete_selected'])