diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 1614f4bae..91e0266f0 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -1549,15 +1549,63 @@ class InventoryItemForm(DeviceComponentForm): queryset=Manufacturer.objects.all(), required=False ) - component_type = ContentTypeChoiceField( - queryset=ContentType.objects.all(), - limit_choices_to=MODULAR_COMPONENT_MODELS, + + # Assigned component selectors + consoleport = DynamicModelChoiceField( + queryset=ConsolePort.objects.all(), required=False, - widget=forms.HiddenInput + query_params={ + 'device_id': '$device' + }, + label=_('Console port') ) - component_id = forms.IntegerField( + consoleserverport = DynamicModelChoiceField( + queryset=ConsoleServerPort.objects.all(), required=False, - widget=forms.HiddenInput + query_params={ + 'device_id': '$device' + }, + label=_('Console server port') + ) + frontport = DynamicModelChoiceField( + queryset=FrontPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Front port') + ) + interface = DynamicModelChoiceField( + queryset=Interface.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Interface') + ) + poweroutlet = DynamicModelChoiceField( + queryset=PowerOutlet.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Power outlet') + ) + powerport = DynamicModelChoiceField( + queryset=PowerPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Power port') + ) + rearport = DynamicModelChoiceField( + queryset=RearPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Rear port') ) fieldsets = ( @@ -1565,22 +1613,61 @@ 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') + + # Used for picking the default active tab for component selection + self.no_component = True + + if instance: + # When editing set the initial value for component selectin + for component_model in ContentType.objects.filter(MODULAR_COMPONENT_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_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) # 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 870cbcc18..115c16112 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2914,23 +2914,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..006b6f008 --- /dev/null +++ b/netbox/templates/dcim/inventoryitem_edit.html @@ -0,0 +1,106 @@ +{% 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 %}