diff --git a/django/contrib/admin/decorators.py b/django/contrib/admin/decorators.py
index d3ff56a59a..ad34df2e65 100644
a
|
b
|
|
1 | | def action(function=None, *, permissions=None, description=None): |
| 1 | def action(function=None, *, permissions=None, description=None, group=None): |
2 | 2 | """ |
3 | 3 | Conveniently add attributes to an action function:: |
4 | 4 | |
… |
… |
def action(function=None, *, permissions=None, description=None):
|
23 | 23 | func.allowed_permissions = permissions |
24 | 24 | if description is not None: |
25 | 25 | func.short_description = description |
| 26 | if group is not None: |
| 27 | func.action_group = group |
26 | 28 | return func |
27 | 29 | |
28 | 30 | if function is None: |
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 8ccacd6213..ca61901fe7 100644
a
|
b
|
|
1 | 1 | import copy |
| 2 | from collections import defaultdict |
2 | 3 | import json |
3 | 4 | import re |
4 | 5 | from functools import partial, update_wrapper |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
1024 | 1025 | Return a list of choices for use in a form object. Each choice is a |
1025 | 1026 | tuple (name, description). |
1026 | 1027 | """ |
| 1028 | choices = defaultdict(list) |
| 1029 | choices[None].extend(default_choices) |
1027 | 1030 | choices = [] + default_choices |
1028 | 1031 | for func, name, description in self.get_actions(request).values(): |
1029 | 1032 | choice = (name, description % model_format_dict(self.opts)) |
1030 | | choices.append(choice) |
1031 | | return choices |
| 1033 | choices[getattr(func, 'action_group', None)].append(choice) |
| 1034 | return list(choices.items()) |
1032 | 1035 | |
1033 | 1036 | def get_action(self, action): |
1034 | 1037 | """ |