mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
* Allow re-assigning InventoryItem components * Refactor logic for finding initial component assignment on InventoryItems * PEP8 fix * Fix wrong HTML causing tab list to extend past the end of the parent row * Tweak form field labels Co-authored-by: jeremystretch <jstretch@ns1.com>
This commit is contained in:
parent
1c636ea127
commit
b9f8370097
@ -1549,15 +1549,63 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
component_type = ContentTypeChoiceField(
|
|
||||||
queryset=ContentType.objects.all(),
|
# Assigned component selectors
|
||||||
limit_choices_to=MODULAR_COMPONENT_MODELS,
|
consoleport = DynamicModelChoiceField(
|
||||||
|
queryset=ConsolePort.objects.all(),
|
||||||
required=False,
|
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,
|
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 = (
|
fieldsets = (
|
||||||
@ -1565,22 +1613,61 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
|
('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):
|
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)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Specifically allow editing the device of IntentoryItems
|
# Specifically allow editing the device of IntentoryItems
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
self.fields['device'].disabled = False
|
self.fields['device'].disabled = False
|
||||||
|
|
||||||
class Meta:
|
def clean(self):
|
||||||
model = InventoryItem
|
super().clean()
|
||||||
fields = [
|
|
||||||
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
# Handle object assignment
|
||||||
'description', 'component_type', 'component_id', 'tags',
|
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
|
# Device component roles
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -1146,3 +1146,8 @@ class InventoryItem(MPTTModel, ComponentModel):
|
|||||||
# When moving an InventoryItem to another device, remove any associated component
|
# When moving an InventoryItem to another device, remove any associated component
|
||||||
if self.component and self.component.device != self.device:
|
if self.component and self.component.device != self.device:
|
||||||
self.component = None
|
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"
|
||||||
|
})
|
||||||
|
@ -2914,23 +2914,14 @@ class InventoryItemView(generic.ObjectView):
|
|||||||
class InventoryItemEditView(generic.ObjectEditView):
|
class InventoryItemEditView(generic.ObjectEditView):
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
form = forms.InventoryItemForm
|
form = forms.InventoryItemForm
|
||||||
|
template_name = 'dcim/inventoryitem_edit.html'
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemCreateView(generic.ComponentCreateView):
|
class InventoryItemCreateView(generic.ComponentCreateView):
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
form = forms.InventoryItemCreateForm
|
form = forms.InventoryItemCreateForm
|
||||||
model_form = forms.InventoryItemForm
|
model_form = forms.InventoryItemForm
|
||||||
|
template_name = 'dcim/inventoryitem_edit.html'
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(InventoryItem, 'delete')
|
@register_model_view(InventoryItem, 'delete')
|
||||||
|
106
netbox/templates/dcim/inventoryitem_edit.html
Normal file
106
netbox/templates/dcim/inventoryitem_edit.html
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
{% extends 'generic/object_edit.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<h5 class="offset-sm-3">InventoryItem</h5>
|
||||||
|
</div>
|
||||||
|
{% 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 %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<h5 class="offset-sm-3">Hardware</h5>
|
||||||
|
</div>
|
||||||
|
{% render_field form.manufacturer %}
|
||||||
|
{% render_field form.part_id %}
|
||||||
|
{% render_field form.serial %}
|
||||||
|
{% render_field form.asset_tag %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<h5 class="offset-sm-3">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.consoleport or form.no_component %}active{% endif %}">
|
||||||
|
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.consoleserverport %}active{% endif %}">
|
||||||
|
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.frontport %}active{% endif %}">
|
||||||
|
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.interface %}active{% endif %}">
|
||||||
|
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.poweroutlet %}active{% endif %}">
|
||||||
|
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.powerport %}active{% endif %}">
|
||||||
|
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.rearport %}active{% endif %}">
|
||||||
|
Rear Port
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content p-0 border-0">
|
||||||
|
<div class="tab-pane {% if form.initial.consoleport or form.no_component %}active{% endif %}" id="consoleport" role="tabpanel" aria-labeled-by="consoleport_tab">
|
||||||
|
{% render_field form.consoleport %}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane {% if form.initial.consoleserverport %}active{% endif %}" id="consoleserverport" role="tabpanel" aria-labeled-by="consoleserverport_tab">
|
||||||
|
{% render_field form.consoleserverport %}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane {% if form.initial.frontport %}active{% endif %}" id="frontport" role="tabpanel" aria-labeled-by="frontport_tab">
|
||||||
|
{% render_field form.frontport %}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane {% if form.initial.interface %}active{% endif %}" id="interface" role="tabpanel" aria-labeled-by="interface_tab">
|
||||||
|
{% render_field form.interface %}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane {% if form.initial.poweroutlet %}active{% endif %}" id="poweroutlet" role="tabpanel" aria-labeled-by="poweroutlet_tab">
|
||||||
|
{% render_field form.poweroutlet %}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane {% if form.initial.powerport %}active{% endif %}" id="powerport" role="tabpanel" aria-labeled-by="powerport_tab">
|
||||||
|
{% render_field form.powerport %}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane {% if form.initial.rearport %}active{% endif %}" id="rearport" role="tabpanel" aria-labeled-by="rearport_tab">
|
||||||
|
{% render_field form.rearport %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if form.custom_fields %}
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
|
</div>
|
||||||
|
{% render_custom_fields form %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user