소스 검색

Closes #20923: Migrate wireless app views to declarative UI layouts (#21646)

* #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

* Consolidate wireless link interface templates into ObjectPanel subclass

Replace duplicate wirelesslink_interface_{a,b}.html templates with a
single shared template and WirelessLinkInterfacePanel(ObjectPanel)
subclass that injects the correct interface via get_context().

* Rename WirelessLANAuthenticationPanel to WirelessAuthenticationPanel

Drop the 'LAN' qualifier since the panel is shared by both WirelessLAN
and WirelessLink views.

* Fix accessor shadowing in WirelessLinkInterfacePanel

Rename __init__ parameter from 'accessor' to 'interface_attr' to avoid
shadowing ObjectPanel.accessor, which would cause super().get_context()
to resolve the wrong context key.

* Use SimpleLayout for WirelessLinkView

Replace explicit Layout with SimpleLayout, which auto-includes plugin
content panels. Remove unused Row, Column, and PluginContentPanel
imports.
Jason Novinger 1 일 전
부모
커밋
8ccb0f7b63

+ 3 - 0
netbox/templates/wireless/attrs/auth_psk.html

@@ -0,0 +1,3 @@
+{% load i18n %}
+<span id="secret" class="font-monospace" data-secret="{{ value }}">{{ value }}</span>
+<button type="button" class="btn btn-primary toggle-secret float-end" data-bs-toggle="button">{% trans "Show Secret" %}</button>

+ 0 - 25
netbox/templates/wireless/inc/authentication_attrs.html

@@ -1,25 +0,0 @@
-{% load helpers %}
-{% load i18n %}
-
-<div class="card">
-  <h2 class="card-header">{% trans "Authentication" %}</h2>
-  <table class="table table-hover attr-table">
-    <tr>
-      <th scope="row">{% trans "Type" %}</th>
-      <td>{{ object.get_auth_type_display|placeholder }}</td>
-    </tr>
-    <tr>
-      <th scope="row">{% trans "Cipher" %}</th>
-      <td>{{ object.get_auth_cipher_display|placeholder }}</td>
-    </tr>
-    <tr>
-      <th scope="row">{% trans "PSK" %}</th>
-      <td>
-        <span id="secret" class="font-monospace" data-secret="{{ object.auth_psk }}">{{ object.auth_psk|placeholder }}</span>
-        {% if object.auth_psk %}
-        <button type="button" class="btn btn-primary toggle-secret float-end" data-bs-toggle="button">{% trans "Show Secret" %}</button>
-        {% endif %}
-      </td>
-    </tr>
-  </table>
-</div>

+ 0 - 51
netbox/templates/wireless/inc/wirelesslink_interface.html

