From 41ef3f66c11b0afed929166048508bcdae99eb7a Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 24 Apr 2025 09:42:10 -0700 Subject: [PATCH 1/5] 18334 add location, device, site to module filters --- netbox/dcim/filtersets.py | 52 +++++++++++++++++++++++++++++++++ netbox/dcim/forms/filtersets.py | 43 +++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index eaaee97e7fe..8d27baab9c3 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1337,10 +1337,62 @@ class ModuleFilterSet(NetBoxModelFilterSet): lookup_expr='in', label=_('Module bay (ID)'), ) + region_id = TreeNodeMultipleChoiceFilter( + queryset=Region.objects.all(), + field_name='device__site__region', + lookup_expr='in', + label=_('Region (ID)'), + ) + region = TreeNodeMultipleChoiceFilter( + queryset=Region.objects.all(), + field_name='device__site__region', + lookup_expr='in', + to_field_name='slug', + label=_('Region (slug)'), + ) + site_id = django_filters.ModelMultipleChoiceFilter( + field_name='device__site', + queryset=Site.objects.all(), + label=_('Site (ID)'), + ) + site = django_filters.ModelMultipleChoiceFilter( + field_name='device__site__slug', + queryset=Site.objects.all(), + to_field_name='slug', + label=_('Site name (slug)'), + ) + location_id = django_filters.ModelMultipleChoiceFilter( + field_name='device__location', + queryset=Location.objects.all(), + label=_('Location (ID)'), + ) + location = django_filters.ModelMultipleChoiceFilter( + field_name='device__location__slug', + queryset=Location.objects.all(), + to_field_name='slug', + label=_('Location (slug)'), + ) + rack_id = django_filters.ModelMultipleChoiceFilter( + field_name='device__rack', + queryset=Rack.objects.all(), + label=_('Rack (ID)'), + ) + rack = django_filters.ModelMultipleChoiceFilter( + field_name='device__rack__name', + queryset=Rack.objects.all(), + to_field_name='name', + label=_('Rack (name)'), + ) device_id = django_filters.ModelMultipleChoiceFilter( queryset=Device.objects.all(), label=_('Device (ID)'), ) + device = django_filters.ModelMultipleChoiceFilter( + field_name='device__name', + queryset=Device.objects.all(), + to_field_name='name', + label=_('Device (name)'), + ) status = django_filters.MultipleChoiceFilter( choices=ModuleStatusChoices, null_value=None diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 41d426e8627..96aec6f448a 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -940,8 +940,51 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo model = Module fieldsets = ( FieldSet('q', 'filter_id', 'tag'), + FieldSet('site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')), FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')), ) + device_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + query_params={ + 'site_id': '$site_id', + 'location_id': '$location_id', + 'rack_id': '$rack_id', + }, + label=_('Device') + ) + region_id = DynamicModelMultipleChoiceField( + queryset=Region.objects.all(), + required=False, + label=_('Region') + ) + site_id = DynamicModelMultipleChoiceField( + queryset=Site.objects.all(), + required=False, + query_params={ + 'region_id': '$region_id', + 'group_id': '$site_group_id', + }, + label=_('Site') + ) + location_id = DynamicModelMultipleChoiceField( + queryset=Location.objects.all(), + required=False, + query_params={ + 'site_id': '$site_id', + }, + label=_('Location') + ) + rack_id = DynamicModelMultipleChoiceField( + queryset=Rack.objects.all(), + required=False, + label=_('Rack'), + null_option='None', + query_params={ + 'site_id': '$site_id', + 'location_id': '$location_id', + } + ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, From bdf0c007adbfe2dfe9f5a48dc8db032f1524b7f9 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 24 Apr 2025 09:58:38 -0700 Subject: [PATCH 2/5] 18334 add location, device, site to module filters --- netbox/dcim/forms/filtersets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 96aec6f448a..d491bdaf88e 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -940,7 +940,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo model = Module fieldsets = ( FieldSet('q', 'filter_id', 'tag'), - FieldSet('site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')), + FieldSet('region_id', 'site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')), FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')), ) device_id = DynamicModelMultipleChoiceField( From 3b64dc4478c0836179c9d7f526eaf460a5891677 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 24 Apr 2025 12:51:53 -0700 Subject: [PATCH 3/5] 18334 add tests --- netbox/dcim/tests/test_filtersets.py | 86 +++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 0e124e60128..2de7d449b52 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -2729,6 +2729,36 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests): @classmethod def setUpTestData(cls): + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = Site.objects.bulk_create(( + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[1]), + Site(name='Site 3', slug='site-3', region=regions[2]), + Site(name='Site X', slug='site-x'), + )) + + locations = ( + Location(name='Location 1', slug='location-1', site=sites[0]), + Location(name='Location 2', slug='location-2', site=sites[1]), + Location(name='Location 3', slug='location-3', site=sites[2]), + ) + for location in locations: + location.save() + + racks = ( + Rack(name='Rack 1', site=sites[0]), + Rack(name='Rack 2', site=sites[1]), + Rack(name='Rack 3', site=sites[2]), + ) + Rack.objects.bulk_create(racks) + manufacturers = ( Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), Manufacturer(name='Manufacturer 2', slug='manufacturer-2'), @@ -2737,10 +2767,32 @@ def setUpTestData(cls): Manufacturer.objects.bulk_create(manufacturers) devices = ( - create_test_device('Test Device 1'), - create_test_device('Test Device 2'), - create_test_device('Test Device 3'), + Device( + name='Test Device 1', + device_type=device_types[0], + site=sites[0], + location=locations[0], + rack=racks[0], + status='active', + ), + Device( + name='Test Device 2', + device_type=device_types[1], + site=sites[1], + location=locations[1], + rack=racks[1], + status='planned', + ), + Device( + name='Test Device 3', + device_type=device_types[2], + site=sites[2], + location=locations[2], + rack=racks[2], + status='offline', + ), ) + Device.objects.bulk_create(devices) module_types = ( ModuleType(manufacturer=manufacturers[0], model='Module Type 1'), @@ -2888,6 +2940,34 @@ def test_asset_tag(self): params = {'asset_tag': ['A', 'B']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_location(self): + locations = Location.objects.all()[:2] + params = {'location_id': [locations[0].pk, locations[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'location': [locations[0].slug, locations[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_rack(self): + racks = Rack.objects.all()[:2] + params = {'rack_id': [racks[0].pk, racks[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'rack': [racks[0].name, racks[1].name]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = ConsolePort.objects.all() From 96e86d2565125d95a7488ca311a977dc63e78b98 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 24 Apr 2025 13:15:35 -0700 Subject: [PATCH 4/5] 18334 fix tests --- netbox/dcim/tests/test_filtersets.py | 47 +++++++++++++++++++--------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 2de7d449b52..36377873400 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -2744,6 +2744,27 @@ def setUpTestData(cls): Site(name='Site X', slug='site-x'), )) + manufacturers = ( + Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), + Manufacturer(name='Manufacturer 2', slug='manufacturer-2'), + Manufacturer(name='Manufacturer 3', slug='manufacturer-3'), + ) + Manufacturer.objects.bulk_create(manufacturers) + + device_types = ( + DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturers[1], model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturers[2], model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + + roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(roles) + locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 2', slug='location-2', site=sites[1]), @@ -2759,17 +2780,11 @@ def setUpTestData(cls): ) Rack.objects.bulk_create(racks) - manufacturers = ( - Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), - Manufacturer(name='Manufacturer 2', slug='manufacturer-2'), - Manufacturer(name='Manufacturer 3', slug='manufacturer-3'), - ) - Manufacturer.objects.bulk_create(manufacturers) - devices = ( Device( name='Test Device 1', device_type=device_types[0], + role=roles[0], site=sites[0], location=locations[0], rack=racks[0], @@ -2778,6 +2793,7 @@ def setUpTestData(cls): Device( name='Test Device 2', device_type=device_types[1], + role=roles[1], site=sites[1], location=locations[1], rack=racks[1], @@ -2786,6 +2802,7 @@ def setUpTestData(cls): Device( name='Test Device 3', device_type=device_types[2], + role=roles[2], site=sites[2], location=locations[2], rack=racks[2], @@ -2943,30 +2960,30 @@ def test_asset_tag(self): def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) def test_location(self): locations = Location.objects.all()[:2] params = {'location_id': [locations[0].pk, locations[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) params = {'location': [locations[0].slug, locations[1].slug]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) def test_rack(self): racks = Rack.objects.all()[:2] params = {'rack_id': [racks[0].pk, racks[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) params = {'rack': [racks[0].name, racks[1].name]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): From c45addd3cf5f2c1d1a37b7c4fad9d67ff56e9198 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 25 Apr 2025 07:24:08 -0700 Subject: [PATCH 5/5] 18334 add site-group --- netbox/dcim/filtersets.py | 13 +++++++++++++ netbox/dcim/forms/filtersets.py | 7 ++++++- netbox/dcim/tests/test_filtersets.py | 21 ++++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 8d27baab9c3..e01c3f6581e 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1350,6 +1350,19 @@ class ModuleFilterSet(NetBoxModelFilterSet): to_field_name='slug', label=_('Region (slug)'), ) + site_group_id = TreeNodeMultipleChoiceFilter( + queryset=SiteGroup.objects.all(), + field_name='device__site__group', + lookup_expr='in', + label=_('Site group (ID)'), + ) + site_group = TreeNodeMultipleChoiceFilter( + queryset=SiteGroup.objects.all(), + field_name='device__site__group', + lookup_expr='in', + to_field_name='slug', + label=_('Site group (slug)'), + ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='device__site', queryset=Site.objects.all(), diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index d491bdaf88e..5d5d25f9615 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -940,7 +940,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo model = Module fieldsets = ( FieldSet('q', 'filter_id', 'tag'), - FieldSet('region_id', 'site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')), + FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')), FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')), ) device_id = DynamicModelMultipleChoiceField( @@ -958,6 +958,11 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo required=False, label=_('Region') ) + site_group_id = DynamicModelMultipleChoiceField( + queryset=SiteGroup.objects.all(), + required=False, + label=_('Site group') + ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), required=False, diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 36377873400..3fa44927d40 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -2737,10 +2737,18 @@ def setUpTestData(cls): for region in regions: region.save() + groups = ( + SiteGroup(name='Site Group 1', slug='site-group-1'), + SiteGroup(name='Site Group 2', slug='site-group-2'), + SiteGroup(name='Site Group 3', slug='site-group-3'), + ) + for group in groups: + group.save() + sites = Site.objects.bulk_create(( - Site(name='Site 1', slug='site-1', region=regions[0]), - Site(name='Site 2', slug='site-2', region=regions[1]), - Site(name='Site 3', slug='site-3', region=regions[2]), + Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]), + Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]), + Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) @@ -2964,6 +2972,13 @@ def test_region(self): params = {'region': [regions[0].slug, regions[1].slug]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) + def test_site_group(self): + site_groups = SiteGroup.objects.all()[:2] + params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) + params = {'site_group': [site_groups[0].slug, site_groups[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) + def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]}