Browse Source

Fixed #29917 -- Stopped collecting ModelAdmin.actions from base ModelAdmins.

Matthias Kestenholz 6 years ago
parent
commit
f9ff1df1da

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

+ 2 - 4
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):
             ...

+ 21 - 0
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`
 -------------------------
 

+ 21 - 0
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'])