Skip to content

Commit cfbd963

Browse files
pheusjeremystretch
authored andcommitted
feat(utilities): Add ranges_to_string_list
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
1 parent c9386bc commit cfbd963

File tree

5 files changed

+67
-17
lines changed

5 files changed

+67
-17
lines changed

netbox/ipam/models/vlans.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
from dcim.models import Interface, Site, SiteGroup
1111
from ipam.choices import *
1212
from ipam.constants import *
13-
from ipam.querysets import VLANQuerySet, VLANGroupQuerySet
13+
from ipam.querysets import VLANGroupQuerySet, VLANQuerySet
1414
from netbox.models import OrganizationalModel, PrimaryModel, NetBoxModel
15-
from utilities.data import check_ranges_overlap, ranges_to_string
15+
from utilities.data import check_ranges_overlap, ranges_to_string, ranges_to_string_list
1616
from virtualization.models import VMInterface
1717

1818
__all__ = (
@@ -164,8 +164,18 @@ def get_child_vlans(self):
164164
"""
165165
return VLAN.objects.filter(group=self).order_by('vid')
166166

167+
@property
168+
def vid_ranges_items(self):
169+
"""
170+
Property that converts VID ranges to a list of string representations.
171+
"""
172+
return ranges_to_string_list(self.vid_ranges)
173+
167174
@property
168175
def vid_ranges_list(self):
176+
"""
177+
Property that converts VID ranges into a string representation.
178+
"""
169179
return ranges_to_string(self.vid_ranges)
170180

171181

netbox/ipam/tables/vlans.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ class VLANGroupTable(TenancyColumnsMixin, NetBoxTable):
4141
linkify=True,
4242
orderable=False
4343
)
44-
vid_ranges_list = tables.Column(
44+
vid_ranges_list = columns.ArrayColumn(
45+
accessor='vid_ranges_items',
4546
verbose_name=_('VID Ranges'),
4647
orderable=False
4748
)

netbox/templates/ipam/vlangroup.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ <h2 class="card-header">{% trans "VLAN Group" %}</h2>
4040
</tr>
4141
<tr>
4242
<th scope="row">{% trans "VLAN IDs" %}</th>
43-
<td>{{ object.vid_ranges_list }}</td>
43+
<td>{{ object.vid_ranges_items|join:", " }}</td>
4444
</tr>
4545
<tr>
4646
<th scope="row">Utilization</th>

netbox/utilities/data.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import decimal
2-
from django.db.backends.postgresql.psycopg_any import NumericRange
32
from itertools import count, groupby
43

4+
from django.db.backends.postgresql.psycopg_any import NumericRange
5+
56
__all__ = (
67
'array_to_ranges',
78
'array_to_string',
@@ -10,6 +11,7 @@
1011
'drange',
1112
'flatten_dict',
1213
'ranges_to_string',
14+
'ranges_to_string_list',
1315
'shallow_compare_dict',
1416
'string_to_ranges',
1517
)
@@ -73,8 +75,10 @@ def shallow_compare_dict(source_dict, destination_dict, exclude=tuple()):
7375
def array_to_ranges(array):
7476
"""
7577
Convert an arbitrary array of integers to a list of consecutive values. Nonconsecutive values are returned as
76-
single-item tuples. For example:
77-
[0, 1, 2, 10, 14, 15, 16] => [(0, 2), (10,), (14, 16)]"
78+
single-item tuples.
79+
80+
Example:
81+
[0, 1, 2, 10, 14, 15, 16] => [(0, 2), (10,), (14, 16)]
7882
"""
7983
group = (
8084
list(x) for _, x in groupby(sorted(array), lambda x, c=count(): next(c) - x)
@@ -87,7 +91,8 @@ def array_to_ranges(array):
8791
def array_to_string(array):
8892
"""
8993
Generate an efficient, human-friendly string from a set of integers. Intended for use with ArrayField.
90-
For example:
94+
95+
Example:
9196
[0, 1, 2, 10, 14, 15, 16] => "0-2, 10, 14-16"
9297
"""
9398
ret = []
@@ -135,6 +140,29 @@ def check_ranges_overlap(ranges):
135140
return False
136141

137142

143+
def ranges_to_string_list(ranges):
144+
"""
145+
Convert numeric ranges to a list of display strings.
146+
147+
Each range is rendered as "lower-upper" or "lower" (for singletons).
148+
Bounds are normalized to inclusive values using ``lower_inc``/``upper_inc``.
149+
This underpins ``ranges_to_string()``, which joins the result with commas.
150+
151+
Example:
152+
[NumericRange(1, 6), NumericRange(8, 9), NumericRange(10, 13)] => ["1-5", "8", "10-12"]
153+
"""
154+
if not ranges:
155+
return []
156+
157+
output: list[str] = []
158+
for r in ranges:
159+
# Compute inclusive bounds regardless of how the DB range is stored.
160+
lower = r.lower if r.lower_inc else r.lower + 1
161+
upper = r.upper if r.upper_inc else r.upper - 1
162+
output.append(f"{lower}-{upper}" if lower != upper else str(lower))
163+
return output
164+
165+
138166
def ranges_to_string(ranges):
139167
"""
140168
Converts a list of ranges into a string representation.
@@ -151,12 +179,7 @@ def ranges_to_string(ranges):
151179
"""
152180
if not ranges:
153181
return ''
154-
output = []
155-
for r in ranges:
156-
lower = r.lower if r.lower_inc else r.lower + 1
157-
upper = r.upper if r.upper_inc else r.upper - 1
158-
output.append(f"{lower}-{upper}" if lower != upper else str(lower))
159-
return ','.join(output)
182+
return ','.join(ranges_to_string_list(ranges))
160183

161184

162185
def string_to_ranges(value):

netbox/utilities/tests/test_data.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from django.db.backends.postgresql.psycopg_any import NumericRange
22
from django.test import TestCase
3-
4-
from utilities.data import check_ranges_overlap, ranges_to_string, string_to_ranges
3+
from utilities.data import (
4+
check_ranges_overlap,
5+
ranges_to_string,
6+
ranges_to_string_list,
7+
string_to_ranges,
8+
)
59

610

711
class RangeFunctionsTestCase(TestCase):
@@ -47,14 +51,26 @@ def test_check_ranges_overlap(self):
4751
])
4852
)
4953

54+
def test_ranges_to_string_list(self):
55+
self.assertEqual(
56+
ranges_to_string_list([
57+
NumericRange(10, 20), # 10-19
58+
NumericRange(30, 40), # 30-39
59+
NumericRange(50, 51), # 50-50
60+
NumericRange(100, 200), # 100-199
61+
]),
62+
['10-19', '30-39', '50', '100-199']
63+
)
64+
5065
def test_ranges_to_string(self):
5166
self.assertEqual(
5267
ranges_to_string([
5368
NumericRange(10, 20), # 10-19
5469
NumericRange(30, 40), # 30-39
70+
NumericRange(50, 51), # 50-50
5571
NumericRange(100, 200), # 100-199
5672
]),
57-
'10-19,30-39,100-199'
73+
'10-19,30-39,50,100-199'
5874
)
5975

6076
def test_string_to_ranges(self):

0 commit comments

Comments
 (0)