@@ -1,51 +0,0 @@
-{% load helpers %}
-{% load i18n %}
-
-<table class="table table-hover attr-table">
-  <tr>
-    <th scope="row">{% trans "Device" %}</th>
-    <td>{{ interface.device|linkify }}</td>
-  </tr>
-  <tr>
-    <th scope="row">{% trans "Interface" %}</th>
-    <td>{{ interface|linkify }}</td>
-  </tr>
-  <tr>
-    <th scope="row">{% trans "Type" %}</th>
-    <td>
-      {{ interface.get_type_display }}
-    </td>
-  </tr>
-  <tr>
-    <th scope="row">{% trans "Role" %}</th>
-    <td>
-      {{ interface.get_rf_role_display|placeholder }}
-    </td>
-  </tr>
-  <tr>
-    <th scope="row">{% trans "Channel" %}</th>
-    <td>
-      {{ interface.get_rf_channel_display|placeholder }}
-    </td>
-  </tr>
-  <tr>
-      <th scope="row">{% trans "Channel Frequency" %}</th>
-      <td>
-        {% if interface.rf_channel_frequency %}
-          {{ interface.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" context "Abbreviation for megahertz" %}
-        {% else %}
-          {{ ''|placeholder }}
-        {% endif %}
-      </td>
-  </tr>
-  <tr>
-      <th scope="row">{% trans "Channel Width" %}</th>
-      <td>
-        {% if interface.rf_channel_width %}
-          {{ interface.rf_channel_width|floatformat:"-3" }} {% trans "MHz" context "Abbreviation for megahertz" %}
-        {% else %}
-          {{ ''|placeholder }}
-        {% endif %}
-      </td>
-  </tr>
-</table>

+ 48 - 0
netbox/templates/wireless/panels/wirelesslink_interface.html

@@ -0,0 +1,48 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers %}
+{% load i18n %}
+
+{% block panel_content %}
+  <table class="table table-hover attr-table">
+    <tr>
+      <th scope="row">{% trans "Device" %}</th>
+      <td>{{ interface.device|linkify }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Interface" %}</th>
+      <td>{{ interface|linkify }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Type" %}</th>
+      <td>{{ interface.get_type_display }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Role" %}</th>
+      <td>{{ interface.get_rf_role_display|placeholder }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Channel" %}</th>
+      <td>{{ interface.get_rf_channel_display|placeholder }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Channel Frequency" %}</th>
+      <td>
+        {% if interface.rf_channel_frequency %}
+          {{ interface.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" context "Abbreviation for megahertz" %}
+        {% else %}
+          {{ ''|placeholder }}
+        {% endif %}
+      </td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Channel Width" %}</th>
+      <td>
+        {% if interface.rf_channel_width %}
+          {{ interface.rf_channel_width|floatformat:"-3" }} {% trans "MHz" context "Abbreviation for megahertz" %}
+        {% else %}
+          {{ ''|placeholder }}
+        {% endif %}
+      </td>
+    </tr>
+  </table>
+{% endblock panel_content %}

+ 0 - 73
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 %}
-<div class="row">
-	<div class="col col-12 col-md-6">
-    <div class="card">
-      <h2 class="card-header">{% trans "Wireless LAN" %}</h2>
-      <table class="table table-hover attr-table">
-        <tr>
-          <th scope="row">{% trans "SSID" %}</th>
-          <td>{{ object.ssid }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Group" %}</th>
-          <td>{{ object.group|linkify|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Status" %}</th>
-          <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Scope" %}</th>
-          {% if object.scope %}
-            <td>{{ object.scope|linkify }} ({% trans object.scope_type.name %})</td>
-          {% else %}
-            <td>{{ ''|placeholder }}</td>
-          {% endif %}
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Description" %}</th>
-          <td>{{ object.description|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "VLAN" %}</th>
-          <td>{{ object.vlan|linkify|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Tenant" %}</th>
-          <td>
-            {% if object.tenant.group %}
-              {{ object.tenant.group|linkify }} /
-            {% endif %}
-            {{ object.tenant|linkify|placeholder }}
-          </td>
-        </tr>
-      </table>
-    </div>
-    {% include 'inc/panels/tags.html' %}
-    {% include 'inc/panels/comments.html' %}
-    {% plugin_left_page object %}
-  </div>
-  <div class="col col-12 col-md-6">
-    {% include 'wireless/inc/authentication_attrs.html' %}
-    {% include 'inc/panels/custom_fields.html' %}
-    {% plugin_right_page object %}
-	</div>
-</div>
-<div class="row">
-  <div class="col col-md-12">
-    <div class="card">
-      <h2 class="card-header">{% trans "Attached Interfaces" %}</h2>
-      <div class="card-body table-responsive">
-        {% render_table interfaces_table 'inc/table.html' %}
-        {% include 'inc/paginator.html' with paginator=interfaces_table.paginator page=interfaces_table.page %}
-      </div>
-    </div>
-    {% plugin_full_width_page object %}
-  </div>
-</div>
-{% endblock %}

+ 0 - 53
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 @@
     </a>
   {% endif %}
 {% endblock extra_controls %}
-
-{% block content %}
-<div class="row mb-3">
-	<div class="col col-12 col-md-6">
-    <div class="card">
-      <h2 class="card-header">{% trans "Wireless LAN Group" %}</h2>
-      <table class="table table-hover attr-table">
-        <tr>
-          <th scope="row">{% trans "Name" %}</th>
-          <td>{{ object.name }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Description" %}</th>
-          <td>{{ object.description|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Parent" %}</th>
-          <td>{{ object.parent|linkify|placeholder }}</td>
-        </tr>
-      </table>
-    </div>
-    {% include 'inc/panels/tags.html' %}
-    {% include 'inc/panels/comments.html' %}
-    {% plugin_left_page object %}
-  </div>
-	<div class="col col-12 col-md-6">
-      {% include 'inc/panels/related_objects.html' %}
-    {% include 'inc/panels/custom_fields.html' %}
-    {% plugin_right_page object %}
-	</div>
-</div>
-<div class="row mb-3">
-	<div class="col col-md-12">
-    <div class="card">
-      <h2 class="card-header">
-        {% trans "Child Groups" %}
-        {% if perms.wireless.add_wirelesslangroup %}
-          <div class="card-actions">
-            <a href="{% url 'wireless:wirelesslangroup_add' %}?parent={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
-              <span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Wireless LAN Group" %}
-            </a>
-          </div>
-        {% endif %}
-      </h2>
-      {% htmx_table 'wireless:wirelesslangroup_list' parent_id=object.pk %}
-    </div>
-    {% plugin_full_width_page object %}
-  </div>
-</div>
-{% endblock %}

+ 0 - 67
netbox/templates/wireless/wirelesslink.html

@@ -1,68 +1 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load i18n %}
-
-{% block content %}
-  <div class="row">
-    <div class="col col-12 col-md-6">
-      <div class="card">
-        <h2 class="card-header">{% trans "Interface" %} A</h2>
-        {% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_a %}
-      </div>
-      <div class="card">
-        <h2 class="card-header">{% trans "Link Properties" %}</h2>
-        <table class="table table-hover attr-table">
-          <tr>
-            <th scope="row">{% trans "Status" %}</th>
-            <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "SSID" %}</th>
-            <td>{{ object.ssid|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Tenant" %}</th>
-            <td>
-              {% if object.tenant.group %}
-                {{ object.tenant.group|linkify }} /
-              {% endif %}
-              {{ object.tenant|linkify|placeholder }}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Description" %}</th>
-            <td>{{ object.description|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Distance" %}</th>
-            <td>
-              {% if object.distance is not None %}
-                {{ object.distance|floatformat }} {{ object.get_distance_unit_display }}
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
-          </tr>
-        </table>
-      </div>
-      {% include 'inc/panels/tags.html' %}
-      {% include 'inc/panels/comments.html' %}
-      {% plugin_left_page object %}
-    </div>
-    <div class="col col-12 col-md-6">
-      <div class="card">
-        <h2 class="card-header">{% trans "Interface" %} B</h2>
-        {% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_b %}
-      </div>
-      {% include 'wireless/inc/authentication_attrs.html' %}
-      {% include 'inc/panels/custom_fields.html' %}
-      {% plugin_right_page object %}
-    </div>
-  </div>
-  <div class="row">
-    <div class="col col-md-12">
-      {% plugin_full_width_page object %}
-    </div>
-  </div>
-{% endblock %}

+ 0 - 0
netbox/wireless/ui/__init__.py


+ 51 - 0
netbox/wireless/ui/panels.py

@@ -0,0 +1,51 @@
+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 WirelessAuthenticationPanel(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 WirelessLinkInterfacePanel(panels.ObjectPanel):
+    template_name = 'wireless/panels/wirelesslink_interface.html'
+
+    def __init__(self, interface_attr, title, **kwargs):
+        super().__init__(**kwargs)
+        self.interface_attr = interface_attr
+        self.title = title
+
+    def get_context(self, context):
+        obj = context['object']
+        return {
+            **super().get_context(context),
+            'interface': getattr(obj, self.interface_attr),
+        }
+
+
+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')

+ 68 - 11
netbox/wireless/views.py

@@ -1,10 +1,20 @@
+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.panels import (
+    CommentsPanel,
+    ObjectsTablePanel,
+    RelatedObjectsPanel,
+)
 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 +38,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 +142,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.WirelessAuthenticationPanel(),
+            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 +217,19 @@ class WirelessLinkListView(generic.ObjectListView):
 @register_model_view(WirelessLink)
 class WirelessLinkView(generic.ObjectView):
     queryset = WirelessLink.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.WirelessLinkInterfacePanel('interface_a', title=_('Interface A')),
+            panels.WirelessLinkPropertiesPanel(),
+            TagsPanel(),
+            CommentsPanel(),
+        ],
+        right_panels=[
+            panels.WirelessLinkInterfacePanel('interface_b', title=_('Interface B')),
+            panels.WirelessAuthenticationPanel(),
+            CustomFieldsPanel(),
+        ],
+    )
 
 
 @register_model_view(WirelessLink, 'add', detail=False)