Skip to content

Commit

Permalink
Merge pull request #8186 from netbox-community/8118-inventoryitem-tem…
Browse files Browse the repository at this point in the history
…plate

Closes #8118: Inventory item templates
  • Loading branch information
jeremystretch committed Dec 29, 2021
2 parents 3bb485d + 1edf80d commit ae3c871
Show file tree
Hide file tree
Showing 28 changed files with 766 additions and 63 deletions.
4 changes: 2 additions & 2 deletions docs/models/dcim/devicetype.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ A device type represents a particular make and model of hardware that exists in

Device types are instantiated as devices installed within sites and/or equipment racks. For example, you might define a device type to represent a Juniper EX4300-48T network switch with 48 Ethernet interfaces. You can then create multiple _instances_ of this type named "switch1," "switch2," and so on. Each device will automatically inherit the components (such as interfaces) of its device type at the time of creation. However, changes made to a device type will **not** apply to instances of that device type retroactively.

Some devices house child devices which share physical resources, like space and power, but which functional independently from one another. A common example of this is blade server chassis. Each device type is designated as one of the following:
Some devices house child devices which share physical resources, like space and power, but which function independently. A common example of this is blade server chassis. Each device type is designated as one of the following:

* A parent device (which has device bays)
* A child device (which must be installed within a device bay)
* Neither

!!! note
This parent/child relationship is **not** suitable for modeling chassis-based devices, wherein child members share a common control plane. Instead, line cards and similarly non-autonomous hardware should be modeled as inventory items within a device, with any associated interfaces or other components assigned directly to the device.
This parent/child relationship is **not** suitable for modeling chassis-based devices, wherein child members share a common control plane. Instead, line cards and similarly non-autonomous hardware should be modeled as modules or inventory items within a device.

A device type may optionally specify an airflow direction, such as front-to-rear, rear-to-front, or passive. Airflow direction may also be set separately per device. If it is not defined for a device at the time of its creation, it will inherit the airflow setting of its device type.
6 changes: 3 additions & 3 deletions docs/models/dcim/inventoryitem.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Inventory Items

Inventory items represent hardware components installed within a device, such as a power supply or CPU or line card. Inventory items are distinct from other device components in that they cannot be templatized on a device type, and cannot be connected by cables. They are intended to be used primarily for inventory purposes.
Inventory items represent hardware components installed within a device, such as a power supply or CPU or line card. They are intended to be used primarily for inventory purposes.

Each inventory item can be assigned a functional role, manufacturer, part ID, serial number, and asset tag (all optional). A boolean toggle is also provided to indicate whether each item was entered manually or discovered automatically (by some process outside of NetBox).
Each inventory item can be assigned a functional role, manufacturer, part ID, serial number, and asset tag (all optional). A boolean toggle is also provided to indicate whether each item was entered manually or discovered automatically (by some process outside NetBox).

Inventory items are hierarchical in nature, such that any individual item may be designated as the parent for other items. For example, an inventory item might be created to represent a line card which houses several SFP optics, each of which exists as a child item within the device.
Inventory items are hierarchical in nature, such that any individual item may be designated as the parent for other items. For example, an inventory item might be created to represent a line card which houses several SFP optics, each of which exists as a child item within the device. An inventory item may also be associated with a specific component within the same device. For example, you may wish to associate a transceiver with an interface.
3 changes: 3 additions & 0 deletions docs/models/dcim/inventoryitemtemplate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Inventory Item Templates

