fix: render rack width fields in device template
This commit is contained in:
@@ -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.3"
|
"git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.8.4"
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternativ kann hinter dem `@` die vollständige Commit-ID stehen.
|
Alternativ kann hinter dem `@` die vollständige Commit-ID stehen.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from netbox.plugins import PluginConfig, get_plugin_config
|
from netbox.plugins import PluginConfig, get_plugin_config
|
||||||
|
|
||||||
__version__ = "0.8.3"
|
__version__ = "0.8.4"
|
||||||
|
|
||||||
|
|
||||||
class NetBoxUtilitiesConfig(PluginConfig):
|
class NetBoxUtilitiesConfig(PluginConfig):
|
||||||
|
|||||||
@@ -424,6 +424,8 @@ def _install_device_form():
|
|||||||
|
|
||||||
class RackWidthDeviceEditView(DeviceEditView):
|
class RackWidthDeviceEditView(DeviceEditView):
|
||||||
form = RackWidthDeviceForm
|
form = RackWidthDeviceForm
|
||||||
|
template_name = "netbox_utilities/device_edit.html"
|
||||||
|
htmx_template_name = "netbox_utilities/device_edit_form.html"
|
||||||
|
|
||||||
RackWidthDeviceEditView.__module__ = __name__
|
RackWidthDeviceEditView.__module__ = __name__
|
||||||
RackWidthDeviceEditView.__qualname__ = "RackWidthDeviceEditView"
|
RackWidthDeviceEditView.__qualname__ = "RackWidthDeviceEditView"
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{% extends 'dcim/device_edit.html' %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
{% include 'netbox_utilities/device_edit_form.html' %}
|
||||||
|
{% endblock form %}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
{% load form_helpers %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% for field in form.hidden_fields %}
|
||||||
|
{{ field }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="col-9 offset-3">{% trans "Device" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% render_field form.name %}
|
||||||
|
{% render_field form.role %}
|
||||||
|
{% render_field form.description %}
|
||||||
|
{% render_field form.tags %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="col-9 offset-3">{% trans "Hardware" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% render_field form.device_type %}
|
||||||
|
{% render_field form.airflow %}
|
||||||
|
{% render_field form.serial %}
|
||||||
|
{% render_field form.asset_tag %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="col-9 offset-3">{% trans "Location" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% render_field form.site %}
|
||||||
|
{% render_field form.location %}
|
||||||
|
{% render_field form.rack %}
|
||||||
|
|
||||||
|
{% if object.device_type.is_child_device and object.parent_bay %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="id_parent_device" class="col-sm-3 col-form-label">{% trans "Parent Device" %}</label>
|
||||||
|
<div class="col">
|
||||||
|
<input id="id_parent_device" class="form-control" value="{{ object.parent_bay.device }}" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="id_parent_bay" class="col-sm-3 col-form-label">{% trans "Parent Bay" %}</label>
|
||||||
|
<div class="col">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="id_parent_bay" class="form-control" value="{{ object.parent_bay.name }}" disabled />
|
||||||
|
<a href="{% url 'dcim:devicebay_depopulate' pk=object.parent_bay.pk %}" title="{% trans "Remove" %}" aria-label="{% trans "Remove" %}" class="btn btn-danger d-inline-flex align-items-center">
|
||||||
|
<i class="mdi mdi-close-thick" aria-hidden="true"></i> {% trans "Remove" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% render_field form.face %}
|
||||||
|
{% render_field form.position %}
|
||||||
|
{% render_field form.utilities_rack_width %}
|
||||||
|
{% render_field form.utilities_horizontal_position %}
|
||||||
|
{% render_field form.latitude %}
|
||||||
|
{% render_field form.longitude %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="col-9 offset-3">{% trans "Management" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% render_field form.status %}
|
||||||
|
{% render_field form.platform %}
|
||||||
|
{% render_field form.config_template %}
|
||||||
|
{% if object.pk %}
|
||||||
|
{% render_field form.primary_ip4 %}
|
||||||
|
{% render_field form.primary_ip6 %}
|
||||||
|
{% render_field form.oob_ip %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="col-9 offset-3">{% trans "Virtualization" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% render_field form.cluster %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group my-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 my-5">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="col-9 offset-3">{% trans "Virtual Chassis" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% render_field form.virtual_chassis %}
|
||||||
|
{% render_field form.vc_position %}
|
||||||
|
{% render_field form.vc_priority %}
|
||||||
|
</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 my-5">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="col-9 offset-3">{% trans "Custom Fields" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% render_custom_fields form %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<h2 class="text-center">{% trans "Local Config Context Data" %}</h2>
|
||||||
|
{% render_field form.local_context_data %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group mb-5">
|
||||||
|
{% render_field form.comments %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 px-3 mb-3">
|
||||||
|
{% render_field form.changelog_message %}
|
||||||
|
</div>
|
||||||
@@ -2,9 +2,12 @@ from importlib import import_module
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.context_processors import PermWrapper
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.test import SimpleTestCase
|
from django.test import RequestFactory, SimpleTestCase
|
||||||
from django.urls import resolve, reverse
|
from django.urls import resolve, reverse
|
||||||
|
|
||||||
from netbox_utilities.rack_width import (
|
from netbox_utilities.rack_width import (
|
||||||
@@ -56,12 +59,13 @@ class RackWidthTest(SimpleTestCase):
|
|||||||
|
|
||||||
self.assertEqual(edit_view.__name__, "RackWidthDeviceEditView")
|
self.assertEqual(edit_view.__name__, "RackWidthDeviceEditView")
|
||||||
self.assertEqual(edit_view.form.__name__, "RackWidthDeviceForm")
|
self.assertEqual(edit_view.form.__name__, "RackWidthDeviceForm")
|
||||||
|
self.assertEqual(edit_view.template_name, "netbox_utilities/device_edit.html")
|
||||||
|
self.assertEqual(edit_view.htmx_template_name, "netbox_utilities/device_edit_form.html")
|
||||||
self.assertIn("utilities_rack_width", edit_view.form.base_fields)
|
self.assertIn("utilities_rack_width", edit_view.form.base_fields)
|
||||||
self.assertIn("utilities_horizontal_position", edit_view.form.base_fields)
|
self.assertIn("utilities_horizontal_position", edit_view.form.base_fields)
|
||||||
|
|
||||||
def test_resolved_device_edit_page_renders_rack_width_fields(self):
|
def test_resolved_device_edit_page_renders_rack_width_fields(self):
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from utilities.forms.rendering import FieldSet
|
|
||||||
|
|
||||||
object_type = SimpleNamespace(pk=1, model_class=lambda: Device)
|
object_type = SimpleNamespace(pk=1, model_class=lambda: Device)
|
||||||
with (
|
with (
|
||||||
@@ -78,32 +82,43 @@ class RackWidthTest(SimpleTestCase):
|
|||||||
):
|
):
|
||||||
url = reverse("dcim:device_edit", kwargs={"pk": 1})
|
url = reverse("dcim:device_edit", kwargs={"pk": 1})
|
||||||
view_class = resolve(url).func.view_class
|
view_class = resolve(url).func.view_class
|
||||||
base_form = view_class.form.__mro__[1]
|
|
||||||
with patch.object(
|
|
||||||
base_form,
|
|
||||||
"fieldsets",
|
|
||||||
(FieldSet("site", "rack", "position", "face", "latitude", name="Location"),),
|
|
||||||
):
|
|
||||||
form = view_class.form()
|
form = view_class.form()
|
||||||
rendered_fieldsets = form.fieldsets
|
request = self._anonymous_request()
|
||||||
html = render_to_string("htmx/form.html", {"form": form})
|
context = {
|
||||||
|
"form": form,
|
||||||
|
"object": SimpleNamespace(
|
||||||
|
pk=None,
|
||||||
|
_meta=Device._meta,
|
||||||
|
device_type=SimpleNamespace(is_child_device=False),
|
||||||
|
parent_bay=None,
|
||||||
|
),
|
||||||
|
"return_url": "/dcim/devices/",
|
||||||
|
"request": request,
|
||||||
|
"settings": settings,
|
||||||
|
"csrf_token": "",
|
||||||
|
"perms": PermWrapper(request.user),
|
||||||
|
}
|
||||||
|
rendered_pages = (
|
||||||
|
render_to_string(view_class.htmx_template_name, context),
|
||||||
|
render_to_string(view_class.template_name, context),
|
||||||
|
)
|
||||||
|
|
||||||
|
rack_position = 'name="position"'
|
||||||
width = 'name="utilities_rack_width"'
|
width = 'name="utilities_rack_width"'
|
||||||
horizontal_position = 'name="utilities_horizontal_position"'
|
horizontal_position = 'name="utilities_horizontal_position"'
|
||||||
|
latitude = 'name="latitude"'
|
||||||
|
for html in rendered_pages:
|
||||||
self.assertIn(width, html)
|
self.assertIn(width, html)
|
||||||
self.assertIn(horizontal_position, html)
|
self.assertIn(horizontal_position, html)
|
||||||
self.assertEqual(
|
self.assertLess(html.index(rack_position), html.index(width))
|
||||||
rendered_fieldsets[0].items,
|
self.assertLess(html.index(width), html.index(horizontal_position))
|
||||||
(
|
self.assertLess(html.index(horizontal_position), html.index(latitude))
|
||||||
"site",
|
|
||||||
"rack",
|
@staticmethod
|
||||||
"position",
|
def _anonymous_request():
|
||||||
"utilities_rack_width",
|
request = RequestFactory().get("/dcim/devices/1/edit/")
|
||||||
"utilities_horizontal_position",
|
request.user = AnonymousUser()
|
||||||
"face",
|
return request
|
||||||
"latitude",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "netbox-utilities"
|
name = "netbox-utilities"
|
||||||
version = "0.8.3"
|
version = "0.8.4"
|
||||||
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user