fix: restore populated rack and reorder views

This commit is contained in:
2026-08-13 09:37:59 +02:00
parent bc2024ac13
commit a5f8cb62c7
12 changed files with 379 additions and 67 deletions
+68 -10
View File
@@ -1,5 +1,6 @@
import decimal
import logging
from collections import defaultdict
from contextvars import ContextVar
from copy import deepcopy
from fractions import Fraction
@@ -114,6 +115,65 @@ def get_width_position(device, placements=None):
return _stored_width_position(device)
def effective_width_positions(devices, placements=None):
"""Return stored rack widths and infer missing rows for devices sharing one slot."""
devices = list(devices)
groups = defaultdict(list)
result = {}
for device in devices:
key = (
device.rack_id,
device.face,
decimal.Decimal(str(device.position)),
decimal.Decimal(str(device.device_type.u_height)),
)
groups[key].append(device)
for group in groups.values():
stored = {}
missing = []
for device in group:
if placements is not None:
placement = placements.get(device.pk)
else:
try:
placement = device.netbox_utilities_rack_placement
except ObjectDoesNotExist:
placement = None
if placement is None:
missing.append(device)
else:
stored[device.pk] = normalize_width_position(placement.width, placement.horizontal_position)
candidate_width = None
stored_widths = {width for width, _position in stored.values()}
if len(stored_widths) == 1:
stored_width = next(iter(stored_widths))
if len(group) <= stored_width:
candidate_width = stored_width
elif not stored and 2 <= len(group) <= 4:
candidate_width = len(group)
if candidate_width is not None:
used_positions = {position for width, position in stored.values() if width == candidate_width}
free_positions = iter(
position for position in range(1, candidate_width + 1) if position not in used_positions
)
for device in sorted(missing, key=lambda item: item.pk):
position = next(free_positions, None)
if position is None:
break
result[device.pk] = (candidate_width, position, "inferred")
for device_id, (width, position) in stored.items():
result[device_id] = (width, position, "stored")
for device in missing:
result.setdefault(device.pk, (FULL_WIDTH, 1, "default"))
return result
def stage_width_position(device, width, horizontal_position):
width, horizontal_position = normalize_width_position(width, horizontal_position)
device._netbox_utilities_rack_width = width
@@ -432,7 +492,7 @@ def _install_device_form():
def _install_rack_svg():
from dcim.svg.racks import RackElevationSVG
if getattr(RackElevationSVG, "_netbox_utilities_rack_width_version", None) == 3:
if getattr(RackElevationSVG, "_netbox_utilities_rack_width_version", None) == 4:
return
current_draw_face = RackElevationSVG.draw_face
current_globals = getattr(current_draw_face, "__globals__", {})
@@ -448,16 +508,14 @@ def _install_rack_svg():
# NetBox's get_rack_units() stores only one device per rack unit and
# therefore cannot represent two devices mounted beside each other.
# Query every mounted device directly and render each exactly once.
for device in _rack_elevation_devices(elevation.rack, face):
try:
placement = device.netbox_utilities_rack_placement
except ObjectDoesNotExist:
placement = None
devices = list(_rack_elevation_devices(elevation.rack, face))
widths = effective_width_positions(devices)
for device in devices:
width_divisor, horizontal_position, _source = widths[device.pk]
height = decimal.Decimal(str(device.device_type.u_height))
coords = elevation._get_device_coords(device.position, height)
width = elevation.unit_width if placement is None else elevation.unit_width / placement.width
if placement is not None:
coords = (coords[0] + width * (placement.horizontal_position - 1), coords[1])
width = elevation.unit_width / width_divisor
coords = (coords[0] + width * (horizontal_position - 1), coords[1])
size = (width, int(elevation.unit_height * height))
if device.pk in elevation.permitted_device_ids:
if device.face == face and not opposite:
@@ -469,7 +527,7 @@ def _install_rack_svg():
RackElevationSVG.draw_face = width_aware_draw_face
RackElevationSVG._netbox_utilities_rack_width_installed = True
RackElevationSVG._netbox_utilities_rack_width_version = 3
RackElevationSVG._netbox_utilities_rack_width_version = 4
def _rack_elevation_devices(rack, face):