From d245ae0f797f7d0b2e89fd228c0f16e160ac1db1 Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 25 Aug 2026 12:03:45 +0200 Subject: [PATCH] ui: place connection VLANs below cable B side --- README.md | 4 + netbox_utilities/__init__.py | 2 +- .../netbox_utilities/cable_edit_form.html | 116 +++++++++++++++++- .../tests/test_connection_vlans.py | 15 +++ netbox_utilities/tests/test_reorder_rack.py | 2 +- netbox_utilities/tests/test_topology_views.py | 2 +- pyproject.toml | 2 +- 7 files changed, 135 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 554d1a0..fa68de0 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ mehrwertige Reverse-Relationen. Das behebt unter NetBox 4.6.8 insbesondere den Fehler `'RelatedManager' object has no attribute '_meta'` beim Öffnen der Seite zum Anlegen einer VLAN-Gruppe. +Ab Version `0.10.2` befindet sich die VLAN-Auswahl beim Anlegen und Bearbeiten +eines Kabels direkt im Abschnitt **B-Seite**, unterhalb des B-seitigen +Verbindungsendes. + ## Kompatibilität - NetBox `>=4.6.5,<4.7` diff --git a/netbox_utilities/__init__.py b/netbox_utilities/__init__.py index 73551f7..305cd9e 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.10.1" +__version__ = "0.10.2" class NetBoxUtilitiesConfig(PluginConfig): diff --git a/netbox_utilities/templates/netbox_utilities/cable_edit_form.html b/netbox_utilities/templates/netbox_utilities/cable_edit_form.html index ce1af16..e28b983 100644 --- a/netbox_utilities/templates/netbox_utilities/cable_edit_form.html +++ b/netbox_utilities/templates/netbox_utilities/cable_edit_form.html @@ -1,12 +1,120 @@ +{% load static %} +{% load helpers %} {% load form_helpers %} +{% load i18n %} -{% include 'dcim/htmx/cable_edit.html' %} +{% for field in form.hidden_fields %} + {{ field }} +{% endfor %} -{% if form.utilities_vlans %} +{# A side termination #} +
+
+

{% trans "A Side" %}

