diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 064452667a7..1501f16dca5 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -218,12 +218,13 @@ class VLANGroupSerializer(NetBoxModelSerializer): scope_id = serializers.IntegerField(allow_null=True, required=False, default=None) scope = serializers.SerializerMethodField(read_only=True) vlan_count = serializers.IntegerField(read_only=True) + utilization = serializers.CharField(read_only=True) class Meta: model = VLANGroup fields = [ 'id', 'url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'min_vid', 'max_vid', - 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', + 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization' ] validators = [] diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index f432e0e6b06..4dd1d3cfe55 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -1,5 +1,7 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import transaction +from django.db.models import F +from django.db.models.functions import Round from django.shortcuts import get_object_or_404 from django_pglocks import advisory_lock from drf_spectacular.utils import extend_schema @@ -145,9 +147,7 @@ class FHRPGroupAssignmentViewSet(NetBoxModelViewSet): class VLANGroupViewSet(NetBoxModelViewSet): - queryset = VLANGroup.objects.annotate( - vlan_count=count_related(VLAN, 'group') - ).prefetch_related('tags') + queryset = VLANGroup.objects.get_utilization().prefetch_related('tags') serializer_class = serializers.VLANGroupSerializer filterset_class = filtersets.VLANGroupFilterSet diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 7d4777da97f..da504ded2ac 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -9,7 +9,7 @@ from dcim.models import Interface from ipam.choices import * from ipam.constants import * -from ipam.querysets import VLANQuerySet +from ipam.querysets import VLANQuerySet, VLANGroupQuerySet from netbox.models import OrganizationalModel, PrimaryModel from virtualization.models import VMInterface @@ -63,6 +63,8 @@ class VLANGroup(OrganizationalModel): help_text=_('Highest permissible ID of a child VLAN') ) + objects = VLANGroupQuerySet.as_manager() + class Meta: ordering = ('name', 'pk') # Name may be non-unique constraints = ( diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index 9f4463f61e8..7cc6ea4b323 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -1,8 +1,10 @@ from django.contrib.contenttypes.models import ContentType -from django.db.models import Q +from django.db.models import F, Q from django.db.models.expressions import RawSQL +from django.db.models.functions import Round from utilities.querysets import RestrictedQuerySet +from utilities.utils import count_related class PrefixQuerySet(RestrictedQuerySet): @@ -30,6 +32,16 @@ def annotate_hierarchy(self): ) +class VLANGroupQuerySet(RestrictedQuerySet): + def get_utilization(self, *args, **kwargs): + from .models import VLAN + + return self.annotate( + vlan_count=count_related(VLAN, 'group'), + utilization=Round(F('vlan_count') / (F('max_vid') - F('min_vid') + 1.0) * 100, 2) + ) + + class VLANQuerySet(RestrictedQuerySet): def get_for_device(self, device): diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index 6fa2cd2da48..5d9828531d5 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -70,6 +70,10 @@ class VLANGroupTable(NetBoxTable): url_params={'group_id': 'pk'}, verbose_name='VLANs' ) + utilization = columns.UtilizationColumn( + orderable=False, + verbose_name='Utilization' + ) tags = columns.TagColumn( url_name='ipam:vlangroup_list' ) @@ -81,9 +85,9 @@ class Meta(NetBoxTable.Meta): model = VLANGroup fields = ( 'pk', 'id', 'name', 'scope_type', 'scope', 'min_vid', 'max_vid', 'vlan_count', 'slug', 'description', - 'tags', 'created', 'last_updated', 'actions', + 'tags', 'created', 'last_updated', 'actions', 'utilization', ) - default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description') + default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'utilization', 'description') # diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 6b73a061bc2..bc83f654f1a 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1,6 +1,7 @@ from django.contrib.contenttypes.models import ContentType -from django.db.models import Prefetch +from django.db.models import F, Prefetch from django.db.models.expressions import RawSQL +from django.db.models.functions import Round from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext as _ @@ -886,9 +887,7 @@ def get_children(self, request, parent): # class VLANGroupListView(generic.ObjectListView): - queryset = VLANGroup.objects.annotate( - vlan_count=count_related(VLAN, 'group') - ) + queryset = VLANGroup.objects.get_utilization().prefetch_related('tags') filterset = filtersets.VLANGroupFilterSet filterset_form = forms.VLANGroupFilterForm table = tables.VLANGroupTable @@ -896,7 +895,7 @@ class VLANGroupListView(generic.ObjectListView): @register_model_view(VLANGroup) class VLANGroupView(generic.ObjectView): - queryset = VLANGroup.objects.all() + queryset = VLANGroup.objects.get_utilization().prefetch_related('tags') def get_extra_context(self, request, instance): related_models = ( @@ -938,18 +937,14 @@ class VLANGroupBulkImportView(generic.BulkImportView): class VLANGroupBulkEditView(generic.BulkEditView): - queryset = VLANGroup.objects.annotate( - vlan_count=count_related(VLAN, 'group') - ) + queryset = VLANGroup.objects.get_utilization().prefetch_related('tags') filterset = filtersets.VLANGroupFilterSet table = tables.VLANGroupTable form = forms.VLANGroupBulkEditForm class VLANGroupBulkDeleteView(generic.BulkDeleteView): - queryset = VLANGroup.objects.annotate( - vlan_count=count_related(VLAN, 'group') - ) + queryset = VLANGroup.objects.get_utilization().prefetch_related('tags') filterset = filtersets.VLANGroupFilterSet table = tables.VLANGroupTable diff --git a/netbox/templates/ipam/vlangroup.html b/netbox/templates/ipam/vlangroup.html index 2917536be5d..e474cbd84ed 100644 --- a/netbox/templates/ipam/vlangroup.html +++ b/netbox/templates/ipam/vlangroup.html @@ -42,6 +42,10 @@