mirror of
https://github.com/netbox-community/netbox.git
synced 2026-04-04 16:37:15 +02:00
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:
@@ -6,7 +6,22 @@ For example, a plugin might define a "sync" action for a model that syncs data f
|
||||
|
||||
## Registering Model Actions
|
||||
|
||||
To register custom actions for a model, call `register_model_actions()` in your plugin's `ready()` method:
|
||||
The preferred way to register custom actions is via Django's `Meta.permissions` on the model class. NetBox will automatically register these as model actions when the app is loaded:
|
||||
|
||||
```python
|
||||
from netbox.models import NetBoxModel
|
||||
|
||||
class MyModel(NetBoxModel):
|
||||
# ...
|
||||
|
||||
class Meta:
|
||||
permissions = [
|
||||
('sync', 'Synchronize data from external source'),
|
||||
('export', 'Export data to external system'),
|
||||
]
|
||||
```
|
||||
|
||||
For dynamic registration (e.g. when actions depend on runtime state), you can call `register_model_actions()` directly, typically in your plugin's `ready()` method:
|
||||
|
||||
```python
|
||||
# __init__.py
|
||||
@@ -29,7 +44,7 @@ class MyPluginConfig(PluginConfig):
|
||||
config = MyPluginConfig
|
||||
```
|
||||
|
||||
Once registered, these actions will appear grouped under your model's name when creating or editing an ObjectPermission that includes your model as an object type.
|
||||
Once registered, these actions appear as checkboxes in a flat list when creating or editing an ObjectPermission.
|
||||
|
||||
::: utilities.permissions.ModelAction
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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')
|
||||
|
||||
@@ -272,7 +272,6 @@ class ObjectPermissionView(generic.ObjectView):
|
||||
left_panels=[
|
||||
panels.ObjectPermissionPanel(),
|
||||
panels.ObjectPermissionActionsPanel(),
|
||||
panels.ObjectPermissionCustomActionsPanel(),
|
||||
JSONPanel('constraints', title=_('Constraints')),
|
||||
],
|
||||
right_panels=[
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user