From 5cfdf6ab6a0bfba5942d25b326b4c69a15631491 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Fri, 20 Feb 2026 13:46:27 -0600 Subject: [PATCH] Add ObjectTypeSplitMultiSelectWidget and RegisteredActionsWidget --- netbox/utilities/forms/widgets/__init__.py | 1 + netbox/utilities/forms/widgets/actions.py | 23 ++++++++ netbox/utilities/forms/widgets/select.py | 58 +++++++++++++++++++ .../templates/widgets/registered_actions.html | 32 ++++++++++ 4 files changed, 114 insertions(+) create mode 100644 netbox/utilities/forms/widgets/actions.py create mode 100644 netbox/utilities/templates/widgets/registered_actions.html diff --git a/netbox/utilities/forms/widgets/__init__.py b/netbox/utilities/forms/widgets/__init__.py index dea43314d..3fbcb33c4 100644 --- a/netbox/utilities/forms/widgets/__init__.py +++ b/netbox/utilities/forms/widgets/__init__.py @@ -1,3 +1,4 @@ +from .actions import * from .apiselect import * from .datetime import * from .misc import * diff --git a/netbox/utilities/forms/widgets/actions.py b/netbox/utilities/forms/widgets/actions.py new file mode 100644 index 000000000..781da5ec6 --- /dev/null +++ b/netbox/utilities/forms/widgets/actions.py @@ -0,0 +1,23 @@ +from django import forms + +__all__ = ( + 'RegisteredActionsWidget', +) + + +class RegisteredActionsWidget(forms.CheckboxSelectMultiple): + """ + Widget rendering checkboxes for registered model actions. + Groups actions by model with data attributes for JS show/hide. + """ + template_name = 'widgets/registered_actions.html' + + def __init__(self, *args, model_actions=None, **kwargs): + super().__init__(*args, **kwargs) + self.model_actions = model_actions or {} + + def get_context(self, name, value, attrs): + context = super().get_context(name, value, attrs) + context['widget']['model_actions'] = self.model_actions + context['widget']['value'] = value or [] + return context diff --git a/netbox/utilities/forms/widgets/select.py b/netbox/utilities/forms/widgets/select.py index 4e0c06211..0d700b13f 100644 --- a/netbox/utilities/forms/widgets/select.py +++ b/netbox/utilities/forms/widgets/select.py @@ -9,6 +9,7 @@ __all__ = ( 'ClearableSelect', 'ColorSelect', 'HTMXSelect', + 'ObjectTypeSplitMultiSelectWidget', 'SelectWithPK', 'SplitMultiSelectWidget', ) @@ -180,3 +181,60 @@ class SplitMultiSelectWidget(forms.MultiWidget): def value_from_datadict(self, data, files, name): # Return only the choices from the SelectedOptions widget return super().value_from_datadict(data, files, name)[1] + + +# +# ObjectType-specific widgets for ObjectPermissionForm +# + +class ObjectTypeSelectMultiple(SelectMultipleBase): + """ + SelectMultiple that adds data-model-key attribute to options for JS targeting. + """ + pk_to_model_key = None + + def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): + option = super().create_option(name, value, label, selected, index, subindex, attrs) + if self.pk_to_model_key: + model_key = self.pk_to_model_key.get(value) or self.pk_to_model_key.get(str(value)) + if model_key: + option['attrs']['data-model-key'] = model_key + return option + + +class ObjectTypeAvailableOptions(ObjectTypeSelectMultiple): + include_selected = False + + def get_context(self, name, value, attrs): + context = super().get_context(name, value, attrs) + context['widget']['attrs']['required'] = False + return context + + +class ObjectTypeSelectedOptions(ObjectTypeSelectMultiple): + include_selected = True + + +class ObjectTypeSplitMultiSelectWidget(SplitMultiSelectWidget): + """ + SplitMultiSelectWidget that adds data-model-key attributes to options. + Used by ObjectPermissionForm to enable JS show/hide of custom actions. + """ + + def __init__(self, choices, attrs=None, ordering=False): + widgets = [ + ObjectTypeAvailableOptions( + attrs={'size': 8}, + choices=choices + ), + ObjectTypeSelectedOptions( + attrs={'size': 8, 'class': 'select-all'}, + choices=choices + ), + ] + forms.MultiWidget.__init__(self, widgets, attrs) + self.ordering = ordering + + def set_model_key_map(self, pk_to_model_key): + for widget in self.widgets: + widget.pk_to_model_key = pk_to_model_key diff --git a/netbox/utilities/templates/widgets/registered_actions.html b/netbox/utilities/templates/widgets/registered_actions.html new file mode 100644 index 000000000..287a1663b --- /dev/null +++ b/netbox/utilities/templates/widgets/registered_actions.html @@ -0,0 +1,32 @@ +{% load i18n %} +
+ {% for model_key, actions in widget.model_actions.items %} + + {% empty %} +

+ {% trans "No custom actions registered." %} +

+ {% endfor %} +