fix: update rack width controls and images

This commit is contained in:
2026-08-12 15:57:22 +02:00
parent c663069825
commit 4ee78ae4fe
6 changed files with 56 additions and 5 deletions
+7 -1
View File
@@ -36,7 +36,7 @@ Release-Tag oder ein bestimmter Commit verwendet werden:
```bash ```bash
/opt/netbox/venv/bin/pip install --upgrade --force-reinstall \ /opt/netbox/venv/bin/pip install --upgrade --force-reinstall \
"git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.8.6" "git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.8.7"
``` ```
Alternativ kann hinter dem `@` die vollständige Commit-ID stehen. Alternativ kann hinter dem `@` die vollständige Commit-ID stehen.
@@ -150,6 +150,12 @@ sowohl die Gerätehöhe als auch die Breite und verhindert horizontale oder
vertikale Überschneidungen. Mehrere HE hohe Geräte werden ebenfalls vertikale Überschneidungen. Mehrere HE hohe Geräte werden ebenfalls
unterstützt. unterstützt.
Nach einer Änderung der Rackbreite wird die **Breitenposition** unmittelbar
aktualisiert; ein Zwischenspeichern oder Neuladen ist nicht erforderlich.
Hinterlegte Vorder- und Rückseitenbilder des Gerätetyps werden bei
Teilbreitengeräten proportional in die verfügbare Breite eingepasst, ohne das
Bild seitlich abzuschneiden.
Ohne Breitenangabe belegt ein Gerät wie bisher die volle Rackbreite. Das gilt Ohne Breitenangabe belegt ein Gerät wie bisher die volle Rackbreite. Das gilt
automatisch für sämtliche vorhandenen Geräte; es findet keine Änderung oder automatisch für sämtliche vorhandenen Geräte; es findet keine Änderung oder
Migration bestehender Platzierungen statt. Teilbreite und Position werden auf Migration bestehender Platzierungen statt. Teilbreite und Position werden auf
+1 -1
View File
@@ -1,6 +1,6 @@
from netbox.plugins import PluginConfig, get_plugin_config from netbox.plugins import PluginConfig, get_plugin_config
__version__ = "0.8.6" __version__ = "0.8.7"
class NetBoxUtilitiesConfig(PluginConfig): class NetBoxUtilitiesConfig(PluginConfig):
+14
View File
@@ -465,6 +465,7 @@ def _install_rack_svg():
elevation.draw_device_front(device, coords, size) elevation.draw_device_front(device, coords, size)
else: else:
elevation.draw_device_rear(device, coords, size) elevation.draw_device_rear(device, coords, size)
_fit_latest_elevation_image(elevation.drawing)
else: else:
from svgwrite.shapes import Rect from svgwrite.shapes import Rect
@@ -496,6 +497,19 @@ def _partial_width_elevation_devices(rack, face):
) )
def _fit_latest_elevation_image(drawing):
"""Fit the latest device image into a partial-width slot without cropping it."""
from svgwrite.image import Image
if not getattr(drawing, "elements", None):
return
container = drawing.elements[-1]
for element in reversed(getattr(container, "elements", ())):
if isinstance(element, Image):
element.fit(scale="meet")
return
def _validate_before_save(sender, instance, raw=False, **kwargs): def _validate_before_save(sender, instance, raw=False, **kwargs):
if not raw: if not raw:
using = kwargs.get("using") using = kwargs.get("using")
@@ -103,11 +103,18 @@
const position = Number.parseInt(option.value || '1', 10); const position = Number.parseInt(option.value || '1', 10);
option.disabled = position > width; option.disabled = position > width;
}); });
if (width === 1 || Number.parseInt(positionField.value || '1', 10) > width) {
const resetPosition = width === 1 || Number.parseInt(positionField.value || '1', 10) > width;
if (resetPosition) {
positionField.value = '1'; positionField.value = '1';
}
positionField.disabled = width === 1;
if (positionField.tomselect) positionField.tomselect.sync();
if (resetPosition) {
positionField.dispatchEvent(new Event('change', {bubbles: true})); positionField.dispatchEvent(new Event('change', {bubbles: true}));
} }
positionField.disabled = width === 1;
}; };
widthField.addEventListener('change', updatePositions); widthField.addEventListener('change', updatePositions);
+24
View File
@@ -1,4 +1,5 @@
from importlib import import_module from importlib import import_module
from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@@ -12,6 +13,7 @@ from django.urls import resolve, reverse
from netbox_utilities.rack_width import ( from netbox_utilities.rack_width import (
_cleanup_unracked_placement, _cleanup_unracked_placement,
_fit_latest_elevation_image,
_partial_width_elevation_devices, _partial_width_elevation_devices,
available_units_for_device, available_units_for_device,
horizontal_interval, horizontal_interval,
@@ -25,6 +27,28 @@ from netbox_utilities.rack_width import (
class RackWidthTest(SimpleTestCase): class RackWidthTest(SimpleTestCase):
def test_rack_width_javascript_synchronizes_netbox_tom_select(self):
script = (Path(__file__).parents[1] / "static" / "netbox_utilities" / "forms.js").read_text(encoding="utf-8")
self.assertIn("positionField.disabled = width === 1", script)
self.assertIn("positionField.tomselect.sync()", script)
def test_partial_width_device_image_is_fitted_without_cropping(self):
from svgwrite import Drawing
from svgwrite.container import Hyperlink
from svgwrite.image import Image
drawing = Drawing()
link = Hyperlink("/dcim/devices/1/")
device_image = Image("/media/device.png", size=(100, 20))
device_image.fit(scale="slice")
link.add(device_image)
drawing.add(link)
_fit_latest_elevation_image(drawing)
self.assertEqual(device_image.attribs["preserveAspectRatio"], "xMidYMid meet")
def test_partial_width_svg_devices_include_core_device_bay_annotation(self): def test_partial_width_svg_devices_include_core_device_bay_annotation(self):
from dcim.choices import DeviceFaceChoices from dcim.choices import DeviceFaceChoices
from dcim.models import Rack from dcim.models import Rack
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "netbox-utilities" name = "netbox-utilities"
version = "0.8.6" version = "0.8.7"
description = "Navigation, tenant utilities, partial-width rack devices, bulk uploads, and rack reordering for NetBox 4.6" description = "Navigation, tenant utilities, partial-width rack devices, bulk uploads, and rack reordering for NetBox 4.6"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"