Просмотр исходного кода

Add ObjectTypeSplitMultiSelectWidget and RegisteredActionsWidget

Jason Novinger 1 неделя назад
Родитель
Сommit
e5439f4eb8

+ 1 - 0
netbox/utilities/forms/widgets/__init__.py

@@ -1,3 +1,4 @@
+from .actions import *
 from .apiselect import *
 from .datetime import *
 from .misc import *

+ 23 - 0
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

+ 58 - 0
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

+ 32 - 0
netbox/utilities/templates/widgets/registered_actions.html

@@ -0,0 +1,32 @@
+{% load i18n %}
+<div class="registered-actions-container" id="id_registered_actions_container">
+  {% for model_key, actions in widget.model_actions.items %}
+    <div class="model-actions card mb-2" data-model="{{ model_key }}" style="display: none;">
+      <div class="card-header py-2">
+        <strong>{{ model_key }}</strong>
+      </div>
+      <div class="card-body py-2">
+        {% for action in actions %}
+          <div class="form-check">
+            <input type="checkbox"
+                   class="form-check-input"
+                   name="{{ widget.name }}"
+                   value="{{ model_key }}.{{ action.name }}"
+                   id="id_{{ widget.name }}_{{ forloop.parentloop.counter }}_{{ forloop.counter }}"
+                   {% if model_key|add:"."|add:action.name in widget.value %}checked{% endif %}>
+            <label class="form-check-label" for="id_{{ widget.name }}_{{ forloop.parentloop.counter }}_{{ forloop.counter }}">
+              {{ action.name }}
+              {% if action.help_text %}
+                <small class="text-muted ms-1">{{ action.help_text }}</small>
+              {% endif %}
+            </label>
+          </div>
+        {% endfor %}
+      </div>
+    </div>
+  {% empty %}
+    <p class="text-muted" id="no-custom-actions-message">
+      {% trans "No custom actions registered." %}
+    </p>
+  {% endfor %}
+</div>