Skip to content

Commit

Permalink
Fixes: #16905 - Allow filtering on Device Status in InventoryItemTable (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
bctiemann authored Aug 29, 2024
1 parent 8282a6d commit a150e5d
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 51 deletions.
4 changes: 4 additions & 0 deletions netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,10 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
to_field_name='name',
label=_('Virtual Chassis'),
)
device_status = django_filters.MultipleChoiceFilter(
choices=DeviceStatusChoices,
field_name='device__status',
)

def search(self, queryset, name, value):
if not value.strip():
Expand Down
52 changes: 42 additions & 10 deletions netbox/dcim/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
},
label=_('Device')
)
device_status = forms.MultipleChoiceField(
choices=DeviceStatusChoices,
required=False,
label=_('Device Status'),
)


class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
Expand Down Expand Up @@ -1173,7 +1178,9 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
)
type = forms.MultipleChoiceField(
Expand All @@ -1195,7 +1202,10 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
)
type = forms.MultipleChoiceField(
Expand All @@ -1217,7 +1227,9 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'type', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
)
type = forms.MultipleChoiceField(
Expand All @@ -1234,7 +1246,10 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'type', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
)
type = forms.MultipleChoiceField(
Expand All @@ -1254,7 +1269,10 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
FieldSet('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', name=_('Wireless')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', 'vdc_id',
name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
)
selector_fields = ('filter_id', 'q', 'device_id')
Expand Down Expand Up @@ -1362,7 +1380,9 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
),
FieldSet('cabled', 'occupied', name=_('Cable')),
)
model = FrontPort
Expand All @@ -1384,7 +1404,10 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
FieldSet('cabled', 'occupied', name=_('Cable')),
)
type = forms.MultipleChoiceField(
Expand All @@ -1405,7 +1428,10 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'position', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
)
tag = TagFilterField(model)
position = forms.CharField(
Expand All @@ -1420,7 +1446,10 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
)
tag = TagFilterField(model)

Expand All @@ -1434,7 +1463,10 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
name=_('Attributes')
),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
FieldSet(
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
)
role_id = DynamicModelMultipleChoiceField(
queryset=InventoryItemRole.objects.all(),
Expand Down
5 changes: 5 additions & 0 deletions netbox/dcim/tables/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ class DeviceComponentTable(NetBoxTable):
linkify=True,
order_by=('_name',)
)
device_status = columns.ChoiceFieldColumn(
accessor=tables.A('device__status'),
verbose_name=_('Device Status'),
color=lambda x: x.device.get_status_color(),
)

class Meta(NetBoxTable.Meta):
order_by = ('device', 'name')
Expand Down
81 changes: 46 additions & 35 deletions netbox/dcim/tests/test_filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ def test_device_role(self):
params = {'device_role': [role[0].slug, role[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_device_status(self):
params = {'device_status': ['active']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'device_status': ['offline', 'active']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)


class DeviceComponentTemplateFilterSetTests:

Expand Down Expand Up @@ -2588,10 +2594,10 @@ def setUpTestData(cls):
Rack.objects.bulk_create(racks)

devices = (
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_types[0], role=roles[0], site=sites[3]), # For cable connections
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
Device(name=None, device_type=device_types[0], role=roles[0], site=sites[3], status='planned'), # For cable connections
)
Device.objects.bulk_create(devices)

Expand Down Expand Up @@ -2768,10 +2774,10 @@ def setUpTestData(cls):
Rack.objects.bulk_create(racks)

devices = (
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
)
Device.objects.bulk_create(devices)

Expand Down Expand Up @@ -2948,10 +2954,10 @@ def setUpTestData(cls):
Rack.objects.bulk_create(racks)

devices = (
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
)
Device.objects.bulk_create(devices)

Expand Down Expand Up @@ -3136,10 +3142,10 @@ def setUpTestData(cls):
Rack.objects.bulk_create(racks)

devices = (
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
)
Device.objects.bulk_create(devices)

Expand Down Expand Up @@ -3334,7 +3340,8 @@ def setUpTestData(cls):
rack=racks[0],
virtual_chassis=virtual_chassis,
vc_position=1,
vc_priority=1
vc_priority=1,
status='active',
),
Device(
name='Device 1B',
Expand All @@ -3345,30 +3352,34 @@ def setUpTestData(cls):
rack=racks[2],
virtual_chassis=virtual_chassis,
vc_position=2,
vc_priority=1
vc_priority=1,
status='active',
),
Device(
name='Device 2',
device_type=device_types[1],
role=roles[1],
site=sites[1],
location=locations[1],
rack=racks[1]
rack=racks[1],
status='offline',
),
Device(
name='Device 3',
device_type=device_types[2],
role=roles[2],
site=sites[2],
location=locations[2],
rack=racks[2]
rack=racks[2],
status='planned',
),
# For cable connections
Device(
name=None,
device_type=device_types[2],
role=roles[2],
site=sites[3]
site=sites[3],
status='planned',
),
)
Device.objects.bulk_create(devices)
Expand Down Expand Up @@ -3814,10 +3825,10 @@ def setUpTestData(cls):
Rack.objects.bulk_create(racks)

devices = (
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
)
Device.objects.bulk_create(devices)

Expand Down Expand Up @@ -4003,10 +4014,10 @@ def setUpTestData(cls):
Rack.objects.bulk_create(racks)

devices = (
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3]), # For cable connections
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
Device(name=None, device_type=device_types[2], role=roles[2], site=sites[3], status='planned'), # For cable connections
)
Device.objects.bulk_create(devices)

Expand Down Expand Up @@ -4184,9 +4195,9 @@ def setUpTestData(cls):
Rack.objects.bulk_create(racks)

devices = (
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
)
Device.objects.bulk_create(devices)

Expand Down Expand Up @@ -4313,9 +4324,9 @@ def setUpTestData(cls):
Rack.objects.bulk_create(racks)

devices = (
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name='Device 1', device_type=device_types[0], role=roles[0], site=sites[0], location=locations[0], rack=racks[0], status='active'),
Device(name='Device 2', device_type=device_types[1], role=roles[1], site=sites[1], location=locations[1], rack=racks[1], status='active'),
Device(name='Device 3', device_type=device_types[2], role=roles[2], site=sites[2], location=locations[2], rack=racks[2], status='offline'),
)
Device.objects.bulk_create(devices)

Expand Down
Loading

0 comments on commit a150e5d

Please sign in to comment.