From 8663a242147850cd19d2858eb4b18f35cdab41cc Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Wed, 11 Mar 2026 12:24:55 -0500 Subject: [PATCH] #20923: Migrate wireless app views to declarative UI layouts Convert WirelessLANGroup, WirelessLAN, and WirelessLink detail views from legacy HTML templates to declarative Python layout definitions. New files: - wireless/ui/panels.py: Panel classes for all three model detail views - templates/wireless/attrs/auth_psk.html: Secret toggle for PSK field - templates/wireless/panels/wirelesslink_interface_{a,b}.html: Interface panels for WirelessLink detail view Removed: - templates/wireless/inc/authentication_attrs.html - templates/wireless/inc/wirelesslink_interface.html --- netbox/templates/wireless/attrs/auth_psk.html | 3 + .../wireless/inc/authentication_attrs.html | 25 ----- .../wireless/inc/wirelesslink_interface.html | 51 ----------- .../panels/wirelesslink_interface_a.html | 48 ++++++++++ .../panels/wirelesslink_interface_b.html | 48 ++++++++++ netbox/templates/wireless/wirelesslan.html | 73 --------------- .../templates/wireless/wirelesslangroup.html | 53 ----------- netbox/templates/wireless/wirelesslink.html | 67 -------------- netbox/wireless/ui/__init__.py | 0 netbox/wireless/ui/panels.py | 35 +++++++ netbox/wireless/views.py | 91 ++++++++++++++++--- 11 files changed, 214 insertions(+), 280 deletions(-) create mode 100644 netbox/templates/wireless/attrs/auth_psk.html delete mode 100644 netbox/templates/wireless/inc/authentication_attrs.html delete mode 100644 netbox/templates/wireless/inc/wirelesslink_interface.html create mode 100644 netbox/templates/wireless/panels/wirelesslink_interface_a.html create mode 100644 netbox/templates/wireless/panels/wirelesslink_interface_b.html create mode 100644 netbox/wireless/ui/__init__.py create mode 100644 netbox/wireless/ui/panels.py diff --git a/netbox/templates/wireless/attrs/auth_psk.html b/netbox/templates/wireless/attrs/auth_psk.html new file mode 100644 index 000000000..3401a3eb4 --- /dev/null +++ b/netbox/templates/wireless/attrs/auth_psk.html @@ -0,0 +1,3 @@ +{% load i18n %} +{{ value }} + diff --git a/netbox/templates/wireless/inc/authentication_attrs.html b/netbox/templates/wireless/inc/authentication_attrs.html deleted file mode 100644 index 555a14f8b..000000000 --- a/netbox/templates/wireless/inc/authentication_attrs.html +++ /dev/null @@ -1,25 +0,0 @@ -{% load helpers %} -{% load i18n %} - -
-

{% trans "Authentication" %}

