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 # 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. - 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. - 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 ```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.0" "git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.8.1"
``` ```
Alternativ kann hinter dem `@` die vollständige Commit-ID stehen. Alternativ kann hinter dem `@` die vollständige Commit-ID stehen.
@@ -135,8 +135,9 @@ empfohlen.
### Mehrere Geräte nebeneinander in derselben HE ### Mehrere Geräte nebeneinander in derselben HE
Im normalen NetBox-Geräteformular stehen direkt nach **Rackseite** zwei neue Auf der normalen **Bearbeitungsseite eines Geräts** stehen direkt nach
optionale Felder zur Verfügung: **Rackseite** zwei neue optionale Felder zur Verfügung. Dafür wird kein
separates Plugin-Menü benötigt:
- **Rackbreite**: volle, halbe, Drittel- oder Viertelbreite; - **Rackbreite**: volle, halbe, Drittel- oder Viertelbreite;
- **Breitenposition**: Position 1 bis 4, von links gezählt. - **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 from netbox.plugins import PluginConfig, get_plugin_config
__version__ = "0.8.0" __version__ = "0.8.1"
class NetBoxUtilitiesConfig(PluginConfig): class NetBoxUtilitiesConfig(PluginConfig):
+55 -58
View File
@@ -306,84 +306,81 @@ def _install_device_validation():
def _install_device_form(): 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 return
original_init = DeviceForm.__init__ base_device_form = DeviceEditView.form
original_clean = DeviceForm.clean
original_save = DeviceForm.save
@wraps(original_init) class RackWidthDeviceForm(base_device_form):
def width_aware_init(form, *args, **kwargs): utilities_rack_width = forms.ChoiceField(
original_init(form, *args, **kwargs)
width, horizontal_position = _stored_width_position(form.instance)
form.fields["utilities_rack_width"] = forms.ChoiceField(
label="Rackbreite", label="Rackbreite",
choices=WIDTH_CHOICES, choices=WIDTH_CHOICES,
required=False, required=False,
initial=width, initial=FULL_WIDTH,
help_text="Optional: Geräte können sich eine HE nebeneinander teilen.", 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", label="Breitenposition",
choices=POSITION_CHOICES, choices=POSITION_CHOICES,
required=False, required=False,
initial=horizontal_position, initial=1,
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.",
) )
if not form.is_bound:
form.initial["utilities_rack_width"] = width
form.initial["utilities_horizontal_position"] = horizontal_position
reordered = {} def __init__(self, *args, **kwargs):
for name, field in form.fields.items(): super().__init__(*args, **kwargs)
if name in {"utilities_rack_width", "utilities_horizontal_position"}: width, horizontal_position = _stored_width_position(self.instance)
continue if not self.is_bound:
reordered[name] = field self.initial["utilities_rack_width"] = width
if name == "face": self.initial["utilities_horizontal_position"] = horizontal_position
reordered["utilities_rack_width"] = form.fields["utilities_rack_width"]
reordered["utilities_horizontal_position"] = form.fields["utilities_horizontal_position"]
form.fields = reordered
@wraps(original_clean) reordered = {}
def width_aware_form_clean(form): for name, field in self.fields.items():
cleaned_data = original_clean(form) if name in {"utilities_rack_width", "utilities_horizontal_position"}:
try: continue
width, horizontal_position = normalize_width_position( reordered[name] = field
cleaned_data.get("utilities_rack_width"), if name == "face":
cleaned_data.get("utilities_horizontal_position"), reordered["utilities_rack_width"] = self.fields["utilities_rack_width"]
) reordered["utilities_horizontal_position"] = self.fields["utilities_horizontal_position"]
except ValidationError as error: self.fields = reordered
form.add_error("utilities_horizontal_position", error)
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 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 save(self, commit=True):
def width_aware_form_save(form, commit=True): device = super().save(commit=commit)
device = original_save(form, commit=commit) if not 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 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 RackWidthDeviceForm.__module__ = __name__
DeviceForm.clean = width_aware_form_clean RackWidthDeviceForm.__qualname__ = "RackWidthDeviceForm"
DeviceForm.save = width_aware_form_save DeviceEditView.form = RackWidthDeviceForm
DeviceForm._netbox_utilities_rack_width_installed = True DeviceEditView._netbox_utilities_rack_width_installed = True
def _install_rack_svg(): def _install_rack_svg():
@@ -18,6 +18,13 @@ from netbox_utilities.rack_width import (
class RackWidthTest(SimpleTestCase): 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): def test_normalizes_full_width_to_first_position(self):
self.assertEqual(normalize_width_position(1, 4), (1, 1)) self.assertEqual(normalize_width_position(1, 4), (1, 1))
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "netbox-utilities" 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" 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"