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
@@ -28,6 +28,8 @@ class OptionalRackIntegrationHeadTest(SimpleTestCase):
"labels": True,
"devices": [],
"status": "ready",
"schema_version": 3,
"complete": True,
}
with (
patch("netbox_utilities.template_content.get_reorder_rack_width_data", return_value=data),
@@ -40,7 +42,7 @@ class OptionalRackIntegrationHeadTest(SimpleTestCase):
self.assertIn('"status": "ready"', html)
def test_topology_adapter_is_emitted_for_an_empty_enabled_payload(self):
data = {"devices": [], "status": "ready"}
data = {"devices": [], "status": "ready", "schema_version": 3, "complete": True}
with (
patch("netbox_utilities.template_content.get_reorder_rack_width_data", return_value=None),
patch("netbox_utilities.template_content.get_topology_rack_width_data", return_value=data),
+60
View File
@@ -17,6 +17,7 @@ from netbox_utilities.rack_width import (
_install_rack_methods,
_rack_elevation_devices,
available_units_for_device,
effective_width_positions,
horizontal_interval,
include_rack_width_in_fieldsets,
intervals_cover_full_width,
@@ -84,6 +85,7 @@ class RackWidthTest(SimpleTestCase):
regular = SimpleNamespace(
pk=1,
rack_id=3,
face="front",
position=12,
device_type=SimpleNamespace(u_height=1),
@@ -91,6 +93,7 @@ class RackWidthTest(SimpleTestCase):
)
partial_right = SimpleNamespace(
pk=2,
rack_id=3,
face="front",
position=11,
device_type=SimpleNamespace(u_height=1),
@@ -98,6 +101,7 @@ class RackWidthTest(SimpleTestCase):
)
partial_left = SimpleNamespace(
pk=3,
rack_id=3,
face="front",
position=11,
device_type=SimpleNamespace(u_height=1),
@@ -131,6 +135,62 @@ class RackWidthTest(SimpleTestCase):
],
)
def test_infers_two_half_widths_when_legacy_devices_share_one_unit(self):
devices = [
SimpleNamespace(
pk=21,
rack_id=3,
face="front",
position=11,
device_type=SimpleNamespace(u_height=1),
netbox_utilities_rack_placement=None,
),
SimpleNamespace(
pk=22,
rack_id=3,
face="front",
position=11,
device_type=SimpleNamespace(u_height=1),
netbox_utilities_rack_placement=None,
),
]
self.assertEqual(
effective_width_positions(devices),
{
21: (2, 1, "inferred"),
22: (2, 2, "inferred"),
},
)
def test_infers_missing_position_next_to_stored_half_width(self):
devices = [
SimpleNamespace(
pk=31,
rack_id=3,
face="front",
position=11,
device_type=SimpleNamespace(u_height=1),
netbox_utilities_rack_placement=SimpleNamespace(width=2, horizontal_position=2),
),
SimpleNamespace(
pk=32,
rack_id=3,
face="front",
position=11,
device_type=SimpleNamespace(u_height=1),
netbox_utilities_rack_placement=None,
),
]
self.assertEqual(
effective_width_positions(devices),
{
31: (2, 2, "stored"),
32: (2, 1, "inferred"),
},
)
def test_adds_rack_width_fields_to_existing_location_fieldset(self):
from utilities.forms.rendering import FieldSet
+93 -5
View File
@@ -97,8 +97,20 @@ class ApplyRackLayoutTest(SimpleTestCase):
placement_model,
):
events = []
first = MagicMock(pk=10, position=Decimal(1), face="front")
second = MagicMock(pk=11, position=Decimal(2), face="front")
first = MagicMock(
pk=10,
rack_id=5,
position=Decimal(1),
face="front",
device_type=SimpleNamespace(u_height=1),
)
second = MagicMock(
pk=11,
rack_id=5,
position=Decimal(2),
face="front",
device_type=SimpleNamespace(u_height=1),
)
first.snapshot.side_effect = lambda: events.append("snapshot-10")
second.snapshot.side_effect = lambda: events.append("snapshot-11")
first.full_clean.side_effect = lambda: events.append("validate-10")
@@ -126,7 +138,13 @@ class ApplyRackLayoutTest(SimpleTestCase):
@patch("netbox_utilities.reorder_rack.get_permission_for_model", return_value="dcim.change_device")
@patch("netbox_utilities.reorder_rack.Device")
def test_rejects_changed_device_without_permission(self, device_model, _get_permission, placement_model):
device = MagicMock(pk=10, position=Decimal(1), face="front")
device = MagicMock(
pk=10,
rack_id=5,
position=Decimal(1),
face="front",
device_type=SimpleNamespace(u_height=1),
)
device_model.objects.select_for_update.return_value.filter.return_value = [device]
placement_model.objects.select_for_update.return_value.filter.return_value = []
user = MagicMock()
@@ -148,7 +166,13 @@ class ApplyRackLayoutTest(SimpleTestCase):
_get_permission,
placement_model,
):
device = MagicMock(pk=10, position=Decimal(4), face="front")
device = MagicMock(
pk=10,
rack_id=5,
position=Decimal(4),
face="front",
device_type=SimpleNamespace(u_height=1),
)
stored = SimpleNamespace(device_id=10, width=2, horizontal_position=1)
device_model.objects.select_for_update.return_value.filter.return_value = [device]
placement_model.objects.select_for_update.return_value.filter.return_value = [stored]
@@ -178,7 +202,13 @@ class ApplyRackLayoutTest(SimpleTestCase):
_get_permission,
placement_model,
):
device = MagicMock(pk=10, position=Decimal(4), face="front")
device = MagicMock(
pk=10,
rack_id=5,
position=Decimal(4),
face="front",
device_type=SimpleNamespace(u_height=1),
)
stored = SimpleNamespace(device_id=10, width=2, horizontal_position=2)
device_model.objects.select_for_update.return_value.filter.return_value = [device]
placement_model.objects.select_for_update.return_value.filter.return_value = [stored]
@@ -194,6 +224,51 @@ class ApplyRackLayoutTest(SimpleTestCase):
defaults={"width": 2, "horizontal_position": 2},
)
@patch("netbox_utilities.reorder_rack.DeviceRackPlacement")
@patch("netbox_utilities.reorder_rack.get_permission_for_model", return_value="dcim.change_device")
@patch("netbox_utilities.reorder_rack.Device")
def test_persists_inferred_half_width_when_shared_device_is_moved(
self,
device_model,
_get_permission,
placement_model,
):
left = MagicMock(
pk=10,
rack_id=5,
position=Decimal(4),
face="front",
device_type=SimpleNamespace(u_height=1),
)
right = MagicMock(
pk=11,
rack_id=5,
position=Decimal(4),
face="front",
device_type=SimpleNamespace(u_height=1),
)
device_model.objects.select_for_update.return_value.filter.return_value = [left, right]
placement_model.objects.select_for_update.return_value.filter.return_value = []
user = MagicMock()
user.has_perm.return_value = True
placements = parse_device_placements(
rack_layout(
front=[
{"id": 10, "x": 0, "y": 4},
{"id": 11, "x": 6, "y": 5},
]
),
grid_columns=12,
)
changed = apply_rack_layout(rack=SimpleNamespace(pk=5), placements=placements, user=user)
self.assertEqual(changed, [right])
placement_model.objects.update_or_create.assert_called_once_with(
device=right,
defaults={"width": 2, "horizontal_position": 2},
)
class ReorderRackFrontendTest(SimpleTestCase):
def test_grid_y_matches_reorder_plugin_for_both_numbering_directions(self):
@@ -213,6 +288,9 @@ class ReorderRackFrontendTest(SimpleTestCase):
self.assertIn("closest('#saveButton')", script)
self.assertIn("X-NetBox-Utilities-Rack-Grid-Columns", script)
self.assertIn("if (face !== 'other')", script)
self.assertIn("data.schema_version !== 3", script)
self.assertIn("data.complete !== true", script)
self.assertIn("nativeMountedItems.length", script)
self.assertNotIn("partialDeviceIds", script)
self.assertNotIn("JSON.stringify", script)
@@ -229,6 +307,7 @@ class ReorderRackFrontendTest(SimpleTestCase):
self.assertEqual(result["status"], "native-fallback")
self.assertEqual(result["devices"], [])
self.assertFalse(result["complete"])
@patch("netbox_utilities.reorder_rack.reorder_rack_width_enabled", return_value=True)
@patch("netbox_utilities.reorder_rack.get_permission_for_model", return_value="dcim.change_device")
@@ -250,6 +329,7 @@ class ReorderRackFrontendTest(SimpleTestCase):
placement = SimpleNamespace(width=2, horizontal_position=2)
device = SimpleNamespace(
pk=10,
rack_id=5,
name="Fritzbox",
label="Fritzbox",
virtual_chassis=None,
@@ -266,6 +346,7 @@ class ReorderRackFrontendTest(SimpleTestCase):
)
adjacent_device = SimpleNamespace(
pk=12,
rack_id=5,
name="Router",
label="Router",
virtual_chassis=None,
@@ -282,6 +363,7 @@ class ReorderRackFrontendTest(SimpleTestCase):
)
full_width_device = SimpleNamespace(
pk=11,
rack_id=5,
name="Switch",
label="Switch",
virtual_chassis=None,
@@ -315,6 +397,8 @@ class ReorderRackFrontendTest(SimpleTestCase):
self.assertEqual(result["columns"], 12)
self.assertEqual(result["status"], "ready")
self.assertEqual(result["schema_version"], 3)
self.assertTrue(result["complete"])
self.assertEqual(len(result["devices"]), 3)
self.assertEqual(result["devices"][0]["label"], "Fritzbox")
self.assertFalse(result["images"])
@@ -336,6 +420,10 @@ class ReorderRackFrontendTest(SimpleTestCase):
{key: result["devices"][2][key] for key in ("id", "grid_x", "grid_width", "width")},
{"id": 11, "grid_x": 0, "grid_width": 12, "width": 1},
)
self.assertEqual(
[device["width_source"] for device in result["devices"]],
["stored", "stored", "default"],
)
class ReorderRackPatchTest(SimpleTestCase):
+37 -16
View File
@@ -43,7 +43,7 @@ class TopologyViewsRackWidthTest(SimpleTestCase):
def test_serializes_fractional_width_geometry_and_device_url(self):
placement = SimpleNamespace(
device_id=17,
device=SimpleNamespace(get_absolute_url=lambda: "/dcim/devices/17/"),
device=SimpleNamespace(pk=17, get_absolute_url=lambda: "/dcim/devices/17/"),
width=3,
horizontal_position=2,
)
@@ -57,12 +57,12 @@ class TopologyViewsRackWidthTest(SimpleTestCase):
"horizontal_position": 2,
"left_percent": 33.33333333,
"width_percent": 33.33333333,
"width_source": "stored",
},
)
@patch("netbox_utilities.topology_views.DeviceRackPlacement.objects")
@patch("netbox_utilities.topology_views.topology_rack_width_enabled", return_value=True)
def test_queries_only_permitted_devices_from_selected_racks(self, _enabled, placement_objects):
def test_queries_all_permitted_devices_and_infers_shared_unit_widths(self, _enabled):
self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation")
rack_queryset = MagicMock()
rack_queryset.filter.return_value = rack_queryset
@@ -71,19 +71,32 @@ class TopologyViewsRackWidthTest(SimpleTestCase):
rack_model.objects.restrict.return_value = rack_queryset
device_queryset = MagicMock()
device_queryset.filter.return_value = "permitted-device-query"
selected_devices = MagicMock()
device_queryset.filter.return_value = selected_devices
device_model = MagicMock()
device_model.objects.restrict.return_value = device_queryset
placement = SimpleNamespace(
device_id=17,
device=SimpleNamespace(get_absolute_url=lambda: "/dcim/devices/17/"),
width=2,
horizontal_position=2,
)
placement_queryset = MagicMock()
placement_queryset.select_related.return_value.order_by.return_value = [placement]
placement_objects.filter.return_value = placement_queryset
devices = [
SimpleNamespace(
pk=17,
rack_id=3,
face="front",
position=11,
device_type=SimpleNamespace(u_height=1),
netbox_utilities_rack_placement=None,
get_absolute_url=lambda: "/dcim/devices/17/",
),
SimpleNamespace(
pk=18,
rack_id=3,
face="front",
position=11,
device_type=SimpleNamespace(u_height=1),
netbox_utilities_rack_placement=None,
get_absolute_url=lambda: "/dcim/devices/18/",
),
]
selected_devices.select_related.return_value.order_by.return_value = devices
with patch.dict("sys.modules", {"dcim.models": SimpleNamespace(Device=device_model, Rack=rack_model)}):
result = get_topology_rack_width_data(self.request)
@@ -91,12 +104,17 @@ class TopologyViewsRackWidthTest(SimpleTestCase):
rack_queryset.filter.assert_called_once_with(pk__in=["3"])
device_queryset.filter.assert_called_once_with(
rack_id__in="selected-rack-query",
position__isnull=False,
position__gt=0,
device_type__u_height__gt=0,
)
placement_objects.filter.assert_called_once_with(device__in="permitted-device-query")
self.assertEqual(result["status"], "ready")
self.assertEqual(result["schema_version"], 3)
self.assertTrue(result["complete"])
self.assertEqual(len(result["devices"]), 2)
self.assertEqual(result["devices"][0]["width"], 2)
self.assertEqual(result["devices"][0]["horizontal_position"], 2)
self.assertEqual(result["devices"][0]["horizontal_position"], 1)
self.assertEqual(result["devices"][1]["horizontal_position"], 2)
self.assertEqual(result["devices"][0]["width_source"], "inferred")
def test_frontend_supports_live_view_and_all_rack_export_formats(self):
script = (Path(__file__).parents[1] / "static" / "netbox_utilities" / "topology-rack-width.js").read_text(
@@ -115,6 +133,8 @@ class TopologyViewsRackWidthTest(SimpleTestCase):
self.assertIn("liveViewCompatible", script)
self.assertIn("payload?.devices", script)
self.assertIn("payload.status !== 'ready'", script)
self.assertIn("payload?.schema_version !== 3", script)
self.assertIn("placement.source", script)
self.assertLess(script.index("liveViewCompatible"), script.index("netbox-utilities-partial-width"))
self.assertLess(script.index("exportGraphic(graphic"), script.index("event.preventDefault()"))
@@ -126,6 +146,7 @@ class TopologyViewsRackWidthTest(SimpleTestCase):
self.assertEqual(result["status"], "native-fallback")
self.assertEqual(result["devices"], [])
self.assertFalse(result["complete"])
def test_partial_width_css_uses_topology_device_slot_geometry(self):
stylesheet = (Path(__file__).parents[1] / "static" / "netbox_utilities" / "netbox_utilities.css").read_text(