Przeglądaj źródła

15541 Add component selector to InventoryItemTemplate (#15759)

* 15541 make inventoryitemtemplateform match inventoryitemform

* 15541 set tab active
Arthur Hanson 1 rok temu
rodzic
commit
90d0104359

+ 97 - 8
netbox/dcim/forms/model_forms.py

@@ -976,21 +976,67 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
         queryset=Manufacturer.objects.all(),
         required=False
     )
-    component_type = ContentTypeChoiceField(
-        queryset=ContentType.objects.all(),
-        limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS,
+    # Assigned component selectors
+    consoleporttemplate = DynamicModelChoiceField(
+        queryset=ConsolePortTemplate.objects.all(),
+        required=False,
+        query_params={
+            'device_type_id': '$device_type'
+        },
+        label=_('Console port template')
+    )
+    consoleserverporttemplate = DynamicModelChoiceField(
+        queryset=ConsoleServerPortTemplate.objects.all(),
+        required=False,
+        query_params={
+            'device_type_id': '$device_type'
+        },
+        label=_('Console server port template')
+    )
+    frontporttemplate = DynamicModelChoiceField(
+        queryset=FrontPortTemplate.objects.all(),
+        required=False,
+        query_params={
+            'device_type_id': '$device_type'
+        },
+        label=_('Front port template')
+    )
+    interfacetemplate = DynamicModelChoiceField(
+        queryset=InterfaceTemplate.objects.all(),
+        required=False,
+        query_params={
+            'device_type_id': '$device_type'
+        },
+        label=_('Interface template')
+    )
+    poweroutlettemplate = DynamicModelChoiceField(
+        queryset=PowerOutletTemplate.objects.all(),
         required=False,
-        widget=forms.HiddenInput
+        query_params={
+            'device_type_id': '$device_type'
+        },
+        label=_('Power outlet template')
     )
-    component_id = forms.IntegerField(
+    powerporttemplate = DynamicModelChoiceField(
+        queryset=PowerPortTemplate.objects.all(),
         required=False,
-        widget=forms.HiddenInput
+        query_params={
+            'device_type_id': '$device_type'
+        },
+        label=_('Power port template')
+    )
+    rearporttemplate = DynamicModelChoiceField(
+        queryset=RearPortTemplate.objects.all(),
+        required=False,
+        query_params={
+            'device_type_id': '$device_type'
+        },
+        label=_('Rear port template')
     )
 
     fieldsets = (
         (None, (
             'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
-            'component_type', 'component_id',
         )),
     )
 
@@ -998,9 +1044,52 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
         model = InventoryItemTemplate
         fields = [
             'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
-            'component_type', 'component_id',
         ]
 
+    def __init__(self, *args, **kwargs):
+        instance = kwargs.get('instance')
+        initial = kwargs.get('initial', {}).copy()
+        component_type = initial.get('component_type')
+        component_id = initial.get('component_id')
+
+        # Used for picking the default active tab for component selection
+        self.no_component = True
+
+        if instance:
+            # When editing set the initial value for component selection
+            for component_model in ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS):
+                if type(instance.component) is component_model.model_class():
+                    initial[component_model.model] = instance.component
+                    self.no_component = False
+                    break
+        elif component_type and component_id:
+            # When adding the InventoryItem from a component page
+            if content_type := ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS).filter(pk=component_type).first():
+                if component := content_type.model_class().objects.filter(pk=component_id).first():
+                    initial[content_type.model] = component
+                    self.no_component = False
+
+        kwargs['initial'] = initial
+
+        super().__init__(*args, **kwargs)
+
+    def clean(self):
+        super().clean()
+
+        # Handle object assignment
+        selected_objects = [
+            field for field in (
+                'consoleporttemplate', 'consoleserverporttemplate', 'frontporttemplate', 'interfacetemplate',
+                'poweroutlettemplate', 'powerporttemplate', 'rearporttemplate'
+            ) if self.cleaned_data[field]
+        ]
+        if len(selected_objects) > 1:
+            raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component."))
+        elif selected_objects:
+            self.instance.component = self.cleaned_data[selected_objects[0]]
+        else:
+            self.instance.component = None
+
 
 #
 # Device components

+ 2 - 0
netbox/dcim/views.py

@@ -1656,6 +1656,7 @@ class InventoryItemTemplateCreateView(generic.ComponentCreateView):
     queryset = InventoryItemTemplate.objects.all()
     form = forms.InventoryItemTemplateCreateForm
     model_form = forms.InventoryItemTemplateForm
+    template_name = 'dcim/inventoryitemtemplate_edit.html'
 
     def alter_object(self, instance, request):
         # Set component (if any)
@@ -1673,6 +1674,7 @@ class InventoryItemTemplateCreateView(generic.ComponentCreateView):
 class InventoryItemTemplateEditView(generic.ObjectEditView):
     queryset = InventoryItemTemplate.objects.all()
     form = forms.InventoryItemTemplateForm
+    template_name = 'dcim/inventoryitemtemplate_edit.html'
 
 
 @register_model_view(InventoryItemTemplate, 'delete')

+ 104 - 0
netbox/templates/dcim/inventoryitemtemplate_edit.html

@@ -0,0 +1,104 @@
+{% extends 'generic/object_edit.html' %}
+{% load static %}
+{% load form_helpers %}
+{% load helpers %}
+{% load i18n %}
+
+{% block form %}
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">{% trans "Inventory Item" %}</h5>
+      </div>
+      {% render_field form.device_type %}
+      {% render_field form.parent %}
+      {% render_field form.name %}
+      {% render_field form.label %}
+      {% render_field form.role %}
+      {% render_field form.description %}
+    </div>
+
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">{% trans "Hardware" %}</h5>
+      </div>
+      {% render_field form.manufacturer %}
+      {% render_field form.part_id %}
+    </div>
+
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">{% trans "Component Assignment" %}</h5>
+      </div>
+      <div class="row mb-2 offset-sm-3">
+        <ul class="nav nav-pills" role="tablist">
+          <li role="presentation" class="nav-item">
+              <button role="tab" type="button" id="consoleport_tab" data-bs-toggle="tab" aria-controls="consoleport" data-bs-target="#consoleport" class="nav-link {% if form.initial.consoleporttemplate or form.no_component %}active{% endif %}">
+                {% trans "Console Port" %}
+              </button>
+            </li>
+            <li role="presentation" class="nav-item">
+              <button role="tab" type="button" id="consoleserverport_tab" data-bs-toggle="tab" aria-controls="consoleserverport" data-bs-target="#consoleserverport" class="nav-link {% if form.initial.consoleserverporttemplate %}active{% endif %}">
+                {% trans "Console Server Port" %}
+              </button>
+            </li>
+            <li role="presentation" class="nav-item">
+              <button role="tab" type="button" id="frontport_tab" data-bs-toggle="tab" aria-controls="frontport" data-bs-target="#frontport" class="nav-link {% if form.initial.frontporttemplate %}active{% endif %}">
+                {% trans "Front Port" %}
+              </button>
+            </li>
+            <li role="presentation" class="nav-item">
+              <button role="tab" type="button" id="interface_tab" data-bs-toggle="tab" aria-controls="interface" data-bs-target="#interface" class="nav-link {% if form.initial.interfacetemplate %}active{% endif %}">
+                {% trans "Interface" %}
+              </button>
+            </li>
+            <li role="presentation" class="nav-item">
+              <button role="tab" type="button" id="poweroutlet_tab" data-bs-toggle="tab" aria-controls="poweroutlet" data-bs-target="#poweroutlet" class="nav-link {% if form.initial.poweroutlettemplate %}active{% endif %}">
+                {% trans "Power Outlet" %}
+              </button>
+            </li>
+            <li role="presentation" class="nav-item">
+              <button role="tab" type="button" id="powerport_tab" data-bs-toggle="tab" aria-controls="powerport" data-bs-target="#powerport" class="nav-link {% if form.initial.powerporttemplate %}active{% endif %}">
+                {% trans "Power Port" %}
+              </button>
+            </li>
+            <li role="presentation" class="nav-item">
+              <button role="tab" type="button" id="rearport_tab" data-bs-toggle="tab" aria-controls="rearport" data-bs-target="#rearport" class="nav-link {% if form.initial.rearporttemplate %}active{% endif %}">
+                {% trans "Rear Port" %}
+              </button>
+            </li>
+        </ul>
+      </div>
+      <div class="tab-content p-0 border-0">
+        <div class="tab-pane {% if form.initial.consoleporttemplate or form.no_component %}active{% endif %}" id="consoleport" role="tabpanel" aria-labeled-by="consoleport_tab">
+            {% render_field form.consoleporttemplate %}
+          </div>
+          <div class="tab-pane {% if form.initial.consoleserverporttemplate %}active{% endif %}" id="consoleserverport" role="tabpanel" aria-labeled-by="consoleserverport_tab">
+            {% render_field form.consoleserverporttemplate %}
+          </div>
+          <div class="tab-pane {% if form.initial.frontporttemplate %}active{% endif %}" id="frontport" role="tabpanel" aria-labeled-by="frontport_tab">
+            {% render_field form.frontporttemplate %}
+          </div>
+          <div class="tab-pane {% if form.initial.interfacetemplate %}active{% endif %}" id="interface" role="tabpanel" aria-labeled-by="interface_tab">
+            {% render_field form.interfacetemplate %}
+          </div>
+          <div class="tab-pane {% if form.initial.poweroutlettemplate %}active{% endif %}" id="poweroutlet" role="tabpanel" aria-labeled-by="poweroutlet_tab">
+            {% render_field form.poweroutlettemplate %}
+          </div>
+          <div class="tab-pane {% if form.initial.powerporttemplate %}active{% endif %}" id="powerport" role="tabpanel" aria-labeled-by="powerport_tab">
+            {% render_field form.powerporttemplate %}
+          </div>
+          <div class="tab-pane {% if form.initial.rearporttemplate %}active{% endif %}" id="rearport" role="tabpanel" aria-labeled-by="rearport_tab">
+            {% render_field form.rearporttemplate %}
+          </div>
+      </div>
+    </div>
+
+    {% if form.custom_fields %}
+      <div class="field-group my-5">
+        <div class="row mb-2">
+          <h5 class="offset-sm-3">{% trans "Custom Fields" %}</h5>
+        </div>
+        {% render_custom_fields form %}
+      </div>
+    {% endif %}
+{% endblock %}