diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index aac1e3200..44043a865 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -6,6 +6,7 @@ * [#6850](https://github.com/netbox-community/netbox/issues/6850) - Default to current user when creating journal entries via REST API * [#6955](https://github.com/netbox-community/netbox/issues/6955) - Include type, ID, and slug on object view +* [#7394](https://github.com/netbox-community/netbox/issues/7394) - Enable filtering cables by termination type & ID in REST API * [#7462](https://github.com/netbox-community/netbox/issues/7462) - Include count of assigned virtual machines under platform view ### Bug Fixes diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 71d43b279..6f2c23c90 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -10,7 +10,8 @@ from tenancy.filtersets import TenancyFilterSet from tenancy.models import Tenant from utilities.choices import ColorChoices from utilities.filters import ( - MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter, + ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, + TreeNodeMultipleChoiceFilter, ) from virtualization.models import Cluster from .choices import * @@ -1183,6 +1184,10 @@ class CableFilterSet(PrimaryModelFilterSet): method='search', label='Search', ) + termination_a_type = ContentTypeFilter() + termination_a_id = MultiValueNumberFilter() + termination_b_type = ContentTypeFilter() + termination_b_id = MultiValueNumberFilter() type = django_filters.MultipleChoiceFilter( choices=CableTypeChoices ) @@ -1227,7 +1232,7 @@ class CableFilterSet(PrimaryModelFilterSet): class Meta: model = Cable - fields = ['id', 'label', 'length', 'length_unit'] + fields = ['id', 'label', 'length', 'length_unit', 'termination_a_id', 'termination_b_id'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 2afe77638..fb94bde08 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -2851,6 +2851,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): ) Interface.objects.bulk_create(interfaces) + console_port = ConsolePort.objects.create(device=devices[0], name='Console Port 1') + console_server_port = ConsoleServerPort.objects.create(device=devices[0], name='Console Server Port 1') + # Cables Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save() Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save() @@ -2858,6 +2861,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save() Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save() Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save() + Cable(termination_a=console_port, termination_b=console_server_port, label='Cable 7').save() def test_label(self): params = {'label': ['Cable 1', 'Cable 2']} @@ -2877,7 +2881,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): def test_status(self): params = {'status': [CableStatusChoices.STATUS_CONNECTED]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'status': [CableStatusChoices.STATUS_PLANNED]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) @@ -2888,30 +2892,44 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) 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(), 5) + 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(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) def test_site(self): site = Site.objects.all()[:2] params = {'site_id': [site[0].pk, site[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) params = {'site': [site[0].slug, site[1].slug]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) def test_tenant(self): tenant = Tenant.objects.all()[:2] params = {'tenant_id': [tenant[0].pk, tenant[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) params = {'tenant': [tenant[0].slug, tenant[1].slug]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) + + def test_termination_types(self): + params = {'termination_a_type': 'dcim.consoleport'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'termination_b_type': 'dcim.consoleserverport'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_termination_ids(self): + interface_ids = Cable.objects.values_list('termination_a_id', flat=True)[:3] + params = { + 'termination_a_type': 'dcim.interface', + 'termination_a_id': list(interface_ids), + } + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests):