ui: place connection VLANs below cable B side

This commit is contained in:
2026-08-25 12:03:45 +02:00
parent 4421000409
commit d245ae0f79
7 changed files with 135 additions and 8 deletions
+4
View File
@@ -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`
+1 -1
View File
@@ -1,6 +1,6 @@
from netbox.plugins import PluginConfig, get_plugin_config
__version__ = "0.10.1"
__version__ = "0.10.2"
class NetBoxUtilitiesConfig(PluginConfig):
@@ -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 #}
<div class="field-group mb-5">
<div class="row">
<h2 class="col-9 offset-3">{% trans "A Side" %}</h2>
</div>
{% 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 %}
</div>
{# B side termination and connection VLANs #}
<div class="field-group mb-5">
<div class="row">
<h2 class="col-9 offset-3">{% trans "B Side" %}</h2>
</div>
{% 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 %}
</div>
{# Cable attributes #}
<div class="field-group mb-5">
<div class="row">
<h2 class="col-9 offset-3">{% trans "Cable" %}</h2>
</div>
{% 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 %}
<div class="row mb-3{% if form.length.errors or form.length_unit.errors %} has-errors{% endif %}">
<label class="col-sm-3 col-form-label text-lg-end">{{ form.length.label }}</label>
<div class="col-md-5">
{% render_field_with_aria form.length %}
{% if form.length.errors %}
<div class="form-text text-danger" id="{{ form.length.auto_id }}_errors" role="alert">
{% for error in form.length.errors %}{{ error }}{% if not forloop.last %}<br />{% endif %}{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-4">
{% render_field_with_aria form.length_unit %}
{% if form.length_unit.errors %}
<div class="form-text text-danger" id="{{ form.length_unit.auto_id }}_errors" role="alert">
{% for error in form.length_unit.errors %}{{ error }}{% if not forloop.last %}<br />{% endif %}{% endfor %}
</div>
{% endif %}
</div>
</div>
{% render_field form.tags %}
</div>
<div class="field-group mb-5">
<div class="row">
<h2 class="col-9 offset-3">{% trans "Tenancy" %}</h2>
</div>
{% render_field form.tenant_group %}
{% render_field form.tenant %}
</div>
<div class="field-group mb-5">
<div class="row">
<h2 class="col-9 offset-3">{% trans "Ownership" %}</h2>
</div>
{% render_field form.owner_group %}
{% render_field form.owner %}
</div>
{% if form.custom_fields %}
<div class="field-group mb-5">
<div class="row">
<h2 class="col-9 offset-3">VLANs der Verbindung</h2>
<h2 class="col-9 offset-3">{% trans "Custom Fields" %}</h2>
</div>
{% render_field form.utilities_vlans %}
{% render_custom_fields form %}
</div>
{% endif %}
<div class="field-group mb-5">
<h2 class="text-center">{% trans "Comments" %}</h2>
{% render_field form.comments %}
</div>
{# Meta fields #}
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 px-3 mb-3">
{% render_field form.changelog_message %}
</div>
@@ -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"
+1 -1
View File
@@ -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)
@@ -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)
+1 -1
View File
@@ -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"