fix: restore populated rack and reorder views
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user