Skip to content

Commit a150e5d

Browse files
authored
Fixes: #16905 - Allow filtering on Device Status in InventoryItemTable (#17260)
* Add device_status as filtering option (and configurable column) for InventoryItemTable * Add device_status to common superclasses for Device Components, and refactor ChoiceFieldColumn to support a "color" callable allowing get_FOO_color behavior to be overridden * Remove unnecessary 'device_status' in fields * Add unit tests for device_status
1 parent 8282a6d commit a150e5d

File tree

5 files changed

+110
-51
lines changed

5 files changed

+110
-51
lines changed

netbox/dcim/filtersets.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,10 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
14111411
to_field_name='name',
14121412
label=_('Virtual Chassis'),
14131413
)
1414+
device_status = django_filters.MultipleChoiceFilter(
1415+
choices=DeviceStatusChoices,
1416+
field_name='device__status',
1417+
)
14141418

14151419
def search(self, queryset, name, value):
14161420
if not value.strip():

netbox/dcim/forms/filtersets.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
129129
},
130130
label=_('Device')
131131
)
132+
device_status = forms.MultipleChoiceField(
133+
choices=DeviceStatusChoices,
134+
required=False,
135+
label=_('Device Status'),
136+
)
132137

133138

134139
class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
@@ -1173,7 +1178,9 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
11731178
FieldSet('q', 'filter_id', 'tag'),
11741179
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
11751180
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1176-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1181+
FieldSet(
1182+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
1183+
),
11771184
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
11781185
)
11791186
type = forms.MultipleChoiceField(
@@ -1195,7 +1202,10 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
11951202
FieldSet('q', 'filter_id', 'tag'),
11961203
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
11971204
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1198-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1205+
FieldSet(
1206+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
1207+
name=_('Device')
1208+
),
11991209
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
12001210
)
12011211
type = forms.MultipleChoiceField(
@@ -1217,7 +1227,9 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
12171227
FieldSet('q', 'filter_id', 'tag'),
12181228
FieldSet('name', 'label', 'type', name=_('Attributes')),
12191229
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1220-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1230+
FieldSet(
1231+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
1232+
),
12211233
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
12221234
)
12231235
type = forms.MultipleChoiceField(
@@ -1234,7 +1246,10 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
12341246
FieldSet('q', 'filter_id', 'tag'),
12351247
FieldSet('name', 'label', 'type', name=_('Attributes')),
12361248
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1237-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1249+
FieldSet(
1250+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
1251+
name=_('Device')
1252+
),
12381253
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
12391254
)
12401255
type = forms.MultipleChoiceField(
@@ -1254,7 +1269,10 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
12541269
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
12551270
FieldSet('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', name=_('Wireless')),
12561271
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1257-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id', name=_('Device')),
1272+
FieldSet(
1273+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', 'vdc_id',
1274+
name=_('Device')
1275+
),
12581276
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
12591277
)
12601278
selector_fields = ('filter_id', 'q', 'device_id')
@@ -1362,7 +1380,9 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
13621380
FieldSet('q', 'filter_id', 'tag'),
13631381
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
13641382
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1365-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1383+
FieldSet(
1384+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
1385+
),
13661386
FieldSet('cabled', 'occupied', name=_('Cable')),
13671387
)
13681388
model = FrontPort
@@ -1384,7 +1404,10 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
13841404
FieldSet('q', 'filter_id', 'tag'),
13851405
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
13861406
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1387-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1407+
FieldSet(
1408+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
1409+
name=_('Device')
1410+
),
13881411
FieldSet('cabled', 'occupied', name=_('Cable')),
13891412
)
13901413
type = forms.MultipleChoiceField(
@@ -1405,7 +1428,10 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
14051428
FieldSet('q', 'filter_id', 'tag'),
14061429
FieldSet('name', 'label', 'position', name=_('Attributes')),
14071430
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1408-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1431+
FieldSet(
1432+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
1433+
name=_('Device')
1434+
),
14091435
)
14101436
tag = TagFilterField(model)
14111437
position = forms.CharField(
@@ -1420,7 +1446,10 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
14201446
FieldSet('q', 'filter_id', 'tag'),
14211447
FieldSet('name', 'label', name=_('Attributes')),
14221448
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1423-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1449+
FieldSet(
1450+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
1451+
name=_('Device')
1452+
),
14241453
)
14251454
tag = TagFilterField(model)
14261455

@@ -1434,7 +1463,10 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
14341463
name=_('Attributes')
14351464
),
14361465
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
1437-
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
1466+
FieldSet(
1467+
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
1468+
name=_('Device')
1469+
),
14381470
)
14391471
role_id = DynamicModelMultipleChoiceField(
14401472
queryset=InventoryItemRole.objects.all(),

netbox/dcim/tables/devices.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ class DeviceComponentTable(NetBoxTable):
290290
linkify=True,
291291
order_by=('_name',)
292292
)
293+
device_status = columns.ChoiceFieldColumn(
294+
accessor=tables.A('device__status'),
295+
verbose_name=_('Device Status'),
296+
color=lambda x: x.device.get_status_color(),
297+
)
293298

