Serialize VID ranges as integer lists in REST API

This commit is contained in:
Jeremy Stretch 2024-07-13 15:00:16 -04:00
parent 45183e4066
commit 46a40c5745
4 changed files with 32 additions and 16 deletions

View File

@ -6,11 +6,10 @@ from dcim.api.serializers_.sites import SiteSerializer
from ipam.choices import *
from ipam.constants import VLANGROUP_SCOPE_TYPES
from ipam.models import VLAN, VLANGroup
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
from netbox.api.fields import ChoiceField, ContentTypeField, IntegerRangeSerializer, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from utilities.api import get_serializer_for_model
from utilities.data import ranges_to_string, string_to_range_array
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
from .roles import RoleSerializer
@ -22,14 +21,6 @@ __all__ = (
)
class NumericRangeArraySerializer(serializers.CharField):
def to_internal_value(self, data):
return string_to_range_array(data)
def to_representation(self, instance):
return ranges_to_string(instance)
class VLANGroupSerializer(NetBoxModelSerializer):
scope_type = ContentTypeField(
queryset=ContentType.objects.filter(
@ -41,11 +32,11 @@ class VLANGroupSerializer(NetBoxModelSerializer):
)
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None)
scope = serializers.SerializerMethodField(read_only=True)
vlan_id_ranges = IntegerRangeSerializer(many=True, required=False)
utilization = serializers.CharField(read_only=True)
# Related object counts
vlan_count = RelatedObjectCountField('vlans')
vlan_id_ranges = NumericRangeArraySerializer(required=False)
class Meta:
model = VLANGroup

View File

@ -96,19 +96,24 @@ class VLANGroup(OrganizationalModel):
raise ValidationError(_("Cannot set scope_id without scope_type."))
# Validate vlan ranges
if check_ranges_overlap(self.vlan_id_ranges):
if self.vlan_id_ranges and check_ranges_overlap(self.vlan_id_ranges):
raise ValidationError({'vlan_id_ranges': _("Ranges cannot overlap.")})
for ranges in self.vlan_id_ranges:
if ranges.lower >= ranges.upper:
raise ValidationError({
'vlan_id_ranges': _("Maximum child VID must be greater than or equal to minimum child VID Invalid range ({value})").format(value=ranges)
'vlan_id_ranges': _(
"Maximum child VID must be greater than or equal to minimum child VID Invalid range ({value})"
).format(value=ranges)
})
def save(self, *args, **kwargs):
self._total_vlan_ids = 0
for vlan_range in self.vlan_id_ranges:
self._total_vlan_ids += vlan_range.upper - vlan_range.lower + 1
if self.vlan_id_ranges:
self._total_vlan_ids = 0
for vlan_range in self.vlan_id_ranges:
self._total_vlan_ids += vlan_range.upper - vlan_range.lower + 1
else:
self._total_vlan_ids = VLAN_VID_MAX - VLAN_VID_MIN + 1
super().save(*args, **kwargs)

View File

@ -1,4 +1,5 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.backends.postgresql.psycopg_any import NumericRange
from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
@ -11,6 +12,7 @@ __all__ = (
'ChoiceField',
'ContentTypeField',
'IPNetworkSerializer',
'IntegerRangeSerializer',
'RelatedObjectCountField',
'SerializedPKRelatedField',
)
@ -154,3 +156,19 @@ class RelatedObjectCountField(serializers.ReadOnlyField):
self.relation = relation
super().__init__(**kwargs)
class IntegerRangeSerializer(serializers.Serializer):
"""
Represents a range of integers.
"""
def to_internal_value(self, data):
if not isinstance(data, (list, tuple)) or len(data) != 2:
raise ValidationError(_("Ranges must be specified in the form (lower, upper)."))
if type(data[0]) is not int or type(data[1]) is not int:
raise ValidationError(_("Range boundaries must be defined as integers."))
return NumericRange(data[0], data[1])
def to_representation(self, instance):
return instance.lower, instance.upper

View File

@ -140,6 +140,8 @@ def ranges_to_string(ranges):
For example:
[Range(1, 100), Range(200, 300)] => "1-100, 200-300"
"""
if not ranges:
return ''
return ', '.join([f"{r.lower}-{r.upper}" for r in ranges])