- - - - - - - - - - - - - -
{% trans "Type" %}{{ object.get_auth_type_display|placeholder }}
{% trans "Cipher" %}{{ object.get_auth_cipher_display|placeholder }}
{% trans "PSK" %} - {{ object.auth_psk|placeholder }} - {% if object.auth_psk %} - - {% endif %} -
-
diff --git a/netbox/templates/wireless/inc/wirelesslink_interface.html b/netbox/templates/wireless/inc/wirelesslink_interface.html deleted file mode 100644 index 8b61a5564..000000000 --- a/netbox/templates/wireless/inc/wirelesslink_interface.html +++ /dev/null @@ -1,51 +0,0 @@ -{% load helpers %} -{% load i18n %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Device" %}{{ interface.device|linkify }}
{% trans "Interface" %}{{ interface|linkify }}
{% trans "Type" %} - {{ interface.get_type_display }} -
{% trans "Role" %} - {{ interface.get_rf_role_display|placeholder }} -
{% trans "Channel" %} - {{ interface.get_rf_channel_display|placeholder }} -
{% trans "Channel Frequency" %} - {% if interface.rf_channel_frequency %} - {{ interface.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" context "Abbreviation for megahertz" %} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "Channel Width" %} - {% if interface.rf_channel_width %} - {{ interface.rf_channel_width|floatformat:"-3" }} {% trans "MHz" context "Abbreviation for megahertz" %} - {% else %} - {{ ''|placeholder }} - {% endif %} -
diff --git a/netbox/templates/wireless/panels/wirelesslink_interface_a.html b/netbox/templates/wireless/panels/wirelesslink_interface_a.html new file mode 100644 index 000000000..80aaa2d04 --- /dev/null +++ b/netbox/templates/wireless/panels/wirelesslink_interface_a.html @@ -0,0 +1,48 @@ +{% load helpers %} +{% load i18n %} + +
+

{% trans "Interface" %} A

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "Device" %}{{ object.interface_a.device|linkify }}
{% trans "Interface" %}{{ object.interface_a|linkify }}
{% trans "Type" %}{{ object.interface_a.get_type_display }}
{% trans "Role" %}{{ object.interface_a.get_rf_role_display|placeholder }}
{% trans "Channel" %}{{ object.interface_a.get_rf_channel_display|placeholder }}
{% trans "Channel Frequency" %} + {% if object.interface_a.rf_channel_frequency %} + {{ object.interface_a.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" context "Abbreviation for megahertz" %} + {% else %} + {{ ''|placeholder }} + {% endif %} +
{% trans "Channel Width" %} + {% if object.interface_a.rf_channel_width %} + {{ object.interface_a.rf_channel_width|floatformat:"-3" }} {% trans "MHz" context "Abbreviation for megahertz" %} + {% else %} + {{ ''|placeholder }} + {% endif %} +
+
diff --git a/netbox/templates/wireless/panels/wirelesslink_interface_b.html b/netbox/templates/wireless/panels/wirelesslink_interface_b.html new file mode 100644 index 000000000..2538bfef1 --- /dev/null +++ b/netbox/templates/wireless/panels/wirelesslink_interface_b.html @@ -0,0 +1,48 @@ +{% load helpers %} +{% load i18n %} + +
+

{% trans "Interface" %} B

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "Device" %}{{ object.interface_b.device|linkify }}
{% trans "Interface" %}{{ object.interface_b|linkify }}
{% trans "Type" %}{{ object.interface_b.get_type_display }}
{% trans "Role" %}{{ object.interface_b.get_rf_role_display|placeholder }}
{% trans "Channel" %}{{ object.interface_b.get_rf_channel_display|placeholder }}
{% trans "Channel Frequency" %} + {% if object.interface_b.rf_channel_frequency %} + {{ object.interface_b.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" context "Abbreviation for megahertz" %} + {% else %} + {{ ''|placeholder }} + {% endif %} +
{% trans "Channel Width" %} + {% if object.interface_b.rf_channel_width %} + {{ object.interface_b.rf_channel_width|floatformat:"-3" }} {% trans "MHz" context "Abbreviation for megahertz" %} + {% else %} + {{ ''|placeholder }} + {% endif %} +
+
diff --git a/netbox/templates/wireless/wirelesslan.html b/netbox/templates/wireless/wirelesslan.html index e2aa1f94b..f15e1d050 100644 --- a/netbox/templates/wireless/wirelesslan.html +++ b/netbox/templates/wireless/wirelesslan.html @@ -1,74 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "Wireless LAN" %}

- - - - - - - - - - - - - - - - {% if object.scope %} - - {% else %} - - {% endif %} - - - - - - - - - - - - - -
{% trans "SSID" %}{{ object.ssid }}
{% trans "Group" %}{{ object.group|linkify|placeholder }}
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Scope" %}{{ object.scope|linkify }} ({% trans object.scope_type.name %}){{ ''|placeholder }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "VLAN" %}{{ object.vlan|linkify|placeholder }}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_left_page object %} -
-
- {% include 'wireless/inc/authentication_attrs.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% plugin_right_page object %} -
-
-
-
-
-

{% trans "Attached Interfaces" %}

-
- {% render_table interfaces_table 'inc/table.html' %} - {% include 'inc/paginator.html' with paginator=interfaces_table.paginator page=interfaces_table.page %} -
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/wireless/wirelesslangroup.html b/netbox/templates/wireless/wirelesslangroup.html index 5440ffe4f..26873c437 100644 --- a/netbox/templates/wireless/wirelesslangroup.html +++ b/netbox/templates/wireless/wirelesslangroup.html @@ -1,7 +1,4 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} {% load i18n %} {% block breadcrumbs %} @@ -18,53 +15,3 @@ {% endif %} {% endblock extra_controls %} - -{% block content %} -
-
-
-

{% trans "Wireless LAN Group" %}

- - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Parent" %}{{ object.parent|linkify|placeholder }}
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_left_page object %} -
-
- {% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% plugin_right_page object %} -
-
-
-
-
-

- {% trans "Child Groups" %} - {% if perms.wireless.add_wirelesslangroup %} - - {% endif %} -

- {% htmx_table 'wireless:wirelesslangroup_list' parent_id=object.pk %} -
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/wireless/wirelesslink.html b/netbox/templates/wireless/wirelesslink.html index c1a80aba0..f15e1d050 100644 --- a/netbox/templates/wireless/wirelesslink.html +++ b/netbox/templates/wireless/wirelesslink.html @@ -1,68 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "Interface" %} A

- {% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_a %} -
-
-

{% trans "Link Properties" %}

- - - - - - - - - - - - - - - - - - - - - -
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "SSID" %}{{ object.ssid|placeholder }}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Distance" %} - {% if object.distance is not None %} - {{ object.distance|floatformat }} {{ object.get_distance_unit_display }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_left_page object %} -
-
-
-

{% trans "Interface" %} B

- {% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_b %} -
- {% include 'wireless/inc/authentication_attrs.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/wireless/ui/__init__.py b/netbox/wireless/ui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/netbox/wireless/ui/panels.py b/netbox/wireless/ui/panels.py new file mode 100644 index 000000000..a0522853c --- /dev/null +++ b/netbox/wireless/ui/panels.py @@ -0,0 +1,35 @@ +from django.utils.translation import gettext_lazy as _ + +from netbox.ui import attrs, panels + + +class WirelessLANGroupPanel(panels.NestedGroupObjectPanel): + pass + + +class WirelessLANPanel(panels.ObjectAttributesPanel): + ssid = attrs.TextAttr('ssid', label=_('SSID')) + group = attrs.RelatedObjectAttr('group', linkify=True) + status = attrs.ChoiceAttr('status') + scope = attrs.GenericForeignKeyAttr('scope', linkify=True) + description = attrs.TextAttr('description') + vlan = attrs.RelatedObjectAttr('vlan', label=_('VLAN'), linkify=True) + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + + +class WirelessLANAuthenticationPanel(panels.ObjectAttributesPanel): + title = _('Authentication') + + auth_type = attrs.ChoiceAttr('auth_type', label=_('Type')) + auth_cipher = attrs.ChoiceAttr('auth_cipher', label=_('Cipher')) + auth_psk = attrs.TemplatedAttr('auth_psk', label=_('PSK'), template_name='wireless/attrs/auth_psk.html') + + +class WirelessLinkPropertiesPanel(panels.ObjectAttributesPanel): + title = _('Link Properties') + + status = attrs.ChoiceAttr('status') + ssid = attrs.TextAttr('ssid', label=_('SSID')) + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + description = attrs.TextAttr('description') + distance = attrs.NumericAttr('distance', unit_accessor='get_distance_unit_display') diff --git a/netbox/wireless/views.py b/netbox/wireless/views.py index 101b0553b..4bd79a329 100644 --- a/netbox/wireless/views.py +++ b/netbox/wireless/views.py @@ -1,10 +1,23 @@ +from django.utils.translation import gettext_lazy as _ + from dcim.models import Interface +from extras.ui.panels import CustomFieldsPanel, TagsPanel +from netbox.ui import actions, layout +from netbox.ui.layout import Column, Row +from netbox.ui.panels import ( + CommentsPanel, + ObjectsTablePanel, + PluginContentPanel, + RelatedObjectsPanel, + TemplatePanel, +) from netbox.views import generic from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, register_model_view from . import filtersets, forms, tables from .models import * +from .ui import panels # # Wireless LAN groups @@ -28,6 +41,33 @@ class WirelessLANGroupListView(generic.ObjectListView): @register_model_view(WirelessLANGroup) class WirelessLANGroupView(GetRelatedModelsMixin, generic.ObjectView): queryset = WirelessLANGroup.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.WirelessLANGroupPanel(), + TagsPanel(), + CommentsPanel(), + ], + right_panels=[ + RelatedObjectsPanel(), + CustomFieldsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + model='wireless.WirelessLANGroup', + title=_('Child Groups'), + filters={'parent_id': lambda ctx: ctx['object'].pk}, + actions=[ + actions.AddObject( + 'wireless.WirelessLANGroup', + label=_('Add Wireless LAN Group'), + url_params={ + 'parent': lambda ctx: ctx['object'].pk, + } + ), + ], + ), + ], + ) def get_extra_context(self, request, instance): groups = instance.get_descendants(include_self=True) @@ -105,17 +145,24 @@ class WirelessLANListView(generic.ObjectListView): @register_model_view(WirelessLAN) class WirelessLANView(generic.ObjectView): queryset = WirelessLAN.objects.all() - - def get_extra_context(self, request, instance): - attached_interfaces = Interface.objects.restrict(request.user, 'view').filter( - wireless_lans=instance - ) - interfaces_table = tables.WirelessLANInterfacesTable(attached_interfaces) - interfaces_table.configure(request) - - return { - 'interfaces_table': interfaces_table, - } + layout = layout.SimpleLayout( + left_panels=[ + panels.WirelessLANPanel(), + TagsPanel(), + CommentsPanel(), + ], + right_panels=[ + panels.WirelessLANAuthenticationPanel(), + CustomFieldsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + model='dcim.Interface', + title=_('Attached Interfaces'), + filters={'wireless_lan_id': lambda ctx: ctx['object'].pk}, + ), + ], + ) @register_model_view(WirelessLAN, 'add', detail=False) @@ -173,6 +220,28 @@ class WirelessLinkListView(generic.ObjectListView): @register_model_view(WirelessLink) class WirelessLinkView(generic.ObjectView): queryset = WirelessLink.objects.all() + layout = layout.Layout( + Row( + Column( + TemplatePanel('wireless/panels/wirelesslink_interface_a.html'), + panels.WirelessLinkPropertiesPanel(), + TagsPanel(), + CommentsPanel(), + PluginContentPanel('left_page'), + ), + Column( + TemplatePanel('wireless/panels/wirelesslink_interface_b.html'), + panels.WirelessLANAuthenticationPanel(), + CustomFieldsPanel(), + PluginContentPanel('right_page'), + ), + ), + Row( + Column( + PluginContentPanel('full_width_page'), + ), + ), + ) @register_model_view(WirelessLink, 'add', detail=False)