mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-08 08:38:16 -06:00
Add tests for range utility functions
This commit is contained in:
parent
aa8475cdc4
commit
881135b938
@ -95,7 +95,7 @@ class VLANGroup(OrganizationalModel):
|
|||||||
if self.scope_id and not self.scope_type:
|
if self.scope_id and not self.scope_type:
|
||||||
raise ValidationError(_("Cannot set scope_id without scope_type."))
|
raise ValidationError(_("Cannot set scope_id without scope_type."))
|
||||||
|
|
||||||
# Validate vlan ranges
|
# Validate VID ranges
|
||||||
if self.vid_ranges and check_ranges_overlap(self.vid_ranges):
|
if self.vid_ranges and check_ranges_overlap(self.vid_ranges):
|
||||||
raise ValidationError({'vid_ranges': _("Ranges cannot overlap.")})
|
raise ValidationError({'vid_ranges': _("Ranges cannot overlap.")})
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer,
|
|||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.models import *
|
from ipam.models import *
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.data import string_to_range_array
|
from utilities.data import string_to_ranges
|
||||||
from utilities.testing import APITestCase, APIViewTestCases, create_test_device, disable_warnings
|
from utilities.testing import APITestCase, APIViewTestCases, create_test_device, disable_warnings
|
||||||
|
|
||||||
|
|
||||||
@ -883,7 +883,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
vlangroup = VLANGroup.objects.create(
|
vlangroup = VLANGroup.objects.create(
|
||||||
name='VLAN Group X',
|
name='VLAN Group X',
|
||||||
slug='vlan-group-x',
|
slug='vlan-group-x',
|
||||||
vid_ranges=string_to_range_array(f"{MIN_VID}-{MAX_VID}")
|
vid_ranges=string_to_ranges(f"{MIN_VID}-{MAX_VID}")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a set of VLANs within the group
|
# Create a set of VLANs within the group
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from netaddr import IPNetwork, IPSet
|
from netaddr import IPNetwork, IPSet
|
||||||
from utilities.data import string_to_range_array
|
from utilities.data import string_to_ranges
|
||||||
|
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.models import *
|
from ipam.models import *
|
||||||
@ -510,7 +510,7 @@ class TestVLANGroup(TestCase):
|
|||||||
vlangroup = VLANGroup.objects.create(
|
vlangroup = VLANGroup.objects.create(
|
||||||
name='VLAN Group 1',
|
name='VLAN Group 1',
|
||||||
slug='vlan-group-1',
|
slug='vlan-group-1',
|
||||||
vid_ranges=string_to_range_array('100-199'),
|
vid_ranges=string_to_ranges('100-199'),
|
||||||
)
|
)
|
||||||
VLAN.objects.bulk_create((
|
VLAN.objects.bulk_create((
|
||||||
VLAN(name='VLAN 100', vid=100, group=vlangroup),
|
VLAN(name='VLAN 100', vid=100, group=vlangroup),
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "VLAN IDs" %}</th>
|
<th scope="row">{% trans "VLAN IDs" %}</th>
|
||||||
<td>{{ object.vlan_ranges }}</td>
|
<td>{{ object.vid_ranges_list }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Utilization</th>
|
<th scope="row">Utilization</th>
|
||||||
|
@ -11,7 +11,7 @@ __all__ = (
|
|||||||
'flatten_dict',
|
'flatten_dict',
|
||||||
'ranges_to_string',
|
'ranges_to_string',
|
||||||
'shallow_compare_dict',
|
'shallow_compare_dict',
|
||||||
'string_to_range_array',
|
'string_to_ranges',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -121,14 +121,15 @@ def drange(start, end, step=decimal.Decimal(1)):
|
|||||||
|
|
||||||
def check_ranges_overlap(ranges):
|
def check_ranges_overlap(ranges):
|
||||||
"""
|
"""
|
||||||
Check if array of ranges overlap
|
Check for overlap in an iterable of NumericRanges.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# sort the ranges in increasing order
|
|
||||||
ranges.sort(key=lambda x: x.lower)
|
ranges.sort(key=lambda x: x.lower)
|
||||||
|
|
||||||
for i in range(1, len(ranges)):
|
for i in range(1, len(ranges)):
|
||||||
if (ranges[i - 1].upper >= ranges[i].lower):
|
prev_range = ranges[i - 1]
|
||||||
|
prev_upper = prev_range.upper if prev_range.upper_inc else prev_range.upper - 1
|
||||||
|
lower = ranges[i].lower if ranges[i].lower_inc else ranges[i].lower + 1
|
||||||
|
if prev_upper >= lower:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -136,8 +137,7 @@ def check_ranges_overlap(ranges):
|
|||||||
|
|
||||||
def ranges_to_string(ranges):
|
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:
|
|
||||||
[[1, 100)], [200, 300)] => "1-99,200-299"
|
[[1, 100)], [200, 300)] => "1-99,200-299"
|
||||||
"""
|
"""
|
||||||
if not ranges:
|
if not ranges:
|
||||||
@ -150,14 +150,15 @@ def ranges_to_string(ranges):
|
|||||||
return ','.join(output)
|
return ','.join(output)
|
||||||
|
|
||||||
|
|
||||||
def string_to_range_array(value):
|
def string_to_ranges(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" return an list of NumericRanges. Intended for use with ArrayField.
|
||||||
For example:
|
For example:
|
||||||
"1-99,200-299" => [NumericRange(1, 100), NumericRange(200, 300)]
|
"1-99,200-299" => [NumericRange(1, 100), NumericRange(200, 300)]
|
||||||
"""
|
"""
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
value.replace(' ', '') # Remove whitespace
|
||||||
values = []
|
values = []
|
||||||
for dash_range in value.split(','):
|
for dash_range in value.split(','):
|
||||||
if '-' not in dash_range:
|
if '-' not in dash_range:
|
||||||
|
@ -2,7 +2,7 @@ from django import forms
|
|||||||
from django.contrib.postgres.forms import SimpleArrayField
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from utilities.data import ranges_to_string, string_to_range_array
|
from utilities.data import ranges_to_string, string_to_ranges
|
||||||
|
|
||||||
from ..utils import parse_numeric_range
|
from ..utils import parse_numeric_range
|
||||||
|
|
||||||
@ -54,4 +54,4 @@ class NumericRangeArrayField(forms.CharField):
|
|||||||
return ranges_to_string(value)
|
return ranges_to_string(value)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return string_to_range_array(value)
|
return string_to_ranges(value)
|
||||||
|
68
netbox/utilities/tests/test_data.py
Normal file
68
netbox/utilities/tests/test_data.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
from django.db.backends.postgresql.psycopg_any import NumericRange
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from utilities.data import check_ranges_overlap, ranges_to_string, string_to_ranges
|
||||||
|
|
||||||
|
|
||||||
|
class RangeFunctionsTestCase(TestCase):
|
||||||
|
|
||||||
|
def test_check_ranges_overlap(self):
|
||||||
|
# Non-overlapping ranges
|
||||||
|
self.assertFalse(
|
||||||
|
check_ranges_overlap([
|
||||||
|
NumericRange(9, 19, bounds='(]'), # 10-19
|
||||||
|
NumericRange(19, 30, bounds='(]'), # 20-29
|
||||||
|
])
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
check_ranges_overlap([
|
||||||
|
NumericRange(10, 19, bounds='[]'), # 10-19
|
||||||
|
NumericRange(20, 29, bounds='[]'), # 20-29
|
||||||
|
])
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
check_ranges_overlap([
|
||||||
|
NumericRange(10, 20, bounds='[)'), # 10-19
|
||||||
|
NumericRange(20, 30, bounds='[)'), # 20-29
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
# Overlapping ranges
|
||||||
|
self.assertTrue(
|
||||||
|
check_ranges_overlap([
|
||||||
|
NumericRange(9, 20, bounds='(]'), # 10-20
|
||||||
|
NumericRange(19, 30, bounds='(]'), # 20-30
|
||||||
|
])
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
check_ranges_overlap([
|
||||||
|
NumericRange(10, 20, bounds='[]'), # 10-20
|
||||||
|
NumericRange(20, 30, bounds='[]'), # 20-30
|
||||||
|
])
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
check_ranges_overlap([
|
||||||
|
NumericRange(10, 21, bounds='[)'), # 10-20
|
||||||
|
NumericRange(20, 31, bounds='[)'), # 10-30
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ranges_to_string(self):
|
||||||
|
self.assertEqual(
|
||||||
|
ranges_to_string([
|
||||||
|
NumericRange(10, 20), # 10-19
|
||||||
|
NumericRange(30, 40), # 30-39
|
||||||
|
NumericRange(100, 200), # 100-199
|
||||||
|
]),
|
||||||
|
'10-19,30-39,100-199'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_string_to_ranges(self):
|
||||||
|
self.assertEqual(
|
||||||
|
string_to_ranges('10-19, 30-39, 100-199'),
|
||||||
|
[
|
||||||
|
NumericRange(10, 19, bounds='[]'), # 10-19
|
||||||
|
NumericRange(30, 39, bounds='[]'), # 30-39
|
||||||
|
NumericRange(100, 199, bounds='[]'), # 100-199
|
||||||
|
]
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user