Fix logic for upper range boundaries

This commit is contained in:
Jeremy Stretch 2024-07-15 16:27:38 -04:00
parent f6c2396a30
commit c1a0eb8b37
6 changed files with 19 additions and 14 deletions

View File

@ -10,7 +10,9 @@ def move_min_max(apps, schema_editor):
VLANGroup = apps.get_model('ipam', 'VLANGroup') VLANGroup = apps.get_model('ipam', 'VLANGroup')
for group in VLANGroup.objects.all(): for group in VLANGroup.objects.all():
if group.min_vid or group.max_vid: if group.min_vid or group.max_vid:
group.vlan_id_ranges = [NumericRange(group.min_vid, group.max_vid)] group.vlan_id_ranges = [
NumericRange(group.min_vid, group.max_vid, bounds='[]')
]
group._total_vlan_ids = 0 group._total_vlan_ids = 0
for vlan_range in group.vlan_id_ranges: for vlan_range in group.vlan_id_ranges:
@ -31,7 +33,7 @@ class Migration(migrations.Migration):
name='vlan_id_ranges', name='vlan_id_ranges',
field=django.contrib.postgres.fields.ArrayField( field=django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ranges.IntegerRangeField(), base_field=django.contrib.postgres.fields.ranges.IntegerRangeField(),
default=ipam.models.vlans.default_vland_id_ranges, default=ipam.models.vlans.default_vlan_id_ranges,
size=None size=None
), ),
), ),

View File

@ -21,7 +21,7 @@ __all__ = (
) )
def default_vland_id_ranges(): def default_vlan_id_ranges():
return [ return [
NumericRange(VLAN_VID_MIN, VLAN_VID_MAX, bounds='[]') NumericRange(VLAN_VID_MIN, VLAN_VID_MAX, bounds='[]')
] ]
@ -57,7 +57,7 @@ class VLANGroup(OrganizationalModel):
vlan_id_ranges = ArrayField( vlan_id_ranges = ArrayField(
IntegerRangeField(), IntegerRangeField(),
verbose_name=_('VLAN ID ranges'), verbose_name=_('VLAN ID ranges'),
default=default_vland_id_ranges default=default_vlan_id_ranges
) )
_total_vlan_ids = models.PositiveBigIntegerField( _total_vlan_ids = models.PositiveBigIntegerField(
default=VLAN_VID_MAX - VLAN_VID_MIN + 1 default=VLAN_VID_MAX - VLAN_VID_MIN + 1
@ -120,7 +120,7 @@ class VLANGroup(OrganizationalModel):
""" """
available_vlans = {} available_vlans = {}
for vlan_range in self.vlan_id_ranges: for vlan_range in self.vlan_id_ranges:
available_vlans = {vid for vid in range(vlan_range.lower, vlan_range.upper + 1)} available_vlans = {vid for vid in range(vlan_range.lower, vlan_range.upper)}
available_vlans -= set(VLAN.objects.filter(group=self).values_list('vid', flat=True)) available_vlans -= set(VLAN.objects.filter(group=self).values_list('vid', flat=True))
return sorted(available_vlans) return sorted(available_vlans)

View File

@ -9,6 +9,7 @@ from utilities.querysets import RestrictedQuerySet
__all__ = ( __all__ = (
'ASNRangeQuerySet', 'ASNRangeQuerySet',
'PrefixQuerySet', 'PrefixQuerySet',
'VLANGroupQuerySet',
'VLANQuerySet', 'VLANQuerySet',
) )

View File

@ -168,7 +168,7 @@ class IntegerRangeSerializer(serializers.Serializer):
if type(data[0]) is not int or type(data[1]) is not int: if type(data[0]) is not int or type(data[1]) is not int:
raise ValidationError(_("Range boundaries must be defined as integers.")) raise ValidationError(_("Range boundaries must be defined as integers."))
return NumericRange(data[0], data[1]) return NumericRange(data[0], data[1], bounds='[]')
def to_representation(self, instance): def to_representation(self, instance):
return instance.lower, instance.upper return instance.lower, instance.upper - 1

View File

@ -138,27 +138,25 @@ def ranges_to_string(ranges):
""" """
Generate a human-friendly string from a set of ranges. Intended for use with ArrayField. Generate a human-friendly string from a set of ranges. Intended for use with ArrayField.
For example: For example:
[Range(1, 100), Range(200, 300)] => "1-100, 200-300" [[1, 100)], [200, 300)] => "1-99,200-299"
""" """
if not ranges: if not ranges:
return '' return ''
return ', '.join([f"{r.lower}-{r.upper}" for r in ranges]) return ','.join([f"{r.lower}-{r.upper - 1}" for r in ranges])
def string_to_range_array(value): def string_to_range_array(value):
""" """
Given a string in the format "1-100, 200-300" create an array of ranges. Intended for use with ArrayField. Given a string in the format "1-100, 200-300" create an array of ranges. Intended for use with ArrayField.
For example: For example:
"1-100, 200-300" => [1-100, 200-300] "1-99,200-299" => [NumericRange(1, 100), NumericRange(200, 300)]
""" """
if not value: if not value:
return None return None
ranges = value.split(",")
values = [] values = []
for dash_range in value.split(','): for dash_range in value.split(','):
if '-' not in dash_range: if '-' not in dash_range:
return None return None
lower, upper = dash_range.split('-') lower, upper = dash_range.split('-')
values.append(NumericRange(int(lower), int(upper))) values.append(NumericRange(int(lower), int(upper), bounds='[]'))
return values return values

View File

@ -2,7 +2,7 @@ import json
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField, RangeField
from django.core.exceptions import FieldDoesNotExist from django.core.exceptions import FieldDoesNotExist
from django.db.models import ManyToManyField, ManyToManyRel, JSONField from django.db.models import ManyToManyField, ManyToManyRel, JSONField
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
@ -12,6 +12,7 @@ from taggit.managers import TaggableManager
from core.models import ObjectType from core.models import ObjectType
from users.models import ObjectPermission from users.models import ObjectPermission
from utilities.data import ranges_to_string
from utilities.object_types import object_type_identifier from utilities.object_types import object_type_identifier
from utilities.permissions import resolve_permission_type from utilities.permissions import resolve_permission_type
from .utils import DUMMY_CF_DATA, extract_form_failures from .utils import DUMMY_CF_DATA, extract_form_failures
@ -139,6 +140,9 @@ class ModelTestCase(TestCase):
if type(field.base_field) is ArrayField: if type(field.base_field) is ArrayField:
# Handle nested arrays (e.g. choice sets) # Handle nested arrays (e.g. choice sets)
model_dict[key] = '\n'.join([f'{k},{v}' for k, v in value]) model_dict[key] = '\n'.join([f'{k},{v}' for k, v in value])
elif issubclass(type(field.base_field), RangeField):
# Handle arrays of numeric ranges (e.g. VLANGroup VLAN ID ranges)
model_dict[key] = ranges_to_string(value)
else: else:
model_dict[key] = ','.join([str(v) for v in value]) model_dict[key] = ','.join([str(v) for v in value])