fix: render rack width fields in grouped device forms

This commit is contained in:
2026-08-12 13:19:58 +02:00
parent 37fc55449f
commit 008a69ed79
5 changed files with 93 additions and 8 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ Release-Tag oder ein bestimmter Commit verwendet werden:
```bash ```bash
/opt/netbox/venv/bin/pip install --upgrade --force-reinstall \ /opt/netbox/venv/bin/pip install --upgrade --force-reinstall \
"git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.8.2" "git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.8.3"
``` ```
Alternativ kann hinter dem `@` die vollständige Commit-ID stehen. Alternativ kann hinter dem `@` die vollständige Commit-ID stehen.
+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.8.2" __version__ = "0.8.3"
class NetBoxUtilitiesConfig(PluginConfig): class NetBoxUtilitiesConfig(PluginConfig):
+41
View File
@@ -1,5 +1,6 @@
import decimal import decimal
from contextvars import ContextVar from contextvars import ContextVar
from copy import deepcopy
from fractions import Fraction from fractions import Fraction
from functools import wraps from functools import wraps
@@ -26,6 +27,8 @@ CORE_RACK_POSITION_CONSTRAINT = "dcim_device_unique_rack_position_face"
_validated_device = ContextVar("netbox_utilities_width_validated_device", default=None) _validated_device = ContextVar("netbox_utilities_width_validated_device", default=None)
RACK_WIDTH_FORM_FIELDS = ("utilities_rack_width", "utilities_horizontal_position")
def normalize_width_position(width, horizontal_position): def normalize_width_position(width, horizontal_position):
try: try:
@@ -114,6 +117,40 @@ def stage_width_position(device, width, horizontal_position):
return width, horizontal_position return width, horizontal_position
def include_rack_width_in_fieldsets(fieldsets):
"""Return an isolated fieldset layout which renders both rack width fields."""
if not fieldsets:
return fieldsets
copied_fieldsets = deepcopy(fieldsets)
def insert_after_position(group):
items = list(getattr(group, "items", ()))
if "position" in items:
index = items.index("position") + 1
for field_name in reversed(RACK_WIDTH_FORM_FIELDS):
if field_name not in items:
items.insert(index, field_name)
group.items = tuple(items)
return True
for item in items:
nested_groups = getattr(item, "groups", ())
if any(insert_after_position(nested_group) for nested_group in nested_groups):
return True
return False
if any(insert_after_position(fieldset) for fieldset in copied_fieldsets):
return tuple(copied_fieldsets)
from utilities.forms.rendering import FieldSet
return (
*copied_fieldsets,
FieldSet(*RACK_WIDTH_FORM_FIELDS, name="Rackbreite im Rack"),
)
def _vertical_interval(device, position=None): def _vertical_interval(device, position=None):
start = decimal.Decimal(position if position is not None else device.position) start = decimal.Decimal(position if position is not None else device.position)
height = decimal.Decimal(str(device.device_type.u_height)) height = decimal.Decimal(str(device.device_type.u_height))
@@ -330,6 +367,10 @@ def _install_device_form():
help_text="Position von links; bei halber Breite sind Position 1 und 2 möglich.", help_text="Position von links; bei halber Breite sind Position 1 und 2 möglich.",
) )
@property
def fieldsets(self):
return include_rack_width_in_fieldsets(super().fieldsets)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
width, horizontal_position = _stored_width_position(self.instance) width, horizontal_position = _stored_width_position(self.instance)
+49 -5
View File
@@ -11,6 +11,7 @@ from netbox_utilities.rack_width import (
_cleanup_unracked_placement, _cleanup_unracked_placement,
available_units_for_device, available_units_for_device,
horizontal_interval, horizontal_interval,
include_rack_width_in_fieldsets,
intervals_cover_full_width, intervals_cover_full_width,
intervals_overlap, intervals_overlap,
normalize_width_position, normalize_width_position,
@@ -20,6 +21,34 @@ from netbox_utilities.rack_width import (
class RackWidthTest(SimpleTestCase): class RackWidthTest(SimpleTestCase):
def test_adds_rack_width_fields_to_existing_location_fieldset(self):
from utilities.forms.rendering import FieldSet
original = (FieldSet("site", "rack", "position", "latitude", name="Location"),)
extended = include_rack_width_in_fieldsets(original)
self.assertEqual(
extended[0].items,
(
"site",
"rack",
"position",
"utilities_rack_width",
"utilities_horizontal_position",
"latitude",
),
)
self.assertEqual(original[0].items, ("site", "rack", "position", "latitude"))
def test_appends_visible_fallback_fieldset_when_position_is_missing(self):
from utilities.forms.rendering import FieldSet
extended = include_rack_width_in_fieldsets((FieldSet("name", name="Device"),))
self.assertEqual(extended[-1].name, "Rackbreite im Rack")
self.assertEqual(extended[-1].items, ("utilities_rack_width", "utilities_horizontal_position"))
def test_device_edit_view_uses_explicit_rack_width_form(self): def test_device_edit_view_uses_explicit_rack_width_form(self):
from netbox.registry import registry from netbox.registry import registry
@@ -32,6 +61,7 @@ class RackWidthTest(SimpleTestCase):
def test_resolved_device_edit_page_renders_rack_width_fields(self): def test_resolved_device_edit_page_renders_rack_width_fields(self):
from dcim.models import Device from dcim.models import Device
from utilities.forms.rendering import FieldSet
object_type = SimpleNamespace(pk=1, model_class=lambda: Device) object_type = SimpleNamespace(pk=1, model_class=lambda: Device)
with ( with (
@@ -48,17 +78,31 @@ class RackWidthTest(SimpleTestCase):
): ):
url = reverse("dcim:device_edit", kwargs={"pk": 1}) url = reverse("dcim:device_edit", kwargs={"pk": 1})
view_class = resolve(url).func.view_class view_class = resolve(url).func.view_class
form = view_class.form() base_form = view_class.form.__mro__[1]
html = render_to_string("htmx/form.html", {"form": form}) with patch.object(
base_form,
"fieldsets",
(FieldSet("site", "rack", "position", "face", "latitude", name="Location"),),
):
form = view_class.form()
rendered_fieldsets = form.fieldsets
html = render_to_string("htmx/form.html", {"form": form})
width = 'name="utilities_rack_width"' width = 'name="utilities_rack_width"'
horizontal_position = 'name="utilities_horizontal_position"' horizontal_position = 'name="utilities_horizontal_position"'
self.assertIn(width, html) self.assertIn(width, html)
self.assertIn(horizontal_position, html) self.assertIn(horizontal_position, html)
field_names = list(form.fields)
self.assertEqual( self.assertEqual(
field_names[field_names.index("position") : field_names.index("position") + 4], rendered_fieldsets[0].items,
["position", "utilities_rack_width", "utilities_horizontal_position", "face"], (
"site",
"rack",
"position",
"utilities_rack_width",
"utilities_horizontal_position",
"face",
"latitude",
),
) )
def test_normalizes_full_width_to_first_position(self): def test_normalizes_full_width_to_first_position(self):
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "netbox-utilities" name = "netbox-utilities"
version = "0.8.2" version = "0.8.3"
description = "Navigation, tenant utilities, partial-width rack devices, bulk uploads, and rack reordering for NetBox 4.6" description = "Navigation, tenant utilities, partial-width rack devices, bulk uploads, and rack reordering for NetBox 4.6"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"