mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
16783 Add status field to InventoryItem (#17627)
* 16783 Add status field to InventoryItem * 16783 fix tests * 16783 fix tests * 16783 review changes
This commit is contained in:
parent
c60a0f4f56
commit
8cd0a3215c
@ -44,3 +44,7 @@ The serial number assigned by the manufacturer.
|
||||
### Asset Tag
|
||||
|
||||
A unique, locally-administered label used to identify hardware resources.
|
||||
|
||||
### Status
|
||||
|
||||
The inventory item's operational status.
|
||||
|
@ -345,11 +345,12 @@ class InventoryItemSerializer(NetBoxModelSerializer):
|
||||
)
|
||||
component = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
status = ChoiceField(choices=InventoryItemStatusChoices, required=False)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'role', 'manufacturer',
|
||||
'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role', 'manufacturer',
|
||||
'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type', 'component_id',
|
||||
'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
|
||||
]
|
||||
|
@ -1648,3 +1648,27 @@ class VirtualDeviceContextStatusChoices(ChoiceSet):
|
||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
||||
(STATUS_OFFLINE, _('Offline'), 'red'),
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# InventoryItem
|
||||
#
|
||||
|
||||
class InventoryItemStatusChoices(ChoiceSet):
|
||||
key = 'InventoryItem.status'
|
||||
|
||||
STATUS_OFFLINE = 'offline'
|
||||
STATUS_ACTIVE = 'active'
|
||||
STATUS_PLANNED = 'planned'
|
||||
STATUS_STAGED = 'staged'
|
||||
STATUS_FAILED = 'failed'
|
||||
STATUS_DECOMMISSIONING = 'decommissioning'
|
||||
|
||||
CHOICES = [
|
||||
(STATUS_OFFLINE, _('Offline'), 'gray'),
|
||||
(STATUS_ACTIVE, _('Active'), 'green'),
|
||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
||||
(STATUS_STAGED, _('Staged'), 'blue'),
|
||||
(STATUS_FAILED, _('Failed'), 'red'),
|
||||
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
|
||||
]
|
||||
|
@ -1860,10 +1860,14 @@ class InventoryItemFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
||||
serial = MultiValueCharFilter(
|
||||
lookup_expr='iexact'
|
||||
)
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
choices=InventoryItemStatusChoices,
|
||||
null_value=None
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = ('id', 'name', 'label', 'part_id', 'asset_tag', 'description', 'discovered')
|
||||
fields = ('id', 'name', 'label', 'part_id', 'asset_tag', 'status', 'description', 'discovered')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
|
@ -1661,10 +1661,16 @@ class InventoryItemBulkEditForm(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
)
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
choices=add_blank_choice(InventoryItemStatusChoices),
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
|
||||
model = InventoryItem
|
||||
fieldsets = (
|
||||
FieldSet('device', 'label', 'role', 'manufacturer', 'part_id', 'description'),
|
||||
FieldSet('device', 'label', 'role', 'manufacturer', 'part_id', 'status', 'description'),
|
||||
)
|
||||
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
|
||||
|
||||
|
@ -1103,11 +1103,16 @@ class InventoryItemImportForm(NetBoxModelImportForm):
|
||||
required=False,
|
||||
help_text=_('Component Name')
|
||||
)
|
||||
status = CSVChoiceField(
|
||||
label=_('Status'),
|
||||
choices=InventoryItemStatusChoices,
|
||||
help_text=_('Operational status')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = (
|
||||
'device', 'name', 'label', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag', 'discovered',
|
||||
'device', 'name', 'label', 'status', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag', 'discovered',
|
||||
'description', 'tags', 'component_type', 'component_name',
|
||||
)
|
||||
|
||||
|
@ -35,7 +35,6 @@ __all__ = (
|
||||
'LocationFilterForm',
|
||||
'ManufacturerFilterForm',
|
||||
'ModuleFilterForm',
|
||||
'ModuleFilterForm',
|
||||
'ModuleBayFilterForm',
|
||||
'ModuleTypeFilterForm',
|
||||
'PlatformFilterForm',
|
||||
@ -1553,6 +1552,11 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
label=_('Status'),
|
||||
choices=InventoryItemStatusChoices,
|
||||
required=False
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
|
@ -1576,7 +1576,7 @@ class InventoryItemForm(DeviceComponentForm):
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('device', 'parent', 'name', 'label', 'role', 'description', 'tags', name=_('Inventory Item')),
|
||||
FieldSet('device', 'parent', 'name', 'label', 'status', 'role', 'description', 'tags', name=_('Inventory Item')),
|
||||
FieldSet('manufacturer', 'part_id', 'serial', 'asset_tag', name=_('Hardware')),
|
||||
FieldSet(
|
||||
TabbedGroups(
|
||||
@ -1596,7 +1596,7 @@ class InventoryItemForm(DeviceComponentForm):
|
||||
model = InventoryItem
|
||||
fields = [
|
||||
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
||||
'description', 'tags',
|
||||
'status', 'description', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
18
netbox/dcim/migrations/0191_inventoryitem_status.py
Normal file
18
netbox/dcim/migrations/0191_inventoryitem_status.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.9 on 2024-09-26 20:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0190_nested_modules'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='inventoryitem',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
]
|
@ -1244,6 +1244,12 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
||||
ct_field='component_type',
|
||||
fk_field='component_id'
|
||||
)
|
||||
status = models.CharField(
|
||||
verbose_name=_('status'),
|
||||
max_length=50,
|
||||
choices=InventoryItemStatusChoices,
|
||||
default=InventoryItemStatusChoices.STATUS_ACTIVE
|
||||
)
|
||||
role = models.ForeignKey(
|
||||
to='dcim.InventoryItemRole',
|
||||
on_delete=models.PROTECT,
|
||||
@ -1285,7 +1291,7 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
||||
|
||||
objects = TreeManager()
|
||||
|
||||
clone_fields = ('device', 'parent', 'role', 'manufacturer', 'part_id',)
|
||||
clone_fields = ('device', 'parent', 'role', 'manufacturer', 'status', 'part_id')
|
||||
|
||||
class Meta:
|
||||
ordering = ('device__id', 'parent__id', '_name')
|
||||
@ -1334,3 +1340,6 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
||||
raise ValidationError({
|
||||
"device": _("Cannot assign inventory item to component on another device")
|
||||
})
|
||||
|
||||
def get_status_color(self):
|
||||
return InventoryItemStatusChoices.colors.get(self.status)
|
||||
|
@ -946,6 +946,9 @@ class InventoryItemTable(DeviceComponentTable):
|
||||
verbose_name=_('Discovered'),
|
||||
false_mark=None
|
||||
)
|
||||
status = columns.ChoiceFieldColumn(
|
||||
verbose_name=_('Status'),
|
||||
)
|
||||
parent = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Parent'),
|
||||
@ -958,11 +961,11 @@ class InventoryItemTable(DeviceComponentTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.InventoryItem
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'parent', 'component', 'label', 'role', 'manufacturer', 'part_id', 'serial',
|
||||
'pk', 'id', 'name', 'device', 'parent', 'component', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial',
|
||||
'asset_tag', 'description', 'discovered', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'device', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
||||
'pk', 'name', 'device', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
||||
)
|
||||
|
||||
|
||||
@ -978,11 +981,11 @@ class DeviceInventoryItemTable(InventoryItemTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.InventoryItem
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
||||
'pk', 'id', 'name', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
||||
'description', 'discovered', 'tags', 'actions',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
||||
'pk', 'name', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
||||
)
|
||||
|
||||
|
||||
|
@ -4751,9 +4751,9 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
)
|
||||
|
||||
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', 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]),
|
||||
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, status=ModuleStatusChoices.STATUS_ACTIVE, 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, status=ModuleStatusChoices.STATUS_PLANNED, 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, status=ModuleStatusChoices.STATUS_FAILED, description='Third', component=components[2]),
|
||||
)
|
||||
for i in inventory_items:
|
||||
i.save()
|
||||
@ -4881,6 +4881,10 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'component_type': 'dcim.interface'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_status(self):
|
||||
params = {'status': [InventoryItemStatusChoices.STATUS_PLANNED, InventoryItemStatusChoices.STATUS_FAILED]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class InventoryItemRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = InventoryItemRole.objects.all()
|
||||
|
@ -2903,6 +2903,7 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
'part_id': '123456',
|
||||
'serial': '123ABC',
|
||||
'asset_tag': 'ABC123',
|
||||
'status': InventoryItemStatusChoices.STATUS_ACTIVE,
|
||||
'description': 'An inventory item',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
@ -2916,6 +2917,7 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
'discovered': False,
|
||||
'part_id': '123456',
|
||||
'serial': '123ABC',
|
||||
'status': InventoryItemStatusChoices.STATUS_ACTIVE,
|
||||
'description': 'An inventory item',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
@ -2927,10 +2929,10 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"device,name,parent",
|
||||
"Device 1,Inventory Item 4,Inventory Item 1",
|
||||
"Device 1,Inventory Item 5,Inventory Item 2",
|
||||
"Device 1,Inventory Item 6,Inventory Item 3",
|
||||
"device,name,parent,status",
|
||||
"Device 1,Inventory Item 4,Inventory Item 1,active",
|
||||
"Device 1,Inventory Item 5,Inventory Item 2,planned",
|
||||
"Device 1,Inventory Item 6,Inventory Item 3,failed",
|
||||
)
|
||||
|
||||
cls.csv_update_data = (
|
||||
|
@ -56,6 +56,10 @@
|
||||
<th scope="row">{% trans "Asset Tag" %}</th>
|
||||
<td>{{ object.asset_tag|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Status" %}</th>
|
||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Description" %}</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
|
Loading…
Reference in New Issue
Block a user