294299
class Meta(NetBoxTable.Meta):
295300
order_by = ('device', 'name')

netbox/dcim/tests/test_filtersets.py

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ def test_device_role(self):
3939
params = {'device_role': [role[0].slug, role[1].slug]}
4040
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
4141

42+
def test_device_status(self):
43+
params = {'device_status': ['active']}
44+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
45+
params = {'device_status': ['offline', 'active']}
46+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
47+
4248

4349
class DeviceComponentTemplateFilterSetTests:
4450

@@ -2588,10 +2594,10 @@ def setUpTestData(cls):
25882594
Rack.objects.bulk_create(racks)
25892595

25902596
devices = (
2591-
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
2592-
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
2593-
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
2594-
Device(name=None, device_type=device_types[0], role=roles[0], site=sites[3]), # For cable connections
2597+
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
2598+
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
2599+
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
2600+
Device(name=None, device_type=device_types[0], role=roles[0], site=sites[3], status='planned'), # For cable connections
25952601
)
25962602
Device.objects.bulk_create(devices)
25972603

@@ -2768,10 +2774,10 @@ def setUpTestData(cls):
27682774
Rack.objects.bulk_create(racks)
27692775

27702776
devices = (
2771-
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
2772-
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
2773-
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
2774-
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
2777+
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
2778+
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
2779+
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
2780+
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
27752781
)
27762782
Device.objects.bulk_create(devices)
27772783

@@ -2948,10 +2954,10 @@ def setUpTestData(cls):
29482954
Rack.objects.bulk_create(racks)
29492955

29502956
devices = (
2951-
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
2952-
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
2953-
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
2954-
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
2957+
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
2958+
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
2959+
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
2960+
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
29552961
)
29562962
Device.objects.bulk_create(devices)
29572963

@@ -3136,10 +3142,10 @@ def setUpTestData(cls):
31363142
Rack.objects.bulk_create(racks)
31373143

31383144
devices = (
3139-
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
3140-
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
3141-
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
3142-
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
3145+
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
3146+
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
3147+
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
3148+
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
31433149
)
31443150
Device.objects.bulk_create(devices)
31453151

@@ -3334,7 +3340,8 @@ def setUpTestData(cls):
33343340
rack=racks[0],
33353341
virtual_chassis=virtual_chassis,
33363342
vc_position=1,
3337-
vc_priority=1
3343+
vc_priority=1,
3344+
status='active',
33383345
),
33393346
Device(
33403347
name='Device 1B',
@@ -3345,30 +3352,34 @@ def setUpTestData(cls):
33453352
rack=racks[2],
33463353
virtual_chassis=virtual_chassis,
33473354
vc_position=2,
3348-
vc_priority=1
3355+
vc_priority=1,
3356+
status='active',
33493357
),
33503358
Device(
33513359
name='Device 2',
33523360
device_type=device_types[1],
33533361
role=roles[1],
33543362
site=sites[1],
33553363
location=locations[1],
3356-
rack=racks[1]
3364+
rack=racks[1],
3365+
status='offline',
33573366
),
33583367
Device(
33593368
name='Device 3',
33603369
device_type=device_types[2],
33613370
role=roles[2],
33623371
site=sites[2],
33633372
location=locations[2],
3364-
rack=racks[2]
3373+
rack=racks[2],
3374+
status='planned',
33653375
),
33663376
# For cable connections
33673377
Device(
33683378
name=None,
33693379
device_type=device_types[2],
33703380
role=roles[2],
3371-
site=sites[3]
3381+
site=sites[3],
3382+
status='planned',
33723383
),
33733384
)
33743385
Device.objects.bulk_create(devices)
@@ -3814,10 +3825,10 @@ def setUpTestData(cls):
38143825
Rack.objects.bulk_create(racks)
38153826

38163827
devices = (
3817-
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
3818-
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
3819-
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
3820-
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
3828+
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
3829+
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
3830+
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
3831+
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
38213832
)
38223833
Device.objects.bulk_create(devices)
38233834

@@ -4003,10 +4014,10 @@ def setUpTestData(cls):
40034014
Rack.objects.bulk_create(racks)
40044015

40054016
devices = (
4006-
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
4007-
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
4008-
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
4009-
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
4017+
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
4018+
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
4019+
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
4020+
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
40104021
)
40114022
Device.objects.bulk_create(devices)
40124023

@@ -4184,9 +4195,9 @@ def setUpTestData(cls):
41844195
Rack.objects.bulk_create(racks)
41854196

41864197
devices = (
4187-
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
4188-
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
4189-
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
4198+
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
4199+
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
4200+
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
41904201
)
41914202
Device.objects.bulk_create(devices)
41924203

@@ -4313,9 +4324,9 @@ def setUpTestData(cls):
43134324
Rack.objects.bulk_create(racks)
43144325

43154326
devices = (
4316-
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
4317-
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
4318-
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
4327+
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
4328+
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
4329+
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
43194330
)
43204331
Device.objects.bulk_create(devices)
43214332

0 commit comments

Comments
 (0)