diff --git a/netbox/templates/users/objectpermission.html b/netbox/templates/users/objectpermission.html
index ecbd52340..25fdcead3 100644
--- a/netbox/templates/users/objectpermission.html
+++ b/netbox/templates/users/objectpermission.html
@@ -47,7 +47,7 @@
{% checkmark object.can_delete %} |
{% for action in object.actions %}
- {% if action not in 'view,add,change,delete' %}
+ {% if action not in reserved_actions %}
| {{ action }} |
{% checkmark True %} |
diff --git a/netbox/users/constants.py b/netbox/users/constants.py
index 18add80cb..982e69868 100644
--- a/netbox/users/constants.py
+++ b/netbox/users/constants.py
@@ -10,6 +10,10 @@ OBJECTPERMISSION_OBJECT_TYPES = (
CONSTRAINT_TOKEN_USER = '$user'
+# Built-in actions that receive special handling (dedicated checkboxes, model properties)
+# and should not be registered as custom model actions.
+RESERVED_ACTIONS = ('view', 'add', 'change', 'delete')
+
# API tokens
TOKEN_PREFIX = 'nbt_' # Used for v2 tokens only
TOKEN_KEY_LENGTH = 12
diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py
index 348c8b6da..ddb203e0b 100644
--- a/netbox/users/forms/model_forms.py
+++ b/netbox/users/forms/model_forms.py
@@ -423,7 +423,7 @@ class ObjectPermissionForm(forms.ModelForm):
remaining_actions = list(self.instance.actions)
# Check the appropriate CRUD checkboxes
- for action in ['view', 'add', 'change', 'delete']:
+ for action in RESERVED_ACTIONS:
if action in remaining_actions:
self.fields[f'can_{action}'].initial = True
remaining_actions.remove(action)
@@ -450,7 +450,7 @@ class ObjectPermissionForm(forms.ModelForm):
if isinstance(self.initial['actions'], str):
self.initial['actions'] = [self.initial['actions']]
if cloned_actions := self.initial['actions']:
- for action in ['view', 'add', 'change', 'delete']:
+ for action in RESERVED_ACTIONS:
if action in cloned_actions:
self.fields[f'can_{action}'].initial = True
self.initial['actions'].remove(action)
@@ -479,10 +479,11 @@ class ObjectPermissionForm(forms.ModelForm):
'Action "{action}" is for {model} which is not selected.'
).format(action=action_name, model=model_key)
})
- final_actions.append(action_name)
+ if action_name not in final_actions:
+ final_actions.append(action_name)
# Append any of the selected CRUD checkboxes to the actions list
- for action in ['view', 'add', 'change', 'delete']:
+ for action in RESERVED_ACTIONS:
if self.cleaned_data.get(f'can_{action}') and action not in final_actions:
final_actions.append(action)
diff --git a/netbox/users/views.py b/netbox/users/views.py
index 59ebe518f..c47e472eb 100644
--- a/netbox/users/views.py
+++ b/netbox/users/views.py
@@ -19,6 +19,7 @@ from utilities.query import count_related
from utilities.views import GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables
+from .constants import RESERVED_ACTIONS
from .models import Group, ObjectPermission, Owner, OwnerGroup, Token, User
#
@@ -285,6 +286,11 @@ class ObjectPermissionView(generic.ObjectView):
],
)
+ def get_extra_context(self, request, instance):
+ return {
+ 'reserved_actions': RESERVED_ACTIONS,
+ }
+
@register_model_view(ObjectPermission, 'add', detail=False)
@register_model_view(ObjectPermission, 'edit')