fix: render rack width fields in grouped device forms
This commit is contained in:
@@ -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,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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user