Skip to content

Commit

Permalink
Initial work on #7846
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Dec 27, 2021
1 parent a58f1c6 commit 99d5013
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 22 deletions.
19 changes: 17 additions & 2 deletions netbox/dcim/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,17 +810,32 @@ class InventoryItemSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail')
device = NestedDeviceSerializer()
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
manufacturer = NestedManufacturerSerializer(required=False, 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_MODELS),
required=False,
allow_null=True
)
component = serializers.SerializerMethodField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True)

class Meta:
model = InventoryItem
fields = [
'id', 'url', 'display', 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial',
'asset_tag', 'discovered', 'description', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
'asset_tag', 'discovered', 'description', 'component_type', 'component_id', 'component', 'tags',
'custom_fields', '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


#
# Device component roles
Expand Down
21 changes: 18 additions & 3 deletions netbox/dcim/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,31 @@


#
# PowerFeeds
# Power feeds
#

POWERFEED_VOLTAGE_DEFAULT = 120

POWERFEED_AMPERAGE_DEFAULT = 20

POWERFEED_MAX_UTILIZATION_DEFAULT = 80 # Percentage


#
# Device components
#

MODULAR_COMPONENT_MODELS = Q(
app_label='dcim',
model__in=(
'consoleport',
'consoleserverport',
'frontport',
'interface',
'poweroutlet',
'powerport',
'rearport',
))


#
# Cabling and connections
#
Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,8 @@ class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
to_field_name='slug',
label='Role (slug)',
)
component_type = ContentTypeFilter()
component_id = MultiValueNumberFilter()
serial = django_filters.CharFilter(
lookup_expr='iexact'
)
Expand Down
20 changes: 17 additions & 3 deletions netbox/dcim/forms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from ipam.models import IPAddress, VLAN, VLANGroup, ASN
from tenancy.forms import TenancyForm
from utilities.forms import (
APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SelectWithPK, SmallTextarea,
APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SelectWithPK, SmallTextarea,
SlugField, StaticSelect,
)
from virtualization.models import Cluster, ClusterGroup
Expand Down Expand Up @@ -1376,6 +1376,15 @@ class InventoryItemForm(CustomFieldModelForm):
queryset=Manufacturer.objects.all(),
required=False
)
component_type = ContentTypeChoiceField(
queryset=ContentType.objects.all(),
limit_choices_to=MODULAR_COMPONENT_MODELS,
required=False,
widget=StaticSelect
)
component_id = forms.IntegerField(
required=False
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
Expand All @@ -1385,8 +1394,13 @@ class Meta:
model = InventoryItem
fields = [
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
'description', 'tags',
'description', 'component_type', 'component_id', 'tags',
]
fieldsets = (
('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
('Component', ('component_type', 'component_id')),
)


#
Expand Down
16 changes: 13 additions & 3 deletions netbox/dcim/forms/object_create.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django import forms
from django.contrib.contenttypes.models import ContentType

from dcim.choices import *
from dcim.constants import *
Expand All @@ -7,8 +8,8 @@
from extras.models import Tag
from ipam.models import VLAN
from utilities.forms import (
add_blank_choice, BootstrapMixin, ColorField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
ExpandableNameField, StaticSelect,
add_blank_choice, BootstrapMixin, ColorField, ContentTypeChoiceField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, ExpandableNameField, StaticSelect,
)
from wireless.choices import *
from .common import InterfaceCommonForm
Expand Down Expand Up @@ -680,7 +681,16 @@ class InventoryItemCreateForm(ComponentCreateForm):
max_length=50,
required=False,
)
component_type = ContentTypeChoiceField(
queryset=ContentType.objects.all(),
limit_choices_to=MODULAR_COMPONENT_MODELS,
required=False,
widget=StaticSelect
)
component_id = forms.IntegerField(
required=False
)
field_order = (
'device', 'parent', 'name_pattern', 'label_pattern', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
'description', 'tags',
'description', 'component_type', 'component_id', 'tags',
)
23 changes: 23 additions & 0 deletions netbox/dcim/migrations/0147_inventoryitem_component.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('dcim', '0146_inventoryitemrole'),
]

operations = [
migrations.AddField(
model_name='inventoryitem',
name='component_id',
field=models.PositiveBigIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='inventoryitem',
name='component_type',
field=models.ForeignKey(blank=True, limit_choices_to=models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'poweroutlet', 'powerport', 'rearport'))), null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype'),
),
]
22 changes: 22 additions & 0 deletions netbox/dcim/models/device_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ class ModularComponentModel(ComponentModel):
blank=True,
null=True
)
inventory_items = GenericRelation(
to='dcim.InventoryItem',
content_type_field='component_type',
object_id_field='component_id',
related_name='%(class)ss',
)

