Fixes #19645: Correct Interface selection for Cable add when VC master is the selected device (#20041)

* Fixes: #19645 - Correct Interface selection for Cable add when VC master is the selected device

* Clarify label

* Add test
This commit is contained in:
Daniel Sheppard 2025-08-15 13:54:18 -05:00 committed by GitHub
parent 44f173f01d
commit 9f605a2db1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 3 deletions

View File

@ -1885,6 +1885,16 @@ class InterfaceFilterSet(
PathEndpointFilterSet, PathEndpointFilterSet,
CommonInterfaceFilterSet CommonInterfaceFilterSet
): ):
virtual_chassis_member_or_master = MultiValueCharFilter(
method='filter_virtual_chassis_member_or_master',
field_name='name',
label=_('Virtual Chassis Interfaces for Device when device is master')
)
virtual_chassis_member_or_master_id = MultiValueNumberFilter(
method='filter_virtual_chassis_member_or_master',
field_name='pk',
label=_('Virtual Chassis Interfaces for Device when device is master (ID)')
)
virtual_chassis_member = MultiValueCharFilter( virtual_chassis_member = MultiValueCharFilter(
method='filter_virtual_chassis_member', method='filter_virtual_chassis_member',
field_name='name', field_name='name',
@ -1995,11 +2005,14 @@ class InterfaceFilterSet(
'cable_id', 'cable_end', 'cable_id', 'cable_end',
) )
def filter_virtual_chassis_member(self, queryset, name, value): def filter_virtual_chassis_member_or_master(self, queryset, name, value):
return self.filter_virtual_chassis_member(queryset, name, value, if_master=True)
def filter_virtual_chassis_member(self, queryset, name, value, if_master=False):
try: try:
vc_interface_ids = [] vc_interface_ids = []
for device in Device.objects.filter(**{f'{name}__in': value}): for device in Device.objects.filter(**{f'{name}__in': value}):
vc_interface_ids.extend(device.vc_interfaces(if_master=False).values_list('id', flat=True)) vc_interface_ids.extend(device.vc_interfaces(if_master=if_master).values_list('id', flat=True))
return queryset.filter(pk__in=vc_interface_ids) return queryset.filter(pk__in=vc_interface_ids)
except Device.DoesNotExist: except Device.DoesNotExist:
return queryset.none() return queryset.none()

View File

@ -19,6 +19,11 @@ def get_cable_form(a_type, b_type):
# Device component # Device component
if hasattr(term_cls, 'device'): if hasattr(term_cls, 'device'):
# Dynamically change the param field for interfaces to use virtual_chassis filter
query_param_device_field = 'device_id'
if term_cls == Interface:
query_param_device_field = 'virtual_chassis_member_or_master_id'
attrs[f'termination_{cable_end}_device'] = DynamicModelMultipleChoiceField( attrs[f'termination_{cable_end}_device'] = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
label=_('Device'), label=_('Device'),
@ -36,7 +41,7 @@ def get_cable_form(a_type, b_type):
'parent': 'device', 'parent': 'device',
}, },
query_params={ query_params={
'device_id': f'$termination_{cable_end}_device', query_param_device_field: f'$termination_{cable_end}_device',
'kind': 'physical', # Exclude virtual interfaces 'kind': 'physical', # Exclude virtual interfaces
} }
) )

View File

@ -4373,6 +4373,9 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
virtual_chassis.master = devices[0]
virtual_chassis.save()
module_bays = ( module_bays = (
ModuleBay(device=devices[0], name='Module Bay 1'), ModuleBay(device=devices[0], name='Module Bay 1'),
ModuleBay(device=devices[1], name='Module Bay 2'), ModuleBay(device=devices[1], name='Module Bay 2'),
@ -4759,6 +4762,19 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
params = {'device': [devices[0].name, devices[1].name]} params = {'device': [devices[0].name, devices[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_virtual_chassis_member_or_master(self):
vc = VirtualChassis.objects.first()
master = vc.master
member = vc.members.exclude(pk=master.pk).first()
params = {'virtual_chassis_member_or_master_id': [master.pk,]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'virtual_chassis_member_or_master_id': [member.pk,]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
params = {'virtual_chassis_member_or_master': [master.name,]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'virtual_chassis_member_or_master': [member.name,]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_virtual_chassis_member(self): def test_virtual_chassis_member(self):
# Device 1A & 3 have 1 management interface, Device 1B has 1 interfaces # Device 1A & 3 have 1 management interface, Device 1B has 1 interfaces
devices = Device.objects.filter(name__in=['Device 1A', 'Device 3']) devices = Device.objects.filter(name__in=['Device 1A', 'Device 3'])