A template for an inventory item that will be automatically created when instantiating a new device. All attributes of this object will be copied to the new inventory item, including the associations with a parent item and assigned component, if any.
7 changes: 7 additions & 0 deletions docs/release-notes/version-3.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ FIELD_CHOICES = {
}
```

#### Inventory Item Templates ([#8118](https://github.com/netbox-community/netbox/issues/8118))

Inventory items can now be templatized on a device type similar to the other component types. This enables users to better pre-model fixed hardware components.

Inventory item templates can be arranged hierarchically within a device type, and may be assigned to other components. These relationships will be mirrored when instantiating inventory items on a newly-created device.

### Enhancements

* [#7650](https://github.com/netbox-community/netbox/issues/7650) - Add support for local account password validation
Expand All @@ -62,6 +68,7 @@ FIELD_CHOICES = {

* Added the following endpoints:
* `/api/dcim/inventory-item-roles/`
* `/api/dcim/inventory-item-templates/`
* `/api/dcim/modules/`
* `/api/dcim/module-bays/`
* `/api/dcim/module-bay-templates/`
Expand Down
10 changes: 10 additions & 0 deletions netbox/dcim/api/nested_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
'NestedInterfaceTemplateSerializer',
'NestedInventoryItemSerializer',
'NestedInventoryItemRoleSerializer',
'NestedInventoryItemTemplateSerializer',
'NestedManufacturerSerializer',
'NestedModuleBaySerializer',
'NestedModuleBayTemplateSerializer',
Expand Down Expand Up @@ -231,6 +232,15 @@ class Meta:
fields = ['id', 'url', 'display', 'name']


class NestedInventoryItemTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtemplate-detail')
_depth = serializers.IntegerField(source='level', read_only=True)

class Meta:
model = models.InventoryItemTemplate
fields = ['id', 'url', 'display', 'name', '_depth']


#
# Devices
#
Expand Down
34 changes: 34 additions & 0 deletions netbox/dcim/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,40 @@ class Meta:
fields = ['id', 'url', 'display', 'device_type', 'name', 'label', 'description', 'created', 'last_updated']


class InventoryItemTemplateSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtemplate-detail')
device_type = NestedDeviceTypeSerializer()
parent = serializers.PrimaryKeyRelatedField(
queryset=InventoryItemTemplate.objects.all(),
allow_null=True,
default=None
)
role = NestedInventoryItemRoleSerializer(required=False, allow_null=True)
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
component_type = ContentTypeField(
queryset=ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS),
required=False,
allow_null=True
)
component = serializers.SerializerMethodField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True)

class Meta:
model = InventoryItemTemplate
fields = [
'id', 'url', 'display', 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id',
'description', 'component_type', 'component_id', 'component', 'created', 'last_updated', '_depth',
]

@swagger_serializer_method(serializer_or_field=serializers.DictField)
def get_component(self, obj):
if obj.component is None:
return None
serializer = get_serializer_for_model(obj.component, prefix='Nested')
context = {'request': self.context['request']}
return serializer(obj.component, context=context).data


#
# Devices
#
Expand Down
1 change: 1 addition & 0 deletions netbox/dcim/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
router.register('rear-port-templates', views.RearPortTemplateViewSet)
router.register('module-bay-templates', views.ModuleBayTemplateViewSet)
router.register('device-bay-templates', views.DeviceBayTemplateViewSet)
router.register('inventory-item-templates', views.InventoryItemTemplateViewSet)

# Device/modules
router.register('device-roles', views.DeviceRoleViewSet)
Expand Down
6 changes: 6 additions & 0 deletions netbox/dcim/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,12 @@ class DeviceBayTemplateViewSet(ModelViewSet):
filterset_class = filtersets.DeviceBayTemplateFilterSet


class InventoryItemTemplateViewSet(ModelViewSet):
queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
serializer_class = serializers.InventoryItemTemplateSerializer
filterset_class = filtersets.InventoryItemTemplateFilterSet


#
# Device roles
#
Expand Down
12 changes: 12 additions & 0 deletions netbox/dcim/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@
# Device components
#

MODULAR_COMPONENT_TEMPLATE_MODELS = Q(
app_label='dcim',
model__in=(
'consoleporttemplate',
'consoleserverporttemplate',
'frontporttemplate',
'interfacetemplate',
'poweroutlettemplate',
'powerporttemplate',
'rearporttemplate',
))

MODULAR_COMPONENT_MODELS = Q(
app_label='dcim',
model__in=(
Expand Down
44 changes: 44 additions & 0 deletions netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
'InterfaceTemplateFilterSet',
'InventoryItemFilterSet',
'InventoryItemRoleFilterSet',
'InventoryItemTemplateFilterSet',
'LocationFilterSet',
'ManufacturerFilterSet',
'ModuleBayFilterSet',
Expand Down Expand Up @@ -687,6 +688,49 @@ class Meta:
fields = ['id', 'name']


class InventoryItemTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItemTemplate.objects.all(),
label='Parent inventory item (ID)',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
)
role_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItemRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug',
queryset=InventoryItemRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
)
component_type = ContentTypeFilter()
component_id = MultiValueNumberFilter()

class Meta:
model = InventoryItemTemplate
fields = ['id', 'name', 'label', 'part_id']

def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value) |
Q(part_id__icontains=value) |
Q(description__icontains=value)
)
return queryset.filter(qs_filter)


class DeviceRoleFilterSet(OrganizationalModelFilterSet):
tag = TagFilter()

Expand Down
26 changes: 26 additions & 0 deletions netbox/dcim/forms/bulk_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
'InterfaceTemplateBulkEditForm',
'InventoryItemBulkEditForm',
'InventoryItemRoleBulkEditForm',
'InventoryItemTemplateBulkEditForm',
'LocationBulkEditForm',
'ManufacturerBulkEditForm',
'ModuleBulkEditForm',
Expand Down Expand Up @@ -907,6 +908,31 @@ class Meta:
nullable_fields = ('label', 'description')


class InventoryItemTemplateBulkEditForm(BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=InventoryItemTemplate.objects.all(),
widget=forms.MultipleHiddenInput()
)
label = forms.CharField(
max_length=64,
required=False
)
description = forms.CharField(
required=False
)
role = DynamicModelChoiceField(
queryset=InventoryItemRole.objects.all(),
required=False
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)

class Meta:
nullable_fields = ['label', 'role', 'manufacturer', 'part_id', 'description']


#
# Device components
#
Expand Down
43 changes: 43 additions & 0 deletions netbox/dcim/forms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
'InterfaceTemplateForm',
'InventoryItemForm',
'InventoryItemRoleForm',
'InventoryItemTemplateForm',
'LocationForm',
'ManufacturerForm',
'ModuleForm',
Expand Down Expand Up @@ -1073,6 +1074,48 @@ class Meta:
}


class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
parent = DynamicModelChoiceField(
queryset=InventoryItem.objects.all(),
required=False,
query_params={
'device_id': '$device'
}
)
role = DynamicModelChoiceField(
queryset=InventoryItemRole.objects.all(),
required=False
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)
component_type = ContentTypeChoiceField(
queryset=ContentType.objects.all(),
limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS,
required=False,
widget=forms.HiddenInput
)
component_id = forms.IntegerField(
required=False,
widget=forms.HiddenInput
)

class Meta:
model = InventoryItemTemplate
fields = [
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
'component_type', 'component_id',
]
fieldsets = (
('Inventory Item', ('device_type', 'parent', 'name', 'label', 'role', 'description')),
('Hardware', ('manufacturer', 'part_id')),
)
widgets = {
'device_type': forms.HiddenInput(),
}


#
# Device components
#
Expand Down
Loading

0 comments on commit ae3c871

Please sign in to comment.