class Meta:
abstract = True
Expand Down Expand Up @@ -994,6 +1000,22 @@ class InventoryItem(MPTTModel, ComponentModel):
null=True,
db_index=True
)
component_type = models.ForeignKey(
to=ContentType,
limit_choices_to=MODULAR_COMPONENT_MODELS,
on_delete=models.PROTECT,
related_name='+',
blank=True,
null=True
)
component_id = models.PositiveBigIntegerField(
blank=True,
null=True
)
component = GenericForeignKey(
ct_field='component_type',
fk_field='component_id'
)
role = models.ForeignKey(
to='dcim.InventoryItemRole',
on_delete=models.PROTECT,
Expand Down
15 changes: 10 additions & 5 deletions netbox/dcim/tables/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,9 @@ class InventoryItemTable(DeviceComponentTable):
manufacturer = tables.Column(
linkify=True
)
component = tables.Column(
linkify=True
)
discovered = BooleanColumn()
tags = TagColumn(
url_name='dcim:inventoryitem_list'
Expand All @@ -790,9 +793,11 @@ class Meta(BaseTable.Meta):
model = InventoryItem
fields = (
'pk', 'id', 'name', 'device', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
'description', 'discovered', 'tags',
'component', 'description', 'discovered', 'tags',
)
default_columns = (
'pk', 'name', 'device', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
)
default_columns = ('pk', 'name', 'device', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag')


class DeviceInventoryItemTable(InventoryItemTable):
Expand All @@ -810,11 +815,11 @@ class DeviceInventoryItemTable(InventoryItemTable):
class Meta(BaseTable.Meta):
model = InventoryItem
fields = (
'pk', 'id', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description',
'discovered', 'tags', 'actions',
'pk', 'id', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
'description', 'discovered', 'tags', 'actions',
)
default_columns = (
'pk', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'actions',
'pk', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component', 'actions',
)


Expand Down
19 changes: 16 additions & 3 deletions netbox/dcim/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1632,28 +1632,41 @@ def setUpTestData(cls):
)
InventoryItemRole.objects.bulk_create(roles)

InventoryItem.objects.create(device=device, name='Inventory Item 1', role=roles[0], manufacturer=manufacturer)
InventoryItem.objects.create(device=device, name='Inventory Item 2', role=roles[0], manufacturer=manufacturer)
InventoryItem.objects.create(device=device, name='Inventory Item 3', role=roles[0], manufacturer=manufacturer)
interfaces = (
Interface(device=device, name='Interface 1'),
Interface(device=device, name='Interface 2'),
Interface(device=device, name='Interface 3'),
)
Interface.objects.bulk_create(interfaces)

InventoryItem.objects.create(device=device, name='Inventory Item 1', role=roles[0], manufacturer=manufacturer, component=interfaces[0])
InventoryItem.objects.create(device=device, name='Inventory Item 2', role=roles[0], manufacturer=manufacturer, component=interfaces[1])
InventoryItem.objects.create(device=device, name='Inventory Item 3', role=roles[0], manufacturer=manufacturer, component=interfaces[2])

cls.create_data = [
{
'device': device.pk,
'name': 'Inventory Item 4',
'role': roles[1].pk,
'manufacturer': manufacturer.pk,
'component_type': 'dcim.interface',
'component_id': interfaces[0].pk,
},
{
'device': device.pk,
'name': 'Inventory Item 5',
'role': roles[1].pk,
'manufacturer': manufacturer.pk,
'component_type': 'dcim.interface',
'component_id': interfaces[1].pk,
},
{
'device': device.pk,
'name': 'Inventory Item 6',
'role': roles[1].pk,
'manufacturer': manufacturer.pk,
'component_type': 'dcim.interface',
'component_id': interfaces[2].pk,
},
]

Expand Down
16 changes: 13 additions & 3 deletions netbox/dcim/tests/test_filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3004,10 +3004,16 @@ def setUpTestData(cls):
)
InventoryItemRole.objects.bulk_create(roles)

components = (
Interface.objects.create(device=devices[0], name='Interface 1'),
ConsolePort.objects.create(device=devices[1], name='Console Port 1'),
ConsoleServerPort.objects.create(device=devices[2], name='Console Server Port 1'),
)

inventory_items = (
InventoryItem(device=devices[0], role=roles[0], manufacturer=manufacturers[0], name='Inventory Item 1', label='A', part_id='1001', serial='ABC', asset_tag='1001', discovered=True, description='First'),
InventoryItem(device=devices[1], role=roles[1], manufacturer=manufacturers[1], name='Inventory Item 2', label='B', part_id='1002', serial='DEF', asset_tag='1002', discovered=True, description='Second'),
InventoryItem(device=devices[2], role=roles[2], manufacturer=manufacturers[2], name='Inventory Item 3', label='C', part_id='1003', serial='GHI', asset_tag='1003', discovered=False, description='Third'),
InventoryItem(device=devices[0], role=roles[0], manufacturer=manufacturers[0], name='Inventory Item 1', label='A', part_id='1001', serial='ABC', asset_tag='1001', discovered=True, description='First', component=components[0]),
InventoryItem(device=devices[1], role=roles[1], manufacturer=manufacturers[1], name='Inventory Item 2', label='B', part_id='1002', serial='DEF', asset_tag='1002', discovered=True, description='Second', component=components[1]),
InventoryItem(device=devices[2], role=roles[2], manufacturer=manufacturers[2], name='Inventory Item 3', label='C', part_id='1003', serial='GHI', asset_tag='1003', discovered=False, description='Third', component=components[2]),
)
for i in inventory_items:
i.save()
Expand Down Expand Up @@ -3103,6 +3109,10 @@ def test_serial(self):
params = {'serial': 'abc'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_component_type(self):
params = {'component_type': 'dcim.interface'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)


class InventoryItemRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = InventoryItemRole.objects.all()
Expand Down
10 changes: 10 additions & 0 deletions netbox/templates/dcim/inventoryitem.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ <h5 class="card-header">Inventory Item</h5>
{% endif %}
</td>
</tr>
<tr>
<th scope="row">Component</th>
<td>
{% if object.component %}
<a href="{{ object.component.get_absolute_url }}">{{ object.component }}</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<th scope="row">Manufacturer</th>
<td>
Expand Down

0 comments on commit 99d5013

Please sign in to comment.