diff --git a/README.md b/README.md index c2fa2e6..ad9184b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Release-Tag oder ein bestimmter Commit verwendet werden: ```bash /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. diff --git a/netbox_utilities/__init__.py b/netbox_utilities/__init__.py index 4e29415..afc115a 100644 --- a/netbox_utilities/__init__.py +++ b/netbox_utilities/__init__.py @@ -1,6 +1,6 @@ from netbox.plugins import PluginConfig, get_plugin_config -__version__ = "0.8.2" +__version__ = "0.8.3" class NetBoxUtilitiesConfig(PluginConfig): diff --git a/netbox_utilities/rack_width.py b/netbox_utilities/rack_width.py index 96c8d74..ae1bfae 100644 --- a/netbox_utilities/rack_width.py +++ b/netbox_utilities/rack_width.py @@ -1,5 +1,6 @@ import decimal from contextvars import ContextVar +from copy import deepcopy from fractions import Fraction 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) +RACK_WIDTH_FORM_FIELDS = ("utilities_rack_width", "utilities_horizontal_position") + def normalize_width_position(width, horizontal_position): try: @@ -114,6 +117,40 @@ def stage_width_position(device, 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): start = decimal.Decimal(position if position is not None else device.position) 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.", ) + @property + def fieldsets(self): + return include_rack_width_in_fieldsets(super().fieldsets) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) width, horizontal_position = _stored_width_position(self.instance) diff --git a/netbox_utilities/tests/test_rack_width.py b/netbox_utilities/tests/test_rack_width.py index 7402164..bd2e240 100644 --- a/netbox_utilities/tests/test_rack_width.py +++ b/netbox_utilities/tests/test_rack_width.py @@ -11,6 +11,7 @@ from netbox_utilities.rack_width import ( _cleanup_unracked_placement, available_units_for_device, horizontal_interval, + include_rack_width_in_fieldsets, intervals_cover_full_width, intervals_overlap, normalize_width_position, @@ -20,6 +21,34 @@ from netbox_utilities.rack_width import ( 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): from netbox.registry import registry @@ -32,6 +61,7 @@ class RackWidthTest(SimpleTestCase): def test_resolved_device_edit_page_renders_rack_width_fields(self): from dcim.models import Device + from utilities.forms.rendering import FieldSet object_type = SimpleNamespace(pk=1, model_class=lambda: Device) with ( @@ -48,17 +78,31 @@ class RackWidthTest(SimpleTestCase): ): url = reverse("dcim:device_edit", kwargs={"pk": 1}) view_class = resolve(url).func.view_class - form = view_class.form() - html = render_to_string("htmx/form.html", {"form": form}) + base_form = view_class.form.__mro__[1] + 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"' horizontal_position = 'name="utilities_horizontal_position"' self.assertIn(width, html) self.assertIn(horizontal_position, html) - field_names = list(form.fields) self.assertEqual( - field_names[field_names.index("position") : field_names.index("position") + 4], - ["position", "utilities_rack_width", "utilities_horizontal_position", "face"], + rendered_fieldsets[0].items, + ( + "site", + "rack", + "position", + "utilities_rack_width", + "utilities_horizontal_position", + "face", + "latitude", + ), ) def test_normalizes_full_width_to_first_position(self): diff --git a/pyproject.toml b/pyproject.toml index c26f77f..6f8e586 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] 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" readme = "README.md" requires-python = ">=3.12"