diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index 7d354fabc..471d6b002 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -911,6 +911,9 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet): cluster = django_filters.NumberFilter( method='filter_scope' ) + vlan_id = django_filters.NumberFilter( + method='filter_vlan_id' + ) class Meta: model = VLANGroup @@ -932,6 +935,11 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet): scope_id=value ) + def filter_vlan_id(self, queryset, name, value): + return queryset.filter( + vid_range__contained_by=value + ) + class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet): region_id = TreeNodeMultipleChoiceFilter( diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index 6016d8506..04bc34f79 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -413,6 +413,7 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm): FieldSet('q', 'filter_id', 'tag'), FieldSet('region', 'sitegroup', 'site', 'location', 'rack', name=_('Location')), FieldSet('cluster_group', 'cluster', name=_('Cluster')), + FieldSet('vlan_id', name=_('VLAN ID')), ) model = VLANGroup region = DynamicModelMultipleChoiceField( @@ -450,6 +451,11 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm): required=False, label=_('Cluster group') ) + vlan_id = forms.IntegerField( + min_value=0, + required=False, + label=_('Contains VLAN ID') + ) tag = TagFilterField(model) diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index 076d05445..e490c238b 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -1,5 +1,5 @@ from django.contrib.contenttypes.models import ContentType -from django.db.models import Count, F, OuterRef, Q, Subquery, Value +from django.db.models import Count, Func, F, OuterRef, Q, Subquery, Value from django.db.models.expressions import RawSQL from django.db.models.functions import Round @@ -66,6 +66,12 @@ class VLANGroupQuerySet(RestrictedQuerySet): utilization=Round(F('vlan_count') * 100 / F('_total_vlan_ids'), 2) ) + def annotate_vlan_ranges(self): + + return self.annotate( + vid_range=Func(F('vlan_id_ranges'), function='unnest') + ) + class VLANQuerySet(RestrictedQuerySet): diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index e0087f5d1..543534982 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -915,7 +915,7 @@ class IPAddressContactsView(ObjectContactsView): # class VLANGroupListView(generic.ObjectListView): - queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') + queryset = VLANGroup.objects.annotate_utilization().annotate_vlan_ranges().prefetch_related('tags') filterset = filtersets.VLANGroupFilterSet filterset_form = forms.VLANGroupFilterForm table = tables.VLANGroupTable