+
+ {% render_field form.a_terminations_type %} + {% if 'termination_a_device' in form.fields %} + {% render_field form.termination_a_device %} + {% endif %} + {% if 'termination_a_powerpanel' in form.fields %} + {% render_field form.termination_a_powerpanel %} + {% endif %} + {% if 'termination_a_circuit' in form.fields %} + {% render_field form.termination_a_circuit %} + {% endif %} + {% if 'a_terminations' in form.fields %} + {% render_field form.a_terminations %} + {% endif %} +
+ +{# B side termination and connection VLANs #} +
+
+

{% trans "B Side" %}

+
+ {% render_field form.b_terminations_type %} + {% if 'termination_b_device' in form.fields %} + {% render_field form.termination_b_device %} + {% endif %} + {% if 'termination_b_powerpanel' in form.fields %} + {% render_field form.termination_b_powerpanel %} + {% endif %} + {% if 'termination_b_circuit' in form.fields %} + {% render_field form.termination_b_circuit %} + {% endif %} + {% if 'b_terminations' in form.fields %} + {% render_field form.b_terminations %} + {% endif %} + {% if form.utilities_vlans %} + {% render_field form.utilities_vlans %} + {% endif %} +
+ +{# Cable attributes #} +
+
+

{% trans "Cable" %}

+
+ {% render_field form.status %} + {% render_field form.profile %} + {% render_field form.type %} + {% render_field form.bundle %} + {% render_field form.label %} + {% render_field form.description %} + {% render_field form.color %} +
+ +
+ {% render_field_with_aria form.length %} + {% if form.length.errors %} + + {% endif %} +
+
+ {% render_field_with_aria form.length_unit %} + {% if form.length_unit.errors %} + + {% endif %} +
+
+ {% render_field form.tags %} +
+ +
+
+

{% trans "Tenancy" %}

+
+ {% render_field form.tenant_group %} + {% render_field form.tenant %} +
+ +
+
+

{% trans "Ownership" %}

+
+ {% render_field form.owner_group %} + {% render_field form.owner %} +
+ +{% if form.custom_fields %}
-

VLANs der Verbindung

+

{% trans "Custom Fields" %}

- {% render_field form.utilities_vlans %} + {% render_custom_fields form %}
{% endif %} + +
+

{% trans "Comments" %}

+ {% render_field form.comments %} +
+ +{# Meta fields #} +
+ {% render_field form.changelog_message %} +
diff --git a/netbox_utilities/tests/test_connection_vlans.py b/netbox_utilities/tests/test_connection_vlans.py index e191547..f3cc37c 100644 --- a/netbox_utilities/tests/test_connection_vlans.py +++ b/netbox_utilities/tests/test_connection_vlans.py @@ -1,8 +1,10 @@ +from pathlib import Path from types import SimpleNamespace from unittest.mock import MagicMock, patch from django import forms from django.forms.utils import ErrorDict +from django.template.loader import get_template from django.test import SimpleTestCase from netbox_utilities.connection_vlans import ( @@ -14,6 +16,19 @@ from netbox_utilities.models import CableVLANAssignment, WirelessLinkVLANAssignm class ConnectionVLANTest(SimpleTestCase): + def test_cable_vlan_field_is_rendered_below_b_side(self): + self.assertIsNotNone(get_template("netbox_utilities/cable_edit_form.html")) + template_path = Path(__file__).parents[1] / "templates" / "netbox_utilities" / "cable_edit_form.html" + template = template_path.read_text(encoding="utf-8") + + b_side = template.index('{% trans "B Side" %}') + vlan_field = template.index("{% render_field form.utilities_vlans %}") + cable_section = template.index('{% trans "Cable" %}') + + self.assertLess(b_side, vlan_field) + self.assertLess(vlan_field, cable_section) + self.assertEqual(template.count("{% render_field form.utilities_vlans %}"), 1) + def test_assignment_models_are_tied_to_their_connection_and_vlans(self): self.assertEqual( CableVLANAssignment._meta.get_field("cable").remote_field.model._meta.label_lower, "dcim.cable" diff --git a/netbox_utilities/tests/test_reorder_rack.py b/netbox_utilities/tests/test_reorder_rack.py index e0b57c4..cf0d12e 100644 --- a/netbox_utilities/tests/test_reorder_rack.py +++ b/netbox_utilities/tests/test_reorder_rack.py @@ -449,7 +449,7 @@ class ReorderRackFrontendTest(SimpleTestCase): self.assertEqual(template_name, "netbox_utilities/reorder_rack.html") self.assertEqual(context["reorder_devices"][0]["label"], "LEO-Fritzbox") self.assertEqual(context["reorder_devices"][0]["grid_width"], 6) - self.assertEqual(context["asset_version"], "0.10.1") + self.assertEqual(context["asset_version"], "0.10.2") self.assertIs(context["reorder_rack_width_data"], get_width_data.return_value) get_width_data.assert_called_once() self.assertIs(get_width_data.call_args.kwargs["rack"], rack) diff --git a/netbox_utilities/tests/test_topology_views.py b/netbox_utilities/tests/test_topology_views.py index b0eace9..9614535 100644 --- a/netbox_utilities/tests/test_topology_views.py +++ b/netbox_utilities/tests/test_topology_views.py @@ -246,7 +246,7 @@ class TopologyViewsRackWidthTest(SimpleTestCase): self.assertIn('id="netbox-utilities-topology-rack-width-styles"', html) self.assertIn('.rack-device[href="/dcim/devices/334/"]', html) self.assertIn("left: calc(50% + 3px) !important", html) - self.assertIn("netbox_utilities/topology-rack-width.js?v=0.10.1", html) + self.assertIn("netbox_utilities/topology-rack-width.js?v=0.10.2", html) self.assertIn("left:calc(0% + 3px)!important", html) self.assertIn("left:calc(50% + 3px)!important", html) self.assertIn("width:calc(50% - 6px)!important", html) diff --git a/pyproject.toml b/pyproject.toml index 6a1c01d..813b957 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "netbox-utilities" -version = "0.10.1" +version = "0.10.2" description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6" readme = "README.md" requires-python = ">=3.12"