diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index ad1e29f2654..2fb1e9949c9 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1100,6 +1100,10 @@ class DeviceFilterSet( queryset=IPAddress.objects.all(), label=_('OOB IP (ID)'), ) + has_virtual_device_context = django_filters.BooleanFilter( + method='_has_virtual_device_context', + label=_('Has virtual device context'), + ) class Meta: model = Device @@ -1176,6 +1180,12 @@ def _module_bays(self, queryset, name, value): def _device_bays(self, queryset, name, value): return queryset.exclude(devicebays__isnull=value) + def _has_virtual_device_context(self, queryset, name, value): + params = Q(vdcs__isnull=False) + if value: + return queryset.filter(params).distinct() + return queryset.exclude(params) + class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet, PrimaryIPFilterSet): device_id = django_filters.ModelMultipleChoiceFilter( diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 21854b53fef..0a28a4ec445 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -657,6 +657,7 @@ class DeviceFilterForm( ), FieldSet( 'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data', + 'has_virtual_device_context', name=_('Miscellaneous') ) ) @@ -813,6 +814,13 @@ class DeviceFilterForm( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) + has_virtual_device_context = forms.NullBooleanField( + required=False, + label=_('Has virtual device contexts'), + widget=forms.Select( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) tag = TagFilterField(model) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 96ea020b318..0a22f5a824b 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -2103,6 +2103,9 @@ def setUpTestData(cls): Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1) Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2) + # VirtualDeviceContext assignment for filtering + VirtualDeviceContext.objects.create(device=devices[0], name="VDC 1", identifier=1, status='active') + def test_q(self): params = {'q': 'foobar1'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) @@ -2336,6 +2339,12 @@ def test_tenant_group(self): params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_has_virtual_device_context(self): + params = {'has_virtual_device_context': 'true'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'has_virtual_device_context': 'false'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Module.objects.all()