mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-09 01:49:35 -06:00
feat(utilities): Add ranges_to_string_list
Some checks are pending
CI / build (20.x, 3.10) (push) Waiting to run
CI / build (20.x, 3.11) (push) Waiting to run
CI / build (20.x, 3.12) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run
Some checks are pending
CI / build (20.x, 3.10) (push) Waiting to run
CI / build (20.x, 3.11) (push) Waiting to run
CI / build (20.x, 3.12) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run
Introduce `ranges_to_string_list` for converting numeric ranges into a list of readable strings. Update the `vid_ranges_list` property and templates to use this method for better readability and maintainability. Add related tests to ensure functionality. Closes #20516
This commit is contained in:
parent
c9386bc9c3
commit
cfbd9632ac
@ -10,9 +10,9 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from dcim.models import Interface, Site, SiteGroup
|
from dcim.models import Interface, Site, SiteGroup
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
from ipam.querysets import VLANQuerySet, VLANGroupQuerySet
|
from ipam.querysets import VLANGroupQuerySet, VLANQuerySet
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel, NetBoxModel
|
from netbox.models import OrganizationalModel, PrimaryModel, NetBoxModel
|
||||||
from utilities.data import check_ranges_overlap, ranges_to_string
|
from utilities.data import check_ranges_overlap, ranges_to_string, ranges_to_string_list
|
||||||
from virtualization.models import VMInterface
|
from virtualization.models import VMInterface
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -164,8 +164,18 @@ class VLANGroup(OrganizationalModel):
|
|||||||
"""
|
"""
|
||||||
return VLAN.objects.filter(group=self).order_by('vid')
|
return VLAN.objects.filter(group=self).order_by('vid')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vid_ranges_items(self):
|
||||||
|
"""
|
||||||
|
Property that converts VID ranges to a list of string representations.
|
||||||
|
"""
|
||||||
|
return ranges_to_string_list(self.vid_ranges)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vid_ranges_list(self):
|
def vid_ranges_list(self):
|
||||||
|
"""
|
||||||
|
Property that converts VID ranges into a string representation.
|
||||||
|
"""
|
||||||
return ranges_to_string(self.vid_ranges)
|
return ranges_to_string(self.vid_ranges)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,8 @@ class VLANGroupTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
linkify=True,
|
linkify=True,
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
vid_ranges_list = tables.Column(
|
vid_ranges_list = columns.ArrayColumn(
|
||||||
|
accessor='vid_ranges_items',
|
||||||
verbose_name=_('VID Ranges'),
|
verbose_name=_('VID Ranges'),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "VLAN IDs" %}</th>
|
<th scope="row">{% trans "VLAN IDs" %}</th>
|
||||||
<td>{{ object.vid_ranges_list }}</td>
|
<td>{{ object.vid_ranges_items|join:", " }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Utilization</th>
|
<th scope="row">Utilization</th>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import decimal
|
import decimal
|
||||||
from django.db.backends.postgresql.psycopg_any import NumericRange
|
|
||||||
from itertools import count, groupby
|
from itertools import count, groupby
|
||||||
|
|
||||||
|
from django.db.backends.postgresql.psycopg_any import NumericRange
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'array_to_ranges',
|
'array_to_ranges',
|
||||||
'array_to_string',
|
'array_to_string',
|
||||||
@ -10,6 +11,7 @@ __all__ = (
|
|||||||
'drange',
|
'drange',
|
||||||
'flatten_dict',
|
'flatten_dict',
|
||||||
'ranges_to_string',
|
'ranges_to_string',
|
||||||
|
'ranges_to_string_list',
|
||||||
'shallow_compare_dict',
|
'shallow_compare_dict',
|
||||||
'string_to_ranges',
|
'string_to_ranges',
|
||||||
)
|
)
|
||||||
@ -73,8 +75,10 @@ def shallow_compare_dict(source_dict, destination_dict, exclude=tuple()):
|
|||||||
def array_to_ranges(array):
|
def array_to_ranges(array):
|
||||||
"""
|
"""
|
||||||
Convert an arbitrary array of integers to a list of consecutive values. Nonconsecutive values are returned as
|
Convert an arbitrary array of integers to a list of consecutive values. Nonconsecutive values are returned as
|
||||||
single-item tuples. For example:
|
single-item tuples.
|
||||||
[0, 1, 2, 10, 14, 15, 16] => [(0, 2), (10,), (14, 16)]"
|
|
||||||
|
Example:
|
||||||
|
[0, 1, 2, 10, 14, 15, 16] => [(0, 2), (10,), (14, 16)]
|
||||||
"""
|
"""
|
||||||
group = (
|
group = (
|
||||||
list(x) for _, x in groupby(sorted(array), lambda x, c=count(): next(c) - x)
|
list(x) for _, x in groupby(sorted(array), lambda x, c=count(): next(c) - x)
|
||||||
@ -87,7 +91,8 @@ def array_to_ranges(array):
|
|||||||
def array_to_string(array):
|
def array_to_string(array):
|
||||||
"""
|
"""
|
||||||
Generate an efficient, human-friendly string from a set of integers. Intended for use with ArrayField.
|
Generate an efficient, human-friendly string from a set of integers. Intended for use with ArrayField.
|
||||||
For example:
|
|
||||||
|
Example:
|
||||||
[0, 1, 2, 10, 14, 15, 16] => "0-2, 10, 14-16"
|
[0, 1, 2, 10, 14, 15, 16] => "0-2, 10, 14-16"
|
||||||
"""
|
"""
|
||||||
ret = []
|
ret = []
|
||||||
@ -135,6 +140,29 @@ def check_ranges_overlap(ranges):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ranges_to_string_list(ranges):
|
||||||
|
"""
|
||||||
|
Convert numeric ranges to a list of display strings.
|
||||||
|
|
||||||
|
Each range is rendered as "lower-upper" or "lower" (for singletons).
|
||||||
|
Bounds are normalized to inclusive values using ``lower_inc``/``upper_inc``.
|
||||||
|
This underpins ``ranges_to_string()``, which joins the result with commas.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
[NumericRange(1, 6), NumericRange(8, 9), NumericRange(10, 13)] => ["1-5", "8", "10-12"]
|
||||||
|
"""
|
||||||
|
if not ranges:
|
||||||
|
return []
|
||||||
|
|
||||||
|
output: list[str] = []
|
||||||
|
for r in ranges:
|
||||||
|
# Compute inclusive bounds regardless of how the DB range is stored.
|
||||||
|
lower = r.lower if r.lower_inc else r.lower + 1
|
||||||
|
upper = r.upper if r.upper_inc else r.upper - 1
|
||||||
|
output.append(f"{lower}-{upper}" if lower != upper else str(lower))
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
def ranges_to_string(ranges):
|
def ranges_to_string(ranges):
|
||||||
"""
|
"""
|
||||||
Converts a list of ranges into a string representation.
|
Converts a list of ranges into a string representation.
|
||||||
@ -151,12 +179,7 @@ def ranges_to_string(ranges):
|
|||||||
"""
|
"""
|
||||||
if not ranges:
|
if not ranges:
|
||||||
return ''
|
return ''
|
||||||
output = []
|
return ','.join(ranges_to_string_list(ranges))
|
||||||
for r in ranges:
|
|
||||||
lower = r.lower if r.lower_inc else r.lower + 1
|
|
||||||
upper = r.upper if r.upper_inc else r.upper - 1
|
|
||||||
output.append(f"{lower}-{upper}" if lower != upper else str(lower))
|
|
||||||
return ','.join(output)
|
|
||||||
|
|
||||||
|
|
||||||
def string_to_ranges(value):
|
def string_to_ranges(value):
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
from django.db.backends.postgresql.psycopg_any import NumericRange
|
from django.db.backends.postgresql.psycopg_any import NumericRange
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from utilities.data import (
|
||||||
from utilities.data import check_ranges_overlap, ranges_to_string, string_to_ranges
|
check_ranges_overlap,
|
||||||
|
ranges_to_string,
|
||||||
|
ranges_to_string_list,
|
||||||
|
string_to_ranges,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RangeFunctionsTestCase(TestCase):
|
class RangeFunctionsTestCase(TestCase):
|
||||||
@ -47,14 +51,26 @@ class RangeFunctionsTestCase(TestCase):
|
|||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_ranges_to_string_list(self):
|
||||||
|
self.assertEqual(
|
||||||
|
ranges_to_string_list([
|
||||||
|
NumericRange(10, 20), # 10-19
|
||||||
|
NumericRange(30, 40), # 30-39
|
||||||
|
NumericRange(50, 51), # 50-50
|
||||||
|
NumericRange(100, 200), # 100-199
|
||||||
|
]),
|
||||||
|
['10-19', '30-39', '50', '100-199']
|
||||||
|
)
|
||||||
|
|
||||||
def test_ranges_to_string(self):
|
def test_ranges_to_string(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ranges_to_string([
|
ranges_to_string([
|
||||||
NumericRange(10, 20), # 10-19
|
NumericRange(10, 20), # 10-19
|
||||||
NumericRange(30, 40), # 30-39
|
NumericRange(30, 40), # 30-39
|
||||||
|
NumericRange(50, 51), # 50-50
|
||||||
NumericRange(100, 200), # 100-199
|
NumericRange(100, 200), # 100-199
|
||||||
]),
|
]),
|
||||||
'10-19,30-39,100-199'
|
'10-19,30-39,50,100-199'
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_string_to_ranges(self):
|
def test_string_to_ranges(self):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user