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 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 15:16:09 +02:00
co-authored by Claude Sonnet 5
parent c722abe497
commit 23acebcf17
5 changed files with 35 additions and 3 deletions
+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.11.1" __version__ = "0.11.2"
class NetBoxUtilitiesConfig(PluginConfig): class NetBoxUtilitiesConfig(PluginConfig):
+3
View File
@@ -122,6 +122,9 @@ def effective_width_positions(devices, placements=None):
result = {} result = {}
for device in devices: for device in devices:
if device.position is None:
result.setdefault(device.pk, (FULL_WIDTH, 1, "default"))
continue
key = ( key = (
device.rack_id, device.rack_id,
device.face, device.face,
+28
View File
@@ -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): def test_adds_rack_width_fields_to_existing_location_fieldset(self):
from utilities.forms.rendering import FieldSet from utilities.forms.rendering import FieldSet
+2 -1
View File
@@ -391,7 +391,8 @@ class RackElevationOfflineSVGView(LoginRequiredMixin, View):
api_request.user = request.user api_request.user = request.user
drf_view = RackViewSet.as_view({"get": "elevation"}) drf_view = RackViewSet.as_view({"get": "elevation"})
response = drf_view(api_request, pk=rack.pk) response = drf_view(api_request, pk=rack.pk)
response.render() if hasattr(response, "render"):
response.render()
offline_svg = strip_svg_hyperlinks(response.content) offline_svg = strip_svg_hyperlinks(response.content)
filename = f"{rack.name}-{face}-offline.svg".replace(" ", "_") filename = f"{rack.name}-{face}-offline.svg".replace(" ", "_")
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "netbox-utilities" 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" description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6/4.7"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"