mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-08 16:48:16 -06:00
Serialize VID ranges as integer lists in REST API
This commit is contained in:
parent
45183e4066
commit
46a40c5745
@ -6,11 +6,10 @@ from dcim.api.serializers_.sites import SiteSerializer
|
|||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.constants import VLANGROUP_SCOPE_TYPES
|
from ipam.constants import VLANGROUP_SCOPE_TYPES
|
||||||
from ipam.models import VLAN, VLANGroup
|
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 netbox.api.serializers import NetBoxModelSerializer
|
||||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||||
from utilities.api import get_serializer_for_model
|
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 vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
|
||||||
from .roles import RoleSerializer
|
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):
|
class VLANGroupSerializer(NetBoxModelSerializer):
|
||||||
scope_type = ContentTypeField(
|
scope_type = ContentTypeField(
|
||||||
queryset=ContentType.objects.filter(
|
queryset=ContentType.objects.filter(
|
||||||
@ -41,11 +32,11 @@ class VLANGroupSerializer(NetBoxModelSerializer):
|
|||||||
)
|
)
|
||||||
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None)
|
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None)
|
||||||
scope = serializers.SerializerMethodField(read_only=True)
|
scope = serializers.SerializerMethodField(read_only=True)
|
||||||
|
vlan_id_ranges = IntegerRangeSerializer(many=True, required=False)
|
||||||
utilization = serializers.CharField(read_only=True)
|
utilization = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
# Related object counts
|
# Related object counts
|
||||||
vlan_count = RelatedObjectCountField('vlans')
|
vlan_count = RelatedObjectCountField('vlans')
|
||||||
vlan_id_ranges = NumericRangeArraySerializer(required=False)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
|
@ -96,19 +96,24 @@ class VLANGroup(OrganizationalModel):
|
|||||||
raise ValidationError(_("Cannot set scope_id without scope_type."))
|
raise ValidationError(_("Cannot set scope_id without scope_type."))
|
||||||
|
|
||||||
# Validate vlan ranges
|
# 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.")})
|
raise ValidationError({'vlan_id_ranges': _("Ranges cannot overlap.")})
|
||||||
|
|
||||||
for ranges in self.vlan_id_ranges:
|
for ranges in self.vlan_id_ranges:
|
||||||
if ranges.lower >= ranges.upper:
|
if ranges.lower >= ranges.upper:
|
||||||
raise ValidationError({
|
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):
|
def save(self, *args, **kwargs):
|
||||||
self._total_vlan_ids = 0
|
if self.vlan_id_ranges:
|
||||||
for vlan_range in self.vlan_id_ranges:
|
self._total_vlan_ids = 0
|
||||||
self._total_vlan_ids += vlan_range.upper - vlan_range.lower + 1
|
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)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.backends.postgresql.psycopg_any import NumericRange
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
@ -11,6 +12,7 @@ __all__ = (
|
|||||||
'ChoiceField',
|
'ChoiceField',
|
||||||
'ContentTypeField',
|
'ContentTypeField',
|
||||||
'IPNetworkSerializer',
|
'IPNetworkSerializer',
|
||||||
|
'IntegerRangeSerializer',
|
||||||
'RelatedObjectCountField',
|
'RelatedObjectCountField',
|
||||||
'SerializedPKRelatedField',
|
'SerializedPKRelatedField',
|
||||||
)
|
)
|
||||||
@ -154,3 +156,19 @@ class RelatedObjectCountField(serializers.ReadOnlyField):
|
|||||||
self.relation = relation
|
self.relation = relation
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
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
|
||||||
|
@ -140,6 +140,8 @@ def ranges_to_string(ranges):
|
|||||||
For example:
|
For example:
|
||||||
[Range(1, 100), Range(200, 300)] => "1-100, 200-300"
|
[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])
|
return ', '.join([f"{r.lower}-{r.upper}" for r in ranges])
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user