diff --git a/netbox/templates/users/panels/actions.html b/netbox/templates/users/panels/actions.html index ffd7d2d95..d6ce07856 100644 --- a/netbox/templates/users/panels/actions.html +++ b/netbox/templates/users/panels/actions.html @@ -1,5 +1,5 @@ {% extends "ui/panels/_base.html" %} -{% load helpers %} +{% load helpers i18n %} {% block panel_content %} @@ -9,18 +9,24 @@ {% endfor %} - {% for action, enabled, models in registered_actions %} + {% for action in registered_actions %} - + {% endfor %} + {% if additional_actions %} + + + + + {% endif %}
{% checkmark enabled %}
{{ action }}{{ action.help_text|default:action.name }}
- {% checkmark enabled %} - {% if models %} - {{ models|join:", " }} + {% checkmark action.enabled %} + {% if action.models %} + {{ action.models|join:", "|title }} {% endif %}
{% trans "Additional actions" %}{{ additional_actions|join:", " }}
{% endblock panel_content %} diff --git a/netbox/users/models/permissions.py b/netbox/users/models/permissions.py index 74b8f589e..80fedd9a1 100644 --- a/netbox/users/models/permissions.py +++ b/netbox/users/models/permissions.py @@ -1,3 +1,4 @@ +from django.apps import apps from django.contrib.postgres.fields import ArrayField from django.db import models from django.urls import reverse @@ -86,20 +87,52 @@ class ObjectPermission(CloningMixin, models.Model): def get_registered_actions(self): """ - Return a list of (action_name, is_enabled, model_keys) tuples for all - registered actions, indicating which are enabled on this permission. + Return a list of dicts for all registered actions: + name: The action identifier + help_text: Human-friendly description (first registration wins) + enabled: Whether this action is enabled on this permission + models: Sorted list of human-friendly model verbose names """ enabled_actions = set(self.actions) - set(RESERVED_ACTIONS) + action_info = {} action_models = {} for model_key, model_actions in registry['model_actions'].items(): + app_label, model_name = model_key.split('.') + try: + verbose_name = str(apps.get_model(app_label, model_name)._meta.verbose_name) + except LookupError: + verbose_name = model_key for action in model_actions: - action_models.setdefault(action.name, []).append(model_key) + # First registration's help_text wins for shared action names + if action.name not in action_info: + action_info[action.name] = action + action_models.setdefault(action.name, []).append(verbose_name) return [ - (name, name in enabled_actions, sorted(action_models[name])) + { + 'name': name, + 'help_text': action_info[name].help_text, + 'enabled': name in enabled_actions, + 'models': sorted(action_models[name]), + } for name in sorted(action_models) ] + def get_additional_actions(self): + """ + Return a sorted list of actions that are neither CRUD nor registered. + These are manually-entered actions from the "Additional actions" field. + """ + registered_names = set() + for model_actions in registry['model_actions'].values(): + for action in model_actions: + registered_names.add(action.name) + + return sorted( + a for a in self.actions + if a not in RESERVED_ACTIONS and a not in registered_names + ) + def get_absolute_url(self): return reverse('users:objectpermission', args=[self.pk]) diff --git a/netbox/users/ui/panels.py b/netbox/users/ui/panels.py index 7c3b9ff71..35a40b88b 100644 --- a/netbox/users/ui/panels.py +++ b/netbox/users/ui/panels.py @@ -63,6 +63,7 @@ class ObjectPermissionActionsPanel(panels.ObjectPanel): **super().get_context(context), 'crud_actions': crud_actions, 'registered_actions': obj.get_registered_actions(), + 'additional_actions': obj.get_additional_actions(), } diff --git a/netbox/utilities/tests/test_permissions.py b/netbox/utilities/tests/test_permissions.py index 34e8450b5..dd4dd7cad 100644 --- a/netbox/utilities/tests/test_permissions.py +++ b/netbox/utilities/tests/test_permissions.py @@ -167,11 +167,11 @@ class ObjectPermissionFormTest(TestCase): registered = permission.get_registered_actions() self.assertEqual(len(registered), 1) - action_name, is_enabled, model_keys = registered[0] - self.assertEqual(action_name, 'render_config') - self.assertTrue(is_enabled) - self.assertIn('dcim.device', model_keys) - self.assertIn('virtualization.virtualmachine', model_keys) + action = registered[0] + self.assertEqual(action['name'], 'render_config') + self.assertEqual(action['help_text'], '') + self.assertTrue(action['enabled']) + self.assertEqual(action['models'], ['device', 'virtual machine']) permission.delete()