feat: integrate rack widths across plugins
This commit is contained in:
@@ -74,14 +74,15 @@ class RackWidthTest(SimpleTestCase):
|
||||
def test_device_edit_view_uses_explicit_rack_width_form(self):
|
||||
from netbox.registry import registry
|
||||
|
||||
edit_view = next(view["view"] for view in registry["views"]["dcim"]["device"] if view["name"] == "edit")
|
||||
for action in ("add", "edit"):
|
||||
device_view = next(view["view"] for view in registry["views"]["dcim"]["device"] if view["name"] == action)
|
||||
|
||||
self.assertEqual(edit_view.__name__, "RackWidthDeviceEditView")
|
||||
self.assertEqual(edit_view.form.__name__, "RackWidthDeviceForm")
|
||||
self.assertEqual(edit_view.template_name, "netbox_utilities/device_edit.html")
|
||||
self.assertEqual(edit_view.htmx_template_name, "netbox_utilities/device_edit_form.html")
|
||||
self.assertIn("utilities_rack_width", edit_view.form.base_fields)
|
||||
self.assertIn("utilities_horizontal_position", edit_view.form.base_fields)
|
||||
self.assertEqual(device_view.__name__, "RackWidthDeviceEditView")
|
||||
self.assertEqual(device_view.form.__name__, "RackWidthDeviceForm")
|
||||
self.assertEqual(device_view.template_name, "netbox_utilities/device_edit.html")
|
||||
self.assertEqual(device_view.htmx_template_name, "netbox_utilities/device_edit_form.html")
|
||||
self.assertIn("utilities_rack_width", device_view.form.base_fields)
|
||||
self.assertIn("utilities_horizontal_position", device_view.form.base_fields)
|
||||
|
||||
def test_resolved_device_edit_page_renders_rack_width_fields(self):
|
||||
from dcim.models import Device
|
||||
@@ -133,6 +134,48 @@ class RackWidthTest(SimpleTestCase):
|
||||
self.assertLess(html.index(width), html.index(horizontal_position))
|
||||
self.assertLess(html.index(horizontal_position), html.index(latitude))
|
||||
|
||||
def test_resolved_device_add_page_renders_rack_width_fields(self):
|
||||
from dcim.models import Device
|
||||
|
||||
object_type = SimpleNamespace(pk=1, model_class=lambda: Device)
|
||||
with (
|
||||
patch("core.models.ObjectType.objects.get_for_model", return_value=object_type),
|
||||
patch(
|
||||
"django.contrib.contenttypes.models.ContentType.objects.get_for_model",
|
||||
return_value=object_type,
|
||||
),
|
||||
patch("extras.models.CustomField.objects.get_for_model", return_value=[]),
|
||||
patch(
|
||||
"netbox_utilities.runtime._get_database_settings",
|
||||
return_value={"tenant_required": True},
|
||||
),
|
||||
):
|
||||
view_class = resolve(reverse("dcim:device_add")).func.view_class
|
||||
form = view_class.form()
|
||||
request = self._anonymous_request()
|
||||
context = {
|
||||
"form": form,
|
||||
"object": SimpleNamespace(
|
||||
pk=None,
|
||||
_meta=Device._meta,
|
||||
device_type=SimpleNamespace(is_child_device=False),
|
||||
parent_bay=None,
|
||||
),
|
||||
"return_url": "/dcim/devices/",
|
||||
"request": request,
|
||||
"settings": settings,
|
||||
"csrf_token": "",
|
||||
"perms": PermWrapper(request.user),
|
||||
}
|
||||
rendered_pages = (
|
||||
render_to_string(view_class.htmx_template_name, context),
|
||||
render_to_string(view_class.template_name, context),
|
||||
)
|
||||
|
||||
for html in rendered_pages:
|
||||
self.assertIn('name="utilities_rack_width"', html)
|
||||
self.assertIn('name="utilities_horizontal_position"', html)
|
||||
|
||||
def test_bound_device_form_clean_handles_netbox_clean_returning_none(self):
|
||||
from dcim.models import Device
|
||||
from netbox.registry import registry
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
from django.test import RequestFactory, SimpleTestCase
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from netbox_utilities.reorder_rack import (
|
||||
RackLayoutError,
|
||||
apply_rack_layout,
|
||||
bulk_reorder_update,
|
||||
get_reorder_rack_width_data,
|
||||
grid_dimensions,
|
||||
horizontal_position_from_grid_x,
|
||||
install_reorder_rack_bulk_save,
|
||||
parse_device_placements,
|
||||
reorder_grid_y,
|
||||
)
|
||||
|
||||
|
||||
@@ -32,14 +37,41 @@ class RackLayoutParsingTest(SimpleTestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[(item.device_id, item.position, item.face) for item in placements],
|
||||
[(item.device_id, item.position, item.face, item.grid_x) for item in placements],
|
||||
[
|
||||
(10, Decimal(4), "front"),
|
||||
(11, Decimal("8.5"), "front"),
|
||||
(12, None, ""),
|
||||
(10, Decimal(4), "front", None),
|
||||
(11, Decimal("8.5"), "front", None),
|
||||
(12, None, "", None),
|
||||
],
|
||||
)
|
||||
|
||||
def test_reads_horizontal_positions_only_from_marked_twelve_column_payload(self):
|
||||
placements = parse_device_placements(
|
||||
rack_layout(front=[{"id": 10, "x": 6, "y": 4}]),
|
||||
grid_columns=12,
|
||||
)
|
||||
|
||||
self.assertEqual(placements[0].grid_x, 6)
|
||||
|
||||
def test_maps_rack_widths_to_twelve_column_grid(self):
|
||||
self.assertEqual(grid_dimensions(1, 1), (12, 0))
|
||||
self.assertEqual(grid_dimensions(2, 2), (6, 6))
|
||||
self.assertEqual(grid_dimensions(3, 2), (4, 4))
|
||||
self.assertEqual(grid_dimensions(4, 4), (3, 9))
|
||||
|
||||
def test_maps_grid_x_back_to_horizontal_position(self):
|
||||
self.assertEqual(horizontal_position_from_grid_x(2, 6), 2)
|
||||
self.assertEqual(horizontal_position_from_grid_x(3, 8), 3)
|
||||
self.assertEqual(horizontal_position_from_grid_x(4, 9), 4)
|
||||
|
||||
def test_rejects_misaligned_grid_position(self):
|
||||
with self.assertRaisesMessage(RackLayoutError, "nicht zulässig"):
|
||||
horizontal_position_from_grid_x(2, 3)
|
||||
|
||||
def test_rejects_non_integer_grid_position(self):
|
||||
with self.assertRaisesMessage(RackLayoutError, "ganze Zahl"):
|
||||
horizontal_position_from_grid_x(2, 6.0)
|
||||
|
||||
def test_rejects_duplicate_devices(self):
|
||||
with self.assertRaisesMessage(RackLayoutError, "mehrfach"):
|
||||
parse_device_placements(
|
||||
@@ -55,9 +87,15 @@ class RackLayoutParsingTest(SimpleTestCase):
|
||||
|
||||
|
||||
class ApplyRackLayoutTest(SimpleTestCase):
|
||||
@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_clears_all_old_positions_before_saving_targets(self, device_model, _get_permission):
|
||||
def test_clears_all_old_positions_before_saving_targets(
|
||||
self,
|
||||
device_model,
|
||||
_get_permission,
|
||||
placement_model,
|
||||
):
|
||||
events = []
|
||||
first = MagicMock(pk=10, position=Decimal(1), face="front")
|
||||
second = MagicMock(pk=11, position=Decimal(2), face="front")
|
||||
@@ -70,6 +108,7 @@ class ApplyRackLayoutTest(SimpleTestCase):
|
||||
|
||||
device_model.objects.select_for_update.return_value.filter.return_value = [first, second]
|
||||
device_model.objects.filter.return_value.update.side_effect = lambda **_kwargs: events.append("clear")
|
||||
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, "y": 2}, {"id": 11, "y": 1}]))
|
||||
@@ -83,11 +122,13 @@ class ApplyRackLayoutTest(SimpleTestCase):
|
||||
self.assertEqual((first.position, first.face), (Decimal(2), "front"))
|
||||
self.assertEqual((second.position, second.face), (Decimal(1), "front"))
|
||||
|
||||
@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_rejects_changed_device_without_permission(self, device_model, _get_permission):
|
||||
def test_rejects_changed_device_without_permission(self, device_model, _get_permission, placement_model):
|
||||
device = MagicMock(pk=10, position=Decimal(1), face="front")
|
||||
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()
|
||||
user.has_perm.return_value = False
|
||||
placements = parse_device_placements(rack_layout(front=[{"id": 10, "y": 2}]))
|
||||
@@ -98,6 +139,147 @@ class ApplyRackLayoutTest(SimpleTestCase):
|
||||
device_model.objects.filter.assert_not_called()
|
||||
device.save.assert_not_called()
|
||||
|
||||
@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_saves_horizontal_only_move_for_partial_width_device(
|
||||
self,
|
||||
device_model,
|
||||
_get_permission,
|
||||
placement_model,
|
||||
):
|
||||
device = MagicMock(pk=10, position=Decimal(4), face="front")
|
||||
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]
|
||||
user = MagicMock()
|
||||
user.has_perm.return_value = True
|
||||
placements = parse_device_placements(
|
||||
rack_layout(front=[{"id": 10, "x": 6, "y": 4}]),
|
||||
grid_columns=12,
|
||||
)
|
||||
|
||||
changed = apply_rack_layout(rack=SimpleNamespace(pk=5), placements=placements, user=user)
|
||||
|
||||
self.assertEqual(changed, [device])
|
||||
self.assertEqual(device._netbox_utilities_rack_width, 2)
|
||||
self.assertEqual(device._netbox_utilities_horizontal_position, 2)
|
||||
placement_model.objects.update_or_create.assert_called_once_with(
|
||||
device=device,
|
||||
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_legacy_payload_preserves_stored_horizontal_position(
|
||||
self,
|
||||
device_model,
|
||||
_get_permission,
|
||||
placement_model,
|
||||
):
|
||||
device = MagicMock(pk=10, position=Decimal(4), face="front")
|
||||
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]
|
||||
user = MagicMock()
|
||||
user.has_perm.return_value = True
|
||||
placements = parse_device_placements(rack_layout(front=[{"id": 10, "x": 0, "y": 5}]))
|
||||
|
||||
changed = apply_rack_layout(rack=SimpleNamespace(pk=5), placements=placements, user=user)
|
||||
|
||||
self.assertEqual(changed, [device])
|
||||
placement_model.objects.update_or_create.assert_called_once_with(
|
||||
device=device,
|
||||
defaults={"width": 2, "horizontal_position": 2},
|
||||
)
|
||||
|
||||
|
||||
class ReorderRackFrontendTest(SimpleTestCase):
|
||||
def test_grid_y_matches_reorder_plugin_for_both_numbering_directions(self):
|
||||
self.assertEqual(reorder_grid_y(SimpleNamespace(u_height=42, desc_units=True), 4, 2), 6)
|
||||
self.assertEqual(reorder_grid_y(SimpleNamespace(u_height=42, desc_units=False), 4, 2), 74)
|
||||
|
||||
def test_width_adapter_runs_before_gridstack_and_preserves_original_save(self):
|
||||
script = (Path(__file__).parents[1] / "static" / "netbox_utilities" / "reorder-rack-width.js").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
self.assertIn("setAttribute(grid, 'gs-column', columns)", script)
|
||||
self.assertIn("setAttribute(item, 'gs-w', device.grid_width)", script)
|
||||
self.assertIn("setAttribute(item, 'gs-x', device.grid_x)", script)
|
||||
self.assertIn("grid.on('dragstop'", script)
|
||||
self.assertIn("grid.on('dropped'", script)
|
||||
self.assertIn("closest('#saveButton')", script)
|
||||
self.assertIn("X-NetBox-Utilities-Rack-Grid-Columns", script)
|
||||
self.assertNotIn("JSON.stringify", script)
|
||||
|
||||
@patch("netbox_utilities.reorder_rack.reorder_rack_width_enabled", return_value=True)
|
||||
@patch("netbox_utilities.reorder_rack.Rack")
|
||||
def test_malformed_partial_placement_fails_open(self, rack_model, _enabled):
|
||||
request = RequestFactory().get("/dcim/racks/5/reorder/")
|
||||
request.resolver_match = SimpleNamespace(kwargs={"pk": 5}, view_name="dcim:rack_reorder")
|
||||
request.user = MagicMock()
|
||||
rack_model.objects.restrict.return_value.filter.return_value.first.return_value = SimpleNamespace(pk=5)
|
||||
|
||||
with patch("netbox_utilities.reorder_rack.Device.objects.restrict", side_effect=AttributeError("stale")):
|
||||
self.assertIsNone(get_reorder_rack_width_data(request))
|
||||
|
||||
@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")
|
||||
@patch("netbox_utilities.reorder_rack.Device")
|
||||
@patch("netbox_utilities.reorder_rack.Rack")
|
||||
def test_describes_partial_devices_for_reorder_grid(
|
||||
self,
|
||||
rack_model,
|
||||
device_model,
|
||||
_get_permission,
|
||||
_enabled,
|
||||
):
|
||||
request = RequestFactory().get("/dcim/racks/5/reorder/?view=labels-only")
|
||||
request.resolver_match = SimpleNamespace(kwargs={"pk": 5}, view_name="dcim:rack_reorder")
|
||||
request.user = MagicMock()
|
||||
request.user.has_perm.return_value = True
|
||||
rack = SimpleNamespace(pk=5, u_height=42, desc_units=False)
|
||||
rack_model.objects.restrict.return_value.filter.return_value.first.return_value = rack
|
||||
placement = SimpleNamespace(width=2, horizontal_position=2)
|
||||
device = SimpleNamespace(
|
||||
pk=10,
|
||||
name="Fritzbox",
|
||||
virtual_chassis=None,
|
||||
device_type=SimpleNamespace(
|
||||
u_height=1,
|
||||
is_full_depth=False,
|
||||
front_image=None,
|
||||
rear_image=None,
|
||||
),
|
||||
role=SimpleNamespace(color="abcdef"),
|
||||
face="front",
|
||||
position=Decimal(4),
|
||||
netbox_utilities_rack_placement=placement,
|
||||
)
|
||||
device_queryset = MagicMock()
|
||||
device_queryset.filter.return_value.select_related.return_value.order_by.return_value = [device]
|
||||
device_model.objects.restrict.return_value = device_queryset
|
||||
|
||||
with (
|
||||
patch("dcim.svg.racks.get_device_name", return_value="Fritzbox"),
|
||||
patch(
|
||||
"netbox.config.get_config",
|
||||
return_value=SimpleNamespace(RACK_ELEVATION_DEFAULT_UNIT_WIDTH=220),
|
||||
),
|
||||
patch("utilities.html.foreground_color", return_value="000000"),
|
||||
):
|
||||
result = get_reorder_rack_width_data(request)
|
||||
|
||||
self.assertEqual(result["columns"], 12)
|
||||
self.assertFalse(result["images"])
|
||||
self.assertTrue(result["labels"])
|
||||
self.assertEqual(
|
||||
{key: result["devices"][0][key] for key in ("id", "grid_x", "grid_width", "width", "horizontal_position")},
|
||||
{"id": 10, "grid_x": 6, "grid_width": 6, "width": 2, "horizontal_position": 2},
|
||||
)
|
||||
|
||||
|
||||
class ReorderRackPatchTest(SimpleTestCase):
|
||||
def test_patches_supported_plugin_only_once(self):
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from django.test import RequestFactory, SimpleTestCase
|
||||
|
||||
from netbox_utilities.topology_views import (
|
||||
get_topology_rack_width_data,
|
||||
is_topology_rack_elevation_request,
|
||||
serialize_topology_placement,
|
||||
topology_rack_width_enabled,
|
||||
)
|
||||
|
||||
|
||||
class TopologyViewsRackWidthTest(SimpleTestCase):
|
||||
def setUp(self):
|
||||
self.request = RequestFactory().get("/plugins/netbox_topology_views/rack-elevation/?rack_id=3")
|
||||
self.request.user = MagicMock()
|
||||
|
||||
def test_detects_only_topology_rack_elevation_route(self):
|
||||
self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation")
|
||||
self.assertTrue(is_topology_rack_elevation_request(self.request))
|
||||
|
||||
self.request.resolver_match = SimpleNamespace(view_name="dcim:rack_list")
|
||||
self.assertFalse(is_topology_rack_elevation_request(self.request))
|
||||
|
||||
def test_skips_integration_when_optional_plugin_is_missing(self):
|
||||
self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation")
|
||||
with (
|
||||
patch("netbox_utilities.topology_views.apps.is_installed", return_value=False),
|
||||
patch("netbox_utilities.topology_views.get_plugin_config", return_value=True),
|
||||
):
|
||||
self.assertFalse(topology_rack_width_enabled(self.request))
|
||||
|
||||
def test_skips_integration_when_admin_disabled_it(self):
|
||||
self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation")
|
||||
with (
|
||||
patch("netbox_utilities.topology_views.apps.is_installed", return_value=True),
|
||||
patch("netbox_utilities.topology_views.get_plugin_config", return_value=False),
|
||||
):
|
||||
self.assertFalse(topology_rack_width_enabled(self.request))
|
||||
|
||||
def test_serializes_fractional_width_geometry_and_device_url(self):
|
||||
placement = SimpleNamespace(
|
||||
device_id=17,
|
||||
device=SimpleNamespace(get_absolute_url=lambda: "/dcim/devices/17/"),
|
||||
width=3,
|
||||
horizontal_position=2,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
serialize_topology_placement(placement),
|
||||
{
|
||||
"device_id": 17,
|
||||
"url": "/dcim/devices/17/",
|
||||
"width": 3,
|
||||
"horizontal_position": 2,
|
||||
"left_percent": 33.33333333,
|
||||
"width_percent": 33.33333333,
|
||||
},
|
||||
)
|
||||
|
||||
@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):
|
||||
self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation")
|
||||
rack_queryset = MagicMock()
|
||||
rack_queryset.filter.return_value = rack_queryset
|
||||
rack_queryset.values.return_value = "selected-rack-query"
|
||||
rack_model = MagicMock()
|
||||
rack_model.objects.restrict.return_value = rack_queryset
|
||||
|
||||
device_queryset = MagicMock()
|
||||
device_queryset.filter.return_value = "permitted-device-query"
|
||||
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
|
||||
|
||||
with patch.dict("sys.modules", {"dcim.models": SimpleNamespace(Device=device_model, Rack=rack_model)}):
|
||||
result = get_topology_rack_width_data(self.request)
|
||||
|
||||
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,
|
||||
)
|
||||
placement_objects.filter.assert_called_once_with(device__in="permitted-device-query")
|
||||
self.assertEqual(result[0]["width"], 2)
|
||||
self.assertEqual(result[0]["horizontal_position"], 2)
|
||||
|
||||
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(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
self.assertIn("netbox-utilities-partial-width", script)
|
||||
self.assertIn("horizontalGeometry", script)
|
||||
self.assertIn("buildRackSvg", script)
|
||||
self.assertIn("buildRackDrawio", script)
|
||||
self.assertIn("format === 'svg'", script)
|
||||
self.assertIn("format === 'drawio'", script)
|
||||
self.assertIn("canvas.toBlob", script)
|
||||
self.assertIn("event.stopImmediatePropagation()", script)
|
||||
self.assertIn("rackExportCompatible", script)
|
||||
self.assertIn("liveViewCompatible", script)
|
||||
self.assertLess(script.index("liveViewCompatible"), script.index("netbox-utilities-partial-width"))
|
||||
self.assertLess(script.index("exportGraphic(graphic"), script.index("event.preventDefault()"))
|
||||
|
||||
@patch("netbox_utilities.topology_views.topology_rack_width_enabled", return_value=True)
|
||||
def test_malformed_partial_placement_fails_open(self, _enabled):
|
||||
self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation")
|
||||
with patch("dcim.models.Rack.objects.restrict", side_effect=AttributeError("stale")):
|
||||
self.assertIsNone(get_topology_rack_width_data(self.request))
|
||||
|
||||
def test_partial_width_css_uses_topology_device_slot_geometry(self):
|
||||
stylesheet = (Path(__file__).parents[1] / "static" / "netbox_utilities" / "netbox_utilities.css").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
self.assertIn(".rack-device.netbox-utilities-partial-width", stylesheet)
|
||||
self.assertIn("--netbox-utilities-rack-device-left", stylesheet)
|
||||
self.assertIn("--netbox-utilities-rack-device-width", stylesheet)
|
||||
Reference in New Issue
Block a user