mirror of
https://github.com/netbox-community/netbox.git
synced 2026-03-21 20:18:38 -06:00
feat(ipam): Expose total_vlan_ids on VLAN groups
Rename the internal `_total_vlan_ids` field to `total_vlan_ids` on VLANGroup and expose it as a read-only integer field. This change includes a migration to rename the database column, adds `total_vlan_ids` to VLANGroup API representations as a read-only attribute, updates the UI table to include a "Total VLAN IDs" column, and adjusts related tests accordingly. Fixes #20698
This commit is contained in:
@@ -36,6 +36,7 @@ class VLANGroupSerializer(OrganizationalModelSerializer):
|
||||
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None)
|
||||
scope = GFKSerializerField(read_only=True)
|
||||
vid_ranges = IntegerRangeSerializer(many=True, required=False)
|
||||
total_vlan_ids = serializers.IntegerField(read_only=True)
|
||||
utilization = serializers.CharField(read_only=True)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||
|
||||
@@ -46,7 +47,8 @@ class VLANGroupSerializer(OrganizationalModelSerializer):
|
||||
model = VLANGroup
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'vid_ranges',
|
||||
'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'total_vlan_ids', 'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated',
|
||||
'vlan_count', 'utilization',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'vlan_count')
|
||||
|
||||
@@ -962,7 +962,7 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = VLANGroup
|
||||
fields = ('id', 'name', 'slug', 'description', 'scope_id')
|
||||
fields = ('id', 'name', 'slug', 'description', 'scope_id', 'total_vlan_ids')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
|
||||
@@ -7,7 +7,7 @@ import strawberry_django
|
||||
from django.db.models import Q
|
||||
from netaddr.core import AddrFormatError
|
||||
from strawberry.scalars import ID
|
||||
from strawberry_django import BaseFilterLookup, DateFilterLookup, FilterLookup, StrFilterLookup
|
||||
from strawberry_django import BaseFilterLookup, ComparisonFilterLookup, DateFilterLookup, FilterLookup, StrFilterLookup
|
||||
|
||||
from dcim.graphql.filter_mixins import ScopedFilterMixin
|
||||
from dcim.models import Device
|
||||
@@ -397,6 +397,7 @@ class VLANGroupFilter(ScopedFilterMixin, OrganizationalModelFilter):
|
||||
vid_ranges: Annotated['IntegerRangeArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
total_vlan_ids: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.VLANTranslationPolicy, lookups=True)
|
||||
|
||||
@@ -305,6 +305,7 @@ class VLANGroupType(OrganizationalObjectType):
|
||||
|
||||
vlans: list[VLANType]
|
||||
vid_ranges: list[str]
|
||||
total_vlan_ids: BigInt
|
||||
tenant: Annotated['TenantType', strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
@strawberry_django.field
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('ipam', '0086_gfk_indexes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='vlangroup',
|
||||
old_name='_total_vlan_ids',
|
||||
new_name='total_vlan_ids',
|
||||
),
|
||||
]
|
||||
@@ -62,6 +62,9 @@ class VLANGroup(OrganizationalModel):
|
||||
verbose_name=_('VLAN ID ranges'),
|
||||
default=default_vid_ranges
|
||||
)
|
||||
total_vlan_ids = models.PositiveBigIntegerField(
|
||||
default=VLAN_VID_MAX - VLAN_VID_MIN + 1,
|
||||
)
|
||||
tenant = models.ForeignKey(
|
||||
to='tenancy.Tenant',
|
||||
on_delete=models.PROTECT,
|
||||
@@ -69,9 +72,6 @@ class VLANGroup(OrganizationalModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
_total_vlan_ids = models.PositiveBigIntegerField(
|
||||
default=VLAN_VID_MAX - VLAN_VID_MIN + 1
|
||||
)
|
||||
|
||||
objects = VLANGroupQuerySet.as_manager()
|
||||
|
||||
@@ -130,10 +130,10 @@ class VLANGroup(OrganizationalModel):
|
||||
raise ValidationError({'vid_ranges': _("Ranges cannot overlap.")})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self._total_vlan_ids = 0
|
||||
self.total_vlan_ids = 0
|
||||
for vid_range in self.vid_ranges:
|
||||
# VID range is inclusive on lower-bound, exclusive on upper-bound
|
||||
self._total_vlan_ids += vid_range.upper - vid_range.lower
|
||||
self.total_vlan_ids += vid_range.upper - vid_range.lower
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class VLANGroupQuerySet(RestrictedQuerySet):
|
||||
|
||||
return self.annotate(
|
||||
vlan_count=count_related(VLAN, 'group'),
|
||||
utilization=Round(F('vlan_count') * 100.0 / F('_total_vlan_ids'), 2)
|
||||
utilization=Round(F('vlan_count') * 100.0 / F('total_vlan_ids'), 2),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@ class VLANGroupTable(TenancyColumnsMixin, OrganizationalModelTable):
|
||||
url_params={'group_id': 'pk'},
|
||||
verbose_name=_('VLANs')
|
||||
)
|
||||
total_vlan_ids = tables.Column(
|
||||
verbose_name=_('Total VLAN IDs'),
|
||||
)
|
||||
utilization = columns.UtilizationColumn(
|
||||
orderable=False,
|
||||
verbose_name=_('Utilization')
|
||||
@@ -67,8 +70,9 @@ class VLANGroupTable(TenancyColumnsMixin, OrganizationalModelTable):
|
||||
class Meta(OrganizationalModelTable.Meta):
|
||||
model = VLANGroup
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'scope_type', 'scope', 'vid_ranges_list', 'vlan_count', 'slug', 'description',
|
||||
'tenant', 'tenant_group', 'comments', 'tags', 'created', 'last_updated', 'actions', 'utilization',
|
||||
'pk', 'id', 'name', 'slug', 'description', 'scope_type', 'scope', 'vid_ranges_list', 'vlan_count',
|
||||
'total_vlan_ids', 'tenant', 'tenant_group', 'comments', 'tags', 'created', 'last_updated', 'actions',
|
||||
'utilization',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'scope_type', 'scope', 'vlan_count', 'utilization', 'tenant', 'description'
|
||||
|
||||
@@ -663,7 +663,7 @@ class TestVLANGroup(TestCase):
|
||||
|
||||
def test_total_vlan_ids(self):
|
||||
vlangroup = VLANGroup.objects.first()
|
||||
self.assertEqual(vlangroup._total_vlan_ids, 100)
|
||||
self.assertEqual(vlangroup.total_vlan_ids, 100)
|
||||
|
||||
|
||||
class TestVLAN(TestCase):
|
||||
|
||||
Reference in New Issue
Block a user