Skip to content

Render VLAN Group VID ranges with ArrayColumn #20516

@pheus

Description

@pheus

NetBox version

v4.4.2

Feature type

Change to existing functionality

Proposed functionality

Introduce a helper that returns a list of human‑readable range strings so callers can control spacing and presentation without re‑implementing range logic:

  • ranges_to_text_list(ranges: Iterable[NumericRange]) -> list[str]
    Returns items like ["5-10", "25-30"] using the same inclusive semantics that ranges_to_string() currently applies.

Refactor the existing ranges_to_string() implementation to build on the new helper while preserving its current output (comma‑separated, no spaces), e.g.:

def ranges_to_string(ranges):
    return ",".join(ranges_to_text_list(ranges))

Finally, update the VLAN Group list to render VID ranges using ArrayColumn so the UI shows the standard comma‑and‑space formatting (", "), improving readability and aligning with other list displays.

Use case

Today, utilities.data.ranges_to_string() emits no spaces between items (e.g., "5-10,25-30"). This is correct for some contexts, but it makes it hard to reuse when a space‑separated presentation is desired (for example, table displays or human‑facing messages). Adding ranges_to_text_list() lets call sites choose their own joiner, and lets tables use ArrayColumn (which already renders with ", ") for consistent UI.

Database changes

None.

External dependencies

None.

Proposed change (implementation notes)

  1. Add helper in utilities/data.py
from typing import Iterable
from django.db.backends.postgresql.psycopg_any import NumericRange

def ranges_to_text_list(ranges: Iterable[NumericRange]) -> list[str]:
    """
    Convert NumericRange values to human-friendly inclusive strings ["lo-hi", ...].
    Mirrors ranges_to_string() semantics.
    """
    if not ranges:
        return []

    items: list[str] = []
    for r in ranges:
        # Compute inclusive bounds regardless of how the DB range is stored.
        lower = r.lower if getattr(r, "lower_inc", True) else r.lower + 1
        upper = r.upper if getattr(r, "upper_inc", False) else r.upper - 1

        # If you prefer singletons like "5" instead of "5-5", uncomment:
        # if lower == upper:
        #     items.append(f"{lower}")
        # else:
        items.append(f"{lower}-{upper}")
    return items
  1. Refactor ranges_to_string() (backward compatible)
def ranges_to_string(ranges):
    if not ranges:
        return ""
    return ",".join(ranges_to_text_list(ranges))
  1. VLAN Group model
    Keep the existing string property (e.g., vid_ranges_list) to avoid breaking any consumers. Add a new list‑returning property for table use:
# ipam/models/vlans.py
from utilities.data import ranges_to_text_list

@property
def vid_ranges_items(self) -> list[str]:
    return ranges_to_text_list(self.vid_ranges)
  1. VLAN Group table
    Switch the column to ArrayColumn but keep the column name vid_ranges_list to preserve saved table configs; point it at the new list property:
# ipam/tables/vlans.py
from netbox.tables import columns

# Old:
# vid_ranges_list = tables.Column(verbose_name=_('VID Ranges'), orderable=False)

# New:
vid_ranges_list = columns.ArrayColumn(
    accessor='vid_ranges_items',
    verbose_name=_('VID Ranges'),
    orderable=False,
)

Backwards compatibility

  • ranges_to_string() output and signature remain unchanged (still returns a str with no spaces).
  • VLANGroup.vid_ranges_list remains available (still a str) to avoid breaking any code or saved table configurations that reference its column name. The table change keeps the same column name while sourcing from a new vid_ranges_items list property.

Additional context / references

  • Background plugin work that motivated this (spacing control): ranges_to_text_list implementation in netbox‑acls:
    pheus/netbox-acls@da406f9

Before / After (table rendering example)

  • Before: VID Ranges1-99,200-299
  • After: VID Ranges1-99, 200-299 (space after comma via ArrayColumn)

Metadata

Metadata

Assignees

Labels

complexity: lowRequires minimal effort to implementstatus: acceptedThis issue has been accepted for implementationtype: featureIntroduction of new functionality to the application

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions