From 18a813aa39760636d80ad486a237bf2ec35713ad Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 17 Oct 2023 06:32:42 -0700 Subject: [PATCH] 13972 allow filtering of cables if have terminations (#13949) * 10769 allow filtering of cables if have terminations * 10769 change to termianted * 10769 add test case * 10769 review cleanup --- netbox/dcim/filtersets.py | 17 +++++++++++++++++ netbox/dcim/forms/filtersets.py | 9 ++++++++- netbox/dcim/tests/test_filtersets.py | 10 ++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 0261998db..d600667d7 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1745,6 +1745,10 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet): method='filter_by_cable_end_b', field_name='terminations__termination_id' ) + unterminated = django_filters.BooleanFilter( + method='_unterminated', + label=_('Unterminated'), + ) type = django_filters.MultipleChoiceFilter( choices=CableTypeChoices ) @@ -1812,6 +1816,19 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet): # Filter by termination id and cable_end type return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_B) + def _unterminated(self, queryset, name, value): + if value: + terminated_ids = ( + queryset.filter(terminations__cable_end=CableEndChoices.SIDE_A) + .filter(terminations__cable_end=CableEndChoices.SIDE_B) + .values("id") + ) + return queryset.exclude(id__in=terminated_ids) + else: + return queryset.filter(terminations__cable_end=CableEndChoices.SIDE_A).filter( + terminations__cable_end=CableEndChoices.SIDE_B + ) + class CableTerminationFilterSet(BaseFilterSet): termination_type = ContentTypeFilter() diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 7f99d1ca4..d0d321187 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -910,7 +910,7 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), (_('Location'), ('site_id', 'location_id', 'rack_id', 'device_id')), - (_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit')), + (_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit', 'unterminated')), (_('Tenant'), ('tenant_group_id', 'tenant_id')), ) region_id = DynamicModelMultipleChoiceField( @@ -979,6 +979,13 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): choices=add_blank_choice(CableLengthUnitChoices), required=False ) + unterminated = forms.NullBooleanField( + label=_('Unterminated'), + required=False, + 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 dd5ff7bc2..1f3b557b5 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -4275,6 +4275,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): Interface(device=devices[4], name='Interface 10', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[5], name='Interface 11', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[5], name='Interface 12', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[5], name='Interface 13', type=InterfaceTypeChoices.TYPE_1GE_FIXED), ) Interface.objects.bulk_create(interfaces) @@ -4290,6 +4291,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): Cable(a_terminations=[interfaces[11]], b_terminations=[interfaces[0]], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, tenant=tenants[2], status=LinkStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save() Cable(a_terminations=[console_port], b_terminations=[console_server_port], label='Cable 7').save() + # Cable for unterminated test + Cable(a_terminations=[interfaces[12]], label='Cable 8', type=CableTypeChoices.TYPE_CAT6, status=LinkStatusChoices.STATUS_DECOMMISSIONING).save() + def test_label(self): params = {'label': ['Cable 1', 'Cable 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -4368,6 +4372,12 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): } self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + def test_unterminated(self): + params = {'unterminated': True} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'unterminated': False} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 7) + class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = PowerPanel.objects.all()