Add RESERVED_ACTIONS constant and fix dedup in registered actions

- Define RESERVED_ACTIONS in users/constants.py for the four built-in
  permission actions (view, add, change, delete)
- Replace hardcoded action lists in ObjectPermissionForm with the constant
- Fix duplicate action names in clean() when the same action is registered
  across multiple models (e.g. render_config for Device and VirtualMachine)
- Fix template substring matching bug in objectpermission.html detail view
  by passing RESERVED_ACTIONS through view context for proper list membership
This commit is contained in:
Jason Novinger
2026-03-03 15:12:46 -06:00
parent 3bc60303fe
commit e2537305b2
4 changed files with 16 additions and 5 deletions
+1 -1
View File
@@ -47,7 +47,7 @@
<td>{% checkmark object.can_delete %}</td>
</tr>
{% for action in object.actions %}
{% if action not in 'view,add,change,delete' %}
{% if action not in reserved_actions %}
<tr>
<th scope="row">{{ action }}</th>
<td>{% checkmark True %}</td>
+4
View File
@@ -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
+5 -4
View File
@@ -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)
+6
View File
@@ -10,6 +10,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
#
@@ -214,6 +215,11 @@ class ObjectPermissionView(generic.ObjectView):
queryset = ObjectPermission.objects.all()
template_name = 'users/objectpermission.html'
def get_extra_context(self, request, instance):
return {
'reserved_actions': RESERVED_ACTIONS,
}
@register_model_view(ObjectPermission, 'add', detail=False)
@register_model_view(ObjectPermission, 'edit')