diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 52c3fbfb4..c7e62fbd6 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1596,6 +1596,7 @@ class FrontPortFilterSet( model = FrontPort fields = ['id', 'name', 'label', 'type', 'color', 'description', 'cable_end'] + def filter_virtual_chassis_member(self, queryset, name, value): try: vc_front_port_ids = [] @@ -1611,6 +1612,17 @@ class RearPortFilterSet( NetBoxModelFilterSet, CabledObjectFilterSet ): + virtual_chassis_member = MultiValueCharFilter( + method='filter_virtual_chassis_member', + field_name='name', + label=_('Virtual Chassis Rear Ports for Device') + ) + virtual_chassis_member_id = MultiValueNumberFilter( + method='filter_virtual_chassis_member', + field_name='pk', + label=_('Virtual Chassis Rear Ports for Device (ID)') + ) + type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -1621,6 +1633,16 @@ class RearPortFilterSet( fields = ['id', 'name', 'label', 'type', 'color', 'positions', 'description', 'cable_end'] + def filter_virtual_chassis_member(self, queryset, name, value): + try: + vc_rear_port_ids = [] + for device in Device.objects.filter(**{f'{name}__in': value}): + vc_rear_port_ids.extend(device.vc_rear_ports(if_master=False).values_list('id', flat=True)) + return queryset.filter(pk__in=vc_front_rear_ids) + except Device.DoesNotExist: + return queryset.none() + + class ModuleBayFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet): class Meta: diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 47e855505..70fe20500 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -1118,6 +1118,22 @@ class Device( if self.virtual_chassis and (self.virtual_chassis.master == self or not if_master): filter |= Q(device__virtual_chassis=self.virtual_chassis) return FrontPort.objects.filter(filter) + + @property + def rear_ports_count(self): + return self.vc_rear_ports().count() + + def vc_rear_ports(self, if_master=True): + """ + Return a QuerySet matching all RearPorts assigned to this Device or, if this Device is a VC master, to another + Device belonging to the same VirtualChassis. + + :param if_master: If True, return VC member rear ports only if this Device is the VC master. + """ + filter = Q(device=self) + if self.virtual_chassis and (self.virtual_chassis.master == self or not if_master): + filter |= Q(device__virtual_chassis=self.virtual_chassis) + return RearPort.objects.filter(filter) def get_cables(self, pk_list=False): """