diff --git a/README.md b/README.md
index ad9184b..9f2940d 100644
--- a/README.md
+++ b/README.md
@@ -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.3"
+ "git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.8.4"
```
Alternativ kann hinter dem `@` die vollständige Commit-ID stehen.
diff --git a/netbox_utilities/__init__.py b/netbox_utilities/__init__.py
index afc115a..e7e108e 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.8.3"
+__version__ = "0.8.4"
class NetBoxUtilitiesConfig(PluginConfig):
diff --git a/netbox_utilities/rack_width.py b/netbox_utilities/rack_width.py
index ae1bfae..80bc123 100644
--- a/netbox_utilities/rack_width.py
+++ b/netbox_utilities/rack_width.py
@@ -424,6 +424,8 @@ def _install_device_form():
class RackWidthDeviceEditView(DeviceEditView):
form = RackWidthDeviceForm
+ template_name = "netbox_utilities/device_edit.html"
+ htmx_template_name = "netbox_utilities/device_edit_form.html"
RackWidthDeviceEditView.__module__ = __name__
RackWidthDeviceEditView.__qualname__ = "RackWidthDeviceEditView"
diff --git a/netbox_utilities/templates/netbox_utilities/device_edit.html b/netbox_utilities/templates/netbox_utilities/device_edit.html
new file mode 100644
index 0000000..40fa740
--- /dev/null
+++ b/netbox_utilities/templates/netbox_utilities/device_edit.html
@@ -0,0 +1,5 @@
+{% extends 'dcim/device_edit.html' %}
+
+{% block form %}
+ {% include 'netbox_utilities/device_edit_form.html' %}
+{% endblock form %}
diff --git a/netbox_utilities/templates/netbox_utilities/device_edit_form.html b/netbox_utilities/templates/netbox_utilities/device_edit_form.html
new file mode 100644
index 0000000..8dd82dd
--- /dev/null
+++ b/netbox_utilities/templates/netbox_utilities/device_edit_form.html
@@ -0,0 +1,130 @@
+{% load form_helpers %}
+{% load i18n %}
+
+{% for field in form.hidden_fields %}
+ {{ field }}
+{% endfor %}
+
+
+
+
{% trans "Device" %}
+
+ {% render_field form.name %}
+ {% render_field form.role %}
+ {% render_field form.description %}
+ {% render_field form.tags %}
+
+
+
+
+
{% trans "Hardware" %}
+
+ {% render_field form.device_type %}
+ {% render_field form.airflow %}
+ {% render_field form.serial %}
+ {% render_field form.asset_tag %}
+
+
+
+
+
{% trans "Location" %}
+
+ {% render_field form.site %}
+ {% render_field form.location %}
+ {% render_field form.rack %}
+
+ {% if object.device_type.is_child_device and object.parent_bay %}
+
+
+
+
+
+
+
+
+
+
+ {% 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 %}
+
+
+
+
+
{% trans "Management" %}
+
+ {% 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 %}
+
+
+
+
+
{% trans "Virtualization" %}
+
+ {% render_field form.cluster %}
+
+
+
+
+
{% trans "Tenancy" %}
+
+ {% render_field form.tenant_group %}
+ {% render_field form.tenant %}
+
+
+
+
+
{% trans "Virtual Chassis" %}
+
+ {% render_field form.virtual_chassis %}
+ {% render_field form.vc_position %}
+ {% render_field form.vc_priority %}
+
+
+
+
+
{% trans "Ownership" %}
+
+ {% render_field form.owner_group %}
+ {% render_field form.owner %}
+
+
+{% if form.custom_fields %}
+
+
+
{% trans "Custom Fields" %}
+
+ {% render_custom_fields form %}
+
+{% endif %}
+
+
+
{% trans "Local Config Context Data" %}
+ {% render_field form.local_context_data %}
+
+
+
+ {% render_field form.comments %}
+
+
+
+ {% render_field form.changelog_message %}
+
diff --git a/netbox_utilities/tests/test_rack_width.py b/netbox_utilities/tests/test_rack_width.py
index bd2e240..ad8e64a 100644
--- a/netbox_utilities/tests/test_rack_width.py
+++ b/netbox_utilities/tests/test_rack_width.py
@@ -2,9 +2,12 @@ from importlib import import_module
from types import SimpleNamespace
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.template.loader import render_to_string
-from django.test import SimpleTestCase
+from django.test import RequestFactory, SimpleTestCase
from django.urls import resolve, reverse
from netbox_utilities.rack_width import (
@@ -56,12 +59,13 @@ class RackWidthTest(SimpleTestCase):
self.assertEqual(edit_view.__name__, "RackWidthDeviceEditView")
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_horizontal_position", edit_view.form.base_fields)
def test_resolved_device_edit_page_renders_rack_width_fields(self):
from dcim.models import Device
- from utilities.forms.rendering import FieldSet
object_type = SimpleNamespace(pk=1, model_class=lambda: Device)
with (
@@ -78,32 +82,43 @@ class RackWidthTest(SimpleTestCase):
):
url = reverse("dcim:device_edit", kwargs={"pk": 1})
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()
- rendered_fieldsets = form.fieldsets
- html = render_to_string("htmx/form.html", {"form": form})
+ form = view_class.form()
+ request = self._anonymous_request()
+ 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"'
horizontal_position = 'name="utilities_horizontal_position"'
- self.assertIn(width, html)
- self.assertIn(horizontal_position, html)
- self.assertEqual(
- rendered_fieldsets[0].items,
- (
- "site",
- "rack",
- "position",
- "utilities_rack_width",
- "utilities_horizontal_position",
- "face",
- "latitude",
- ),
- )
+ latitude = 'name="latitude"'
+ for html in rendered_pages:
+ self.assertIn(width, html)
+ self.assertIn(horizontal_position, html)
+ self.assertLess(html.index(rack_position), html.index(width))
+ self.assertLess(html.index(width), html.index(horizontal_position))
+ self.assertLess(html.index(horizontal_position), html.index(latitude))
+
+ @staticmethod
+ def _anonymous_request():
+ request = RequestFactory().get("/dcim/devices/1/edit/")
+ request.user = AnonymousUser()
+ return request
def test_normalizes_full_width_to_first_position(self):
self.assertEqual(normalize_width_position(1, 4), (1, 1))
diff --git a/pyproject.toml b/pyproject.toml
index 6f8e586..66cc23c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
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"
readme = "README.md"
requires-python = ">=3.12"