From 93eecfcd80cb60c017d75dcf0b607c1df8e6ef0c Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Tue, 20 Dec 2022 11:32:02 +0100 Subject: [PATCH] Allow re-assigning InventoryItem components --- netbox/dcim/forms/model_forms.py | 116 ++++++++++++++++-- netbox/dcim/models/device_components.py | 5 + netbox/dcim/views.py | 13 +- netbox/templates/dcim/inventoryitem_edit.html | 108 ++++++++++++++++ 4 files changed, 219 insertions(+), 23 deletions(-) create mode 100644 netbox/templates/dcim/inventoryitem_edit.html diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 1614f4bae..2f17fb3e1 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -1549,15 +1549,61 @@ class InventoryItemForm(DeviceComponentForm): queryset=Manufacturer.objects.all(), required=False ) - component_type = ContentTypeChoiceField( - queryset=ContentType.objects.all(), - limit_choices_to=MODULAR_COMPONENT_MODELS, + + consoleport = DynamicModelChoiceField( + queryset=ConsolePort.objects.all(), required=False, - widget=forms.HiddenInput + query_params={ + 'device_id': '$device' + } ) - component_id = forms.IntegerField( + + consoleserverport = DynamicModelChoiceField( + queryset=ConsoleServerPort.objects.all(), required=False, - widget=forms.HiddenInput + query_params={ + 'device_id': '$device' + } + ) + + frontport = DynamicModelChoiceField( + queryset=FrontPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + } + ) + + interface = DynamicModelChoiceField( + queryset=Interface.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + } + ) + + poweroutlet = DynamicModelChoiceField( + queryset=PowerOutlet.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + } + ) + + powerport = DynamicModelChoiceField( + queryset=PowerPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + } + ) + + rearport = DynamicModelChoiceField( + queryset=RearPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + } ) fieldsets = ( @@ -1565,22 +1611,68 @@ class InventoryItemForm(DeviceComponentForm): ('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')), ) + class Meta: + model = InventoryItem + fields = [ + 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', + 'description', 'tags', + ] + 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') + + if instance: + if type(instance.component) is ConsolePort: + initial['consoleport'] = instance.component + elif type(instance.component) is ConsoleServerPort: + initial['consoleserverport'] = instance.component + elif type(instance.component) is FrontPort: + initial['frontport'] = instance.component + elif type(instance.component) is Interface: + initial['interface'] = instance.component + elif type(instance.component) is PowerPort: + initial['powerport'] = instance.component + elif type(instance.component) is RearPort: + initial['rearport'] = instance.component + else: + self.no_component = True + elif component_type and component_id: + self.no_component = True + if content_type := ContentType.objects.filter(MODULAR_COMPONENT_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 + else: + self.no_component = True + + kwargs['initial'] = initial + super().__init__(*args, **kwargs) # Specifically allow editing the device of IntentoryItems if self.instance.pk: self.fields['device'].disabled = False - class Meta: - model = InventoryItem - fields = [ - 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', - 'description', 'component_type', 'component_id', 'tags', + def clean(self): + super().clean() + + # Handle object assignment + selected_objects = [ + field for field in ( + 'consoleport', 'consoleserverport', 'frontport', 'interface', 'poweroutlet', 'powerport', 'rearport' + ) 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 component roles # diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 658423e52..26a6ade98 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -1146,3 +1146,8 @@ class InventoryItem(MPTTModel, ComponentModel): # When moving an InventoryItem to another device, remove any associated component if self.component and self.component.device != self.device: self.component = None + else: + if self.component and self.component.device != self.device: + raise ValidationError({ + "device": "Cannot assign inventory item to component on another device" + }) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 06a486534..5a6c52dcb 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2913,23 +2913,14 @@ class InventoryItemView(generic.ObjectView): class InventoryItemEditView(generic.ObjectEditView): queryset = InventoryItem.objects.all() form = forms.InventoryItemForm + template_name = 'dcim/inventoryitem_edit.html' class InventoryItemCreateView(generic.ComponentCreateView): queryset = InventoryItem.objects.all() form = forms.InventoryItemCreateForm model_form = forms.InventoryItemForm - - def alter_object(self, instance, request): - # Set component (if any) - component_type = request.GET.get('component_type') - component_id = request.GET.get('component_id') - - if component_type and component_id: - content_type = get_object_or_404(ContentType, pk=component_type) - instance.component = get_object_or_404(content_type.model_class(), pk=component_id) - - return instance + template_name = 'dcim/inventoryitem_edit.html' @register_model_view(InventoryItem, 'delete') diff --git a/netbox/templates/dcim/inventoryitem_edit.html b/netbox/templates/dcim/inventoryitem_edit.html new file mode 100644 index 000000000..8a5fb4ae0 --- /dev/null +++ b/netbox/templates/dcim/inventoryitem_edit.html @@ -0,0 +1,108 @@ +{% extends 'generic/object_edit.html' %} +{% load static %} +{% load form_helpers %} +{% load helpers %} + +{% block form %} +
+
+
InventoryItem
+
+ {% render_field form.device %} + {% render_field form.parent %} + {% render_field form.name %} + {% render_field form.label %} + {% render_field form.role %} + {% render_field form.description %} + {% render_field form.tags %} +
+ +
+
+
Hardware
+
+ {% render_field form.manufacturer %} + {% render_field form.part_id %} + {% render_field form.serial %} + {% render_field form.asset_tag %} +
+ +
+
+
Component Assignment
+
+
+
+ +
+
+
+
+ {% render_field form.consoleport %} +
+
+ {% render_field form.consoleserverport %} +
+
+ {% render_field form.frontport %} +
+
+ {% render_field form.interface %} +
+
+ {% render_field form.poweroutlet %} +
+
+ {% render_field form.powerport %} +
+
+ {% render_field form.rearport %} +
+
+
+ + {% if form.custom_fields %} +
+
+
Custom Fields
+
+ {% render_custom_fields form %} +
+ {% endif %} +{% endblock %}