fix: expose rack width fields on device edit form

This commit is contained in:
2026-08-12 11:30:43 +02:00
parent a504341442
commit 2a7459c460
5 changed files with 69 additions and 64 deletions
+5 -4
View File
@@ -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.
+1 -1
View File
@@ -1,6 +1,6 @@
from netbox.plugins import PluginConfig, get_plugin_config
__version__ = "0.8.0"
__version__ = "0.8.1"
class NetBoxUtilitiesConfig(PluginConfig):
+55 -58
View File
@@ -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():
@@ -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))
+1 -1
View File
@@ -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"