Consolidate ObjectPermission detail view actions panel

Merge ObjectPermissionActionsPanel and ObjectPermissionCustomActionsPanel
into a single Actions panel that shows CRUD booleans and all registered
actions in one table, matching the form's consolidated layout.

Also fix data-object-types-selected attribute value (True -> 'true') and
update plugin docs to show Meta.permissions as the primary registration
approach.
This commit is contained in:
Jason Novinger
2026-04-01 17:16:57 -05:00
parent 84c2acb1f9
commit e9be6e4178
5 changed files with 50 additions and 40 deletions

View File

@@ -3,12 +3,18 @@
{% block panel_content %}
<table class="table table-hover attr-table">
{% for action, models in custom_actions %}
{% for label, enabled in crud_actions %}
<tr>
<th scope="row">{{ label }}</th>
<td>{% checkmark enabled %}</td>
</tr>
{% endfor %}
{% for action, enabled, models in registered_actions %}
<tr>
<th scope="row">{{ action }}</th>
<td>
<div class="d-flex justify-content-between align-items-start">
{% checkmark True %}
{% checkmark enabled %}
{% if models %}
<small class="text-muted">{{ models }}</small>
{% endif %}

View File

@@ -47,54 +47,44 @@ class ObjectPermissionPanel(panels.ObjectAttributesPanel):
enabled = attrs.BooleanAttr('enabled')
class ObjectPermissionActionsPanel(panels.ObjectAttributesPanel):
class ObjectPermissionActionsPanel(panels.ObjectPanel):
template_name = 'users/panels/actions.html'
title = _('Actions')
can_view = attrs.BooleanAttr('can_view', label=_('View'))
can_add = attrs.BooleanAttr('can_add', label=_('Add'))
can_change = attrs.BooleanAttr('can_change', label=_('Change'))
can_delete = attrs.BooleanAttr('can_delete', label=_('Delete'))
class ObjectPermissionCustomActionsPanel(panels.ObjectPanel):
"""
A panel which displays non-CRUD (custom) actions assigned to an ObjectPermission.
"""
template_name = 'users/panels/custom_actions.html'
title = _('Custom Actions')
def get_context(self, context):
obj = context['object']
custom_actions = [a for a in obj.actions if a not in RESERVED_ACTIONS]
# Build a list of (action_name, model_labels) tuples from the registry,
# scoped to the object types assigned to this permission.
assigned_types = {
f'{ot.app_label}.{ot.model}' for ot in obj.object_types.all()
}
crud_actions = [
(_('View'), 'view' in obj.actions),
(_('Add'), 'add' in obj.actions),
(_('Change'), 'change' in obj.actions),
(_('Delete'), 'delete' in obj.actions),
]
enabled_actions = set(obj.actions) - set(RESERVED_ACTIONS)
# Collect all registered actions from the full registry, deduplicating by name.
seen = []
seen_set = set()
action_models = {}
for model_key, model_actions in registry['model_actions'].items():
if model_key in assigned_types:
for action in model_actions:
if action.name in custom_actions:
action_models.setdefault(action.name, []).append(model_key)
for action in model_actions:
if action.name not in seen_set:
seen.append(action.name)
seen_set.add(action.name)
action_models.setdefault(action.name, []).append(model_key)
custom_actions_display = [
(action, ', '.join(action_models.get(action, [])))
for action in custom_actions
registered_display = [
(action, action in enabled_actions, ', '.join(sorted(action_models[action])))
for action in seen
]
return {
**super().get_context(context),
'custom_actions': custom_actions_display,
'crud_actions': crud_actions,
'registered_actions': registered_display,
}
def render(self, context):
ctx = self.get_context(context)
if not ctx['custom_actions']:
return ''
return super().render(context)
class OwnerPanel(panels.ObjectAttributesPanel):
name = attrs.TextAttr('name')

View File

@@ -272,7 +272,6 @@ class ObjectPermissionView(generic.ObjectView):
left_panels=[
panels.ObjectPermissionPanel(),
panels.ObjectPermissionActionsPanel(),
panels.ObjectPermissionCustomActionsPanel(),
JSONPanel('constraints', title=_('Constraints')),
],
right_panels=[

View File

@@ -218,7 +218,7 @@ class ObjectTypeSelectedOptions(ObjectTypeSelectMultiple):
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context['widget']['attrs']['data-object-types-selected'] = True
context['widget']['attrs']['data-object-types-selected'] = 'true'
return context