From 23acebcf17f42004f227e72f189e3e9f996b23d6 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 17 Sep 2026 15:16:09 +0200 Subject: [PATCH] fix: handle plain HttpResponse from elevation SVG endpoint, unracked devices - RackElevationOfflineSVGView called .render() unconditionally on the RackViewSet elevation action's response, but that action returns a plain HttpResponse (not a DRF Response) when render=svg is requested, causing AttributeError on every click. - effective_width_positions() crashed with decimal.InvalidOperation when a device had no rack position set (e.g. surfaced via NetBox Topology Views, which includes unracked devices). Such devices now get the default full-width result instead of entering the Decimal conversion. Co-Authored-By: Claude Sonnet 5 --- netbox_utilities/__init__.py | 2 +- netbox_utilities/rack_width.py | 3 +++ netbox_utilities/tests/test_rack_width.py | 28 +++++++++++++++++++++++ netbox_utilities/views.py | 3 ++- pyproject.toml | 2 +- 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/netbox_utilities/__init__.py b/netbox_utilities/__init__.py index 9c952c0..96dc203 100644 --- a/netbox_utilities/__init__.py +++ b/netbox_utilities/__init__.py @@ -1,6 +1,6 @@ from netbox.plugins import PluginConfig, get_plugin_config -__version__ = "0.11.1" +__version__ = "0.11.2" class NetBoxUtilitiesConfig(PluginConfig): diff --git a/netbox_utilities/rack_width.py b/netbox_utilities/rack_width.py index f156b03..56ba9e7 100644 --- a/netbox_utilities/rack_width.py +++ b/netbox_utilities/rack_width.py @@ -122,6 +122,9 @@ def effective_width_positions(devices, placements=None): result = {} for device in devices: + if device.position is None: + result.setdefault(device.pk, (FULL_WIDTH, 1, "default")) + continue key = ( device.rack_id, device.face, diff --git a/netbox_utilities/tests/test_rack_width.py b/netbox_utilities/tests/test_rack_width.py index 0dcfd6d..6c37e0c 100644 --- a/netbox_utilities/tests/test_rack_width.py +++ b/netbox_utilities/tests/test_rack_width.py @@ -191,6 +191,34 @@ class RackWidthTest(SimpleTestCase): }, ) + def test_ignores_unracked_devices_without_a_position(self): + devices = [ + SimpleNamespace( + pk=41, + rack_id=None, + face="front", + position=None, + device_type=SimpleNamespace(u_height=1), + netbox_utilities_rack_placement=None, + ), + SimpleNamespace( + pk=42, + rack_id=3, + face="front", + position=11, + device_type=SimpleNamespace(u_height=1), + netbox_utilities_rack_placement=None, + ), + ] + + self.assertEqual( + effective_width_positions(devices), + { + 41: (1, 1, "default"), + 42: (1, 1, "default"), + }, + ) + def test_adds_rack_width_fields_to_existing_location_fieldset(self): from utilities.forms.rendering import FieldSet diff --git a/netbox_utilities/views.py b/netbox_utilities/views.py index 5ab7aa0..eab4635 100644 --- a/netbox_utilities/views.py +++ b/netbox_utilities/views.py @@ -391,7 +391,8 @@ class RackElevationOfflineSVGView(LoginRequiredMixin, View): api_request.user = request.user drf_view = RackViewSet.as_view({"get": "elevation"}) response = drf_view(api_request, pk=rack.pk) - response.render() + if hasattr(response, "render"): + response.render() offline_svg = strip_svg_hyperlinks(response.content) filename = f"{rack.name}-{face}-offline.svg".replace(" ", "_") diff --git a/pyproject.toml b/pyproject.toml index f17a7ce..d9fb427 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "netbox-utilities" -version = "0.11.1" +version = "0.11.2" description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6/4.7" readme = "README.md" requires-python = ">=3.12"