diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 68edc93f6..52c3fbfb4 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1576,6 +1576,17 @@ class FrontPortFilterSet( NetBoxModelFilterSet, CabledObjectFilterSet ): + virtual_chassis_member = MultiValueCharFilter( + method='filter_virtual_chassis_member', + field_name='name', + label=_('Virtual Chassis Front Ports for Device') + ) + virtual_chassis_member_id = MultiValueNumberFilter( + method='filter_virtual_chassis_member', + field_name='pk', + label=_('Virtual Chassis Front Ports for Device (ID)') + ) + type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -1585,6 +1596,15 @@ 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 = [] + for device in Device.objects.filter(**{f'{name}__in': value}): + vc_front_port_ids.extend(device.vc_front_ports(if_master=False).values_list('id', flat=True)) + return queryset.filter(pk__in=vc_front_port_ids) + except Device.DoesNotExist: + return queryset.none() + class RearPortFilterSet( ModularDeviceComponentFilterSet, diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 4b9689a22..47e855505 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -1103,6 +1103,22 @@ class Device( filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False) return Interface.objects.filter(filter) + @property + def front_ports_count(self): + return self.vc_front_ports().count() + + def vc_front_ports(self, if_master=True): + """ + Return a QuerySet matching all FrontPorts 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 front 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 FrontPort.objects.filter(filter) + def get_cables(self, pk_list=False): """ Return a QuerySet or PK list matching all Cables connected to a component of this Device. diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 497935b15..4071b686f 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1945,12 +1945,15 @@ class DeviceFrontPortsView(DeviceComponentsView): template_name = 'dcim/device/frontports.html' tab = ViewTab( label=_('Front Ports'), - badge=lambda obj: obj.front_port_count, + badge=lambda obj: obj.vc_front_ports().count(), permission='dcim.view_frontport', weight=530, hide_if_empty=True ) + def get_children(self, request, parent): + return parent.vc_front_ports().restrict(request.user, 'view') + @register_model_view(Device, 'rearports', path='rear-ports') class DeviceRearPortsView(DeviceComponentsView):