From 2a7459c460760c4e676f5c6d091e2e82dddcd795 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 12 Aug 2026 11:30:43 +0200 Subject: [PATCH] fix: expose rack width fields on device edit form --- README.md | 9 +- netbox_utilities/__init__.py | 2 +- netbox_utilities/rack_width.py | 113 +++++++++++----------- netbox_utilities/tests/test_rack_width.py | 7 ++ pyproject.toml | 2 +- 5 files changed, 69 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 5aa5eaf..b440a5f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # NetBox Utilities -Plugin für **NetBox 4.6.5** mit neun Funktionen: +Plugin für **NetBox 4.6.5 bis 4.6.7** mit neun Funktionen: - Jeder Benutzer kann die Menüs der linken Navigation verschieben oder ausblenden. - Ein Dropdown in der Kopfleiste setzt einen sitzungsweiten Filter für einen Mandanten oder eine Mandantengruppe. @@ -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.0" + "git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.8.1" ``` Alternativ kann hinter dem `@` die vollständige Commit-ID stehen. @@ -135,8 +135,9 @@ empfohlen. ### Mehrere Geräte nebeneinander in derselben HE -Im normalen NetBox-Geräteformular stehen direkt nach **Rackseite** zwei neue -optionale Felder zur Verfügung: +Auf der normalen **Bearbeitungsseite eines Geräts** stehen direkt nach +**Rackseite** zwei neue optionale Felder zur Verfügung. Dafür wird kein +separates Plugin-Menü benötigt: - **Rackbreite**: volle, halbe, Drittel- oder Viertelbreite; - **Breitenposition**: Position 1 bis 4, von links gezählt. diff --git a/netbox_utilities/__init__.py b/netbox_utilities/__init__.py index 5e2dbfa..1fc09eb 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.0" +__version__ = "0.8.1" class NetBoxUtilitiesConfig(PluginConfig): diff --git a/netbox_utilities/rack_width.py b/netbox_utilities/rack_width.py index 7240909..235c295 100644 --- a/netbox_utilities/rack_width.py +++ b/netbox_utilities/rack_width.py @@ -306,84 +306,81 @@ def _install_device_validation(): def _install_device_form(): - from dcim.forms import DeviceForm + from dcim.views import DeviceEditView - if getattr(DeviceForm, "_netbox_utilities_rack_width_installed", False): + if getattr(DeviceEditView, "_netbox_utilities_rack_width_installed", False): return - original_init = DeviceForm.__init__ - original_clean = DeviceForm.clean - original_save = DeviceForm.save + base_device_form = DeviceEditView.form - @wraps(original_init) - def width_aware_init(form, *args, **kwargs): - original_init(form, *args, **kwargs) - width, horizontal_position = _stored_width_position(form.instance) - form.fields["utilities_rack_width"] = forms.ChoiceField( + class RackWidthDeviceForm(base_device_form): + utilities_rack_width = forms.ChoiceField( label="Rackbreite", choices=WIDTH_CHOICES, required=False, - initial=width, + initial=FULL_WIDTH, help_text="Optional: Geräte können sich eine HE nebeneinander teilen.", ) - form.fields["utilities_horizontal_position"] = forms.ChoiceField( + utilities_horizontal_position = forms.ChoiceField( label="Breitenposition", choices=POSITION_CHOICES, required=False, - initial=horizontal_position, + initial=1, help_text="Position von links; bei halber Breite sind Position 1 und 2 möglich.", ) - if not form.is_bound: - form.initial["utilities_rack_width"] = width - form.initial["utilities_horizontal_position"] = horizontal_position - reordered = {} - for name, field in form.fields.items(): - if name in {"utilities_rack_width", "utilities_horizontal_position"}: - continue - reordered[name] = field - if name == "face": - reordered["utilities_rack_width"] = form.fields["utilities_rack_width"] - reordered["utilities_horizontal_position"] = form.fields["utilities_horizontal_position"] - form.fields = reordered + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + width, horizontal_position = _stored_width_position(self.instance) + if not self.is_bound: + self.initial["utilities_rack_width"] = width + self.initial["utilities_horizontal_position"] = horizontal_position - @wraps(original_clean) - def width_aware_form_clean(form): - cleaned_data = original_clean(form) - try: - width, horizontal_position = normalize_width_position( - cleaned_data.get("utilities_rack_width"), - cleaned_data.get("utilities_horizontal_position"), - ) - except ValidationError as error: - form.add_error("utilities_horizontal_position", error) + reordered = {} + for name, field in self.fields.items(): + if name in {"utilities_rack_width", "utilities_horizontal_position"}: + continue + reordered[name] = field + if name == "face": + reordered["utilities_rack_width"] = self.fields["utilities_rack_width"] + reordered["utilities_horizontal_position"] = self.fields["utilities_horizontal_position"] + self.fields = reordered + + def clean(self): + cleaned_data = super().clean() + try: + width, horizontal_position = normalize_width_position( + cleaned_data.get("utilities_rack_width"), + cleaned_data.get("utilities_horizontal_position"), + ) + except ValidationError as error: + self.add_error("utilities_horizontal_position", error) + return cleaned_data + if not cleaned_data.get("rack") or not cleaned_data.get("position"): + width, horizontal_position = FULL_WIDTH, 1 + cleaned_data["utilities_rack_width"] = width + cleaned_data["utilities_horizontal_position"] = horizontal_position + stage_width_position(self.instance, width, horizontal_position) return cleaned_data - if not cleaned_data.get("rack") or not cleaned_data.get("position"): - width, horizontal_position = FULL_WIDTH, 1 - cleaned_data["utilities_rack_width"] = width - cleaned_data["utilities_horizontal_position"] = horizontal_position - stage_width_position(form.instance, width, horizontal_position) - return cleaned_data - @wraps(original_save) - def width_aware_form_save(form, commit=True): - device = original_save(form, commit=commit) - if not commit: + def save(self, commit=True): + device = super().save(commit=commit) + if not commit: + return device + width, horizontal_position = get_width_position(device) + if width == FULL_WIDTH or not device.rack_id or not device.position: + DeviceRackPlacement.objects.filter(device=device).delete() + else: + DeviceRackPlacement.objects.update_or_create( + device=device, + defaults={"width": width, "horizontal_position": horizontal_position}, + ) return device - width, horizontal_position = get_width_position(device) - if width == FULL_WIDTH or not device.rack_id or not device.position: - DeviceRackPlacement.objects.filter(device=device).delete() - else: - DeviceRackPlacement.objects.update_or_create( - device=device, - defaults={"width": width, "horizontal_position": horizontal_position}, - ) - return device - DeviceForm.__init__ = width_aware_init - DeviceForm.clean = width_aware_form_clean - DeviceForm.save = width_aware_form_save - DeviceForm._netbox_utilities_rack_width_installed = True + RackWidthDeviceForm.__module__ = __name__ + RackWidthDeviceForm.__qualname__ = "RackWidthDeviceForm" + DeviceEditView.form = RackWidthDeviceForm + DeviceEditView._netbox_utilities_rack_width_installed = True def _install_rack_svg(): diff --git a/netbox_utilities/tests/test_rack_width.py b/netbox_utilities/tests/test_rack_width.py index ecfb14f..cc9d022 100644 --- a/netbox_utilities/tests/test_rack_width.py +++ b/netbox_utilities/tests/test_rack_width.py @@ -18,6 +18,13 @@ from netbox_utilities.rack_width import ( class RackWidthTest(SimpleTestCase): + def test_device_edit_view_uses_explicit_rack_width_form(self): + from dcim.views import DeviceEditView + + self.assertEqual(DeviceEditView.form.__name__, "RackWidthDeviceForm") + self.assertIn("utilities_rack_width", DeviceEditView.form.base_fields) + self.assertIn("utilities_horizontal_position", DeviceEditView.form.base_fields) + def test_normalizes_full_width_to_first_position(self): self.assertEqual(normalize_width_position(1, 4), (1, 1)) diff --git a/pyproject.toml b/pyproject.toml index 56e829d..51543cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "netbox-utilities" -version = "0.8.0" +version = "0.8.1" description = "Navigation, tenant utilities, partial-width rack devices, bulk uploads, and rack reordering for NetBox 4.6" readme = "README.md" requires-python = ">=3.12"