diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 11d192732..0afbb26b9 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1482,6 +1482,7 @@ class CableTypeChoices(ChoiceSet): TYPE_AOC = 'aoc' TYPE_POWER = 'power' TYPE_USB = 'usb' + TYPE_EMPTY = 'EMPTY' CHOICES = ( ( @@ -1514,8 +1515,13 @@ class CableTypeChoices(ChoiceSet): (TYPE_AOC, 'Active Optical Cabling (AOC)'), ), ), - (TYPE_USB, _('USB')), - (TYPE_POWER, _('Power')), + ( + _('Other'), ( + (TYPE_USB, _('USB')), + (TYPE_POWER, _('Power')), + (TYPE_EMPTY, _('(unset)')), + ) + ) ) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index e08059b85..b8b05bb42 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -18,7 +18,7 @@ from tenancy.models import * from users.models import User from utilities.filters import ( ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter, - NumericArrayFilter, TreeNodeMultipleChoiceFilter, EmptyStringFilter, + NumericArrayFilter, TreeNodeMultipleChoiceFilter, EmptyStringMultipleChoiceFilter, ) from virtualization.models import Cluster, ClusterGroup from vpn.models import L2VPN @@ -1980,12 +1980,9 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet): method='_unterminated', label=_('Unterminated'), ) - type = django_filters.MultipleChoiceFilter( + type = EmptyStringMultipleChoiceFilter( choices=CableTypeChoices ) - type__empty = EmptyStringFilter( - field_name='type' - ) status = django_filters.MultipleChoiceFilter( choices=LinkStatusChoices ) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 56f54b249..26c8f30c3 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -5240,6 +5240,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): def test_type(self): params = {'type': [CableTypeChoices.TYPE_CAT3, CableTypeChoices.TYPE_CAT5E]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + params = {'type': [CableTypeChoices.TYPE_EMPTY]} params = {'type__empty': 'true'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8) params = {'type__empty': 'false'} @@ -5248,8 +5249,8 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): def test_type_empty(self): params = {'type__empty': 'true'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8) - params = {'type__empty': 'false'} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) + params = {'type': [CableTypeChoices.TYPE_EMPTY, CableTypeChoices.TYPE_CAT3]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 10) def test_status(self): params = {'status': [LinkStatusChoices.STATUS_CONNECTED]} diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 95c615dcb..3d8203b85 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -173,12 +173,10 @@ class ContentTypeFilter(django_filters.CharFilter): ) -class EmptyStringFilter(django_filters.BooleanFilter): +class EmptyStringMultipleChoiceFilter(django_filters.MultipleChoiceFilter): + empty_value = 'EMPTY' + def filter(self, qs, value): - if value in EMPTY_VALUES: - return qs - - exclude = self.exclude ^ (value is False) - method = qs.exclude if exclude else qs.filter - - return method(**{self.field_name: ""}) + if self.empty_value in value: + value.append('') + return super().filter(qs, value)