Fixes #21012: Ensure all tagged VLANs assigned to an interface are listed under the interface detail UI view

This commit is contained in:
Jeremy Stretch
2026-03-05 16:35:31 -05:00
parent 93e01d5b07
commit 44abeeff5a
4 changed files with 12 additions and 80 deletions

View File

@@ -16,7 +16,7 @@ from circuits.models import Circuit, CircuitTermination
from extras.ui.panels import CustomFieldsPanel, ImageAttachmentsPanel, TagsPanel from extras.ui.panels import CustomFieldsPanel, ImageAttachmentsPanel, TagsPanel
from extras.views import ObjectConfigContextView, ObjectRenderConfigView from extras.views import ObjectConfigContextView, ObjectRenderConfigView
from ipam.models import ASN, VLAN, IPAddress, Prefix, VLANGroup from ipam.models import ASN, VLAN, IPAddress, Prefix, VLANGroup
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable from ipam.tables import VLANTranslationRuleTable
from netbox.object_actions import * from netbox.object_actions import *
from netbox.ui import actions, layout from netbox.ui import actions, layout
from netbox.ui.panels import ( from netbox.ui.panels import (
@@ -3230,21 +3230,6 @@ class InterfaceView(generic.ObjectView):
) )
lag_interfaces_table.configure(request) lag_interfaces_table.configure(request)
# Get assigned VLANs and annotate whether each is tagged or untagged
vlans = []
if instance.untagged_vlan is not None:
vlans.append(instance.untagged_vlan)
vlans[0].tagged = False
for vlan in instance.tagged_vlans.restrict(request.user).prefetch_related('site', 'group', 'tenant', 'role'):
vlan.tagged = True
vlans.append(vlan)
vlan_table = InterfaceVLANTable(
interface=instance,
data=vlans,
orderable=False
)
vlan_table.configure(request)
# Get VLAN translation rules # Get VLAN translation rules
vlan_translation_table = None vlan_translation_table = None
if instance.vlan_translation_policy: if instance.vlan_translation_policy:
@@ -3260,7 +3245,6 @@ class InterfaceView(generic.ObjectView):
'bridge_interfaces_table': bridge_interfaces_table, 'bridge_interfaces_table': bridge_interfaces_table,
'child_interfaces_table': child_interfaces_table, 'child_interfaces_table': child_interfaces_table,
'lag_interfaces_table': lag_interfaces_table, 'lag_interfaces_table': lag_interfaces_table,
'vlan_table': vlan_table,
'vlan_translation_table': vlan_translation_table, 'vlan_translation_table': vlan_translation_table,
} }

View File

@@ -1,19 +1,17 @@
import django_tables2 as tables import django_tables2 as tables
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_tables2.utils import Accessor
from dcim.models import Interface from dcim.models import Interface
from dcim.tables.template_code import INTERFACE_LINKTERMINATION, LINKTERMINATION from dcim.tables.template_code import INTERFACE_LINKTERMINATION, LINKTERMINATION
from ipam.models import * from ipam.models import *
from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
from tenancy.tables import TenancyColumnsMixin, TenantColumn from tenancy.tables import TenancyColumnsMixin
from virtualization.models import VMInterface from virtualization.models import VMInterface
from .template_code import * from .template_code import *
__all__ = ( __all__ = (
'InterfaceVLANTable',
'VLANDevicesTable', 'VLANDevicesTable',
'VLANGroupTable', 'VLANGroupTable',
'VLANMembersTable', 'VLANMembersTable',
@@ -198,47 +196,6 @@ class VLANVirtualMachinesTable(VLANMembersTable):
exclude = ('id', ) exclude = ('id', )
class InterfaceVLANTable(NetBoxTable):
"""
List VLANs assigned to a specific Interface.
"""
vid = tables.Column(
linkify=True,
verbose_name=_('VID')
)
tagged = columns.BooleanColumn(
verbose_name=_('Tagged'),
false_mark=None
)
site = tables.Column(
verbose_name=_('Site'),
linkify=True
)
group = tables.Column(
accessor=Accessor('group__name'),
verbose_name=_('Group')
)
tenant = TenantColumn(
verbose_name=_('Tenant'),
)
status = columns.ChoiceFieldColumn(
verbose_name=_('Status'),
)
role = tables.Column(
verbose_name=_('Role'),
linkify=True
)
class Meta(NetBoxTable.Meta):
model = VLAN
fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
exclude = ('id', )
def __init__(self, interface, *args, **kwargs):
self.interface = interface
super().__init__(*args, **kwargs)
# #
# VLAN Translation # VLAN Translation
# #

View File

@@ -411,7 +411,10 @@
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col col-md-12"> <div class="col col-md-12">
{% include 'inc/panel_table.html' with table=vlan_table heading="VLANs" %} <div class="card">
<h2 class="card-header">{% trans "VLANs" %}</h2>
{% htmx_table 'ipam:vlan_list' interface_id=object.pk %}
</div>
</div> </div>
</div> </div>
{% if object.is_lag %} {% if object.is_lag %}

View File

@@ -13,7 +13,7 @@ from dcim.tables import DeviceTable
from extras.ui.panels import CustomFieldsPanel, ImageAttachmentsPanel, TagsPanel from extras.ui.panels import CustomFieldsPanel, ImageAttachmentsPanel, TagsPanel
from extras.views import ObjectConfigContextView, ObjectRenderConfigView from extras.views import ObjectConfigContextView, ObjectRenderConfigView
from ipam.models import IPAddress, VLANGroup from ipam.models import IPAddress, VLANGroup
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable from ipam.tables import VLANTranslationRuleTable
from ipam.ui.panels import FHRPGroupAssignmentsPanel from ipam.ui.panels import FHRPGroupAssignmentsPanel
from netbox.object_actions import ( from netbox.object_actions import (
AddObject, AddObject,
@@ -594,7 +594,11 @@ class VMInterfaceView(generic.ObjectView):
), ),
], ],
), ),
ContextTablePanel('vlan_table', title=_('Assigned VLANs')), ObjectsTablePanel(
model='ipam.VLAN',
title=_('Assigned VLANs'),
filters={'vminterface_id': lambda ctx: ctx['object'].pk},
),
ContextTablePanel('vlan_translation_table', title=_('VLAN Translation')), ContextTablePanel('vlan_translation_table', title=_('VLAN Translation')),
ContextTablePanel('child_interfaces_table', title=_('Child Interfaces')), ContextTablePanel('child_interfaces_table', title=_('Child Interfaces')),
], ],
@@ -620,24 +624,8 @@ class VMInterfaceView(generic.ObjectView):
) )
vlan_translation_table.configure(request) vlan_translation_table.configure(request)
# Get assigned VLANs and annotate whether each is tagged or untagged
vlans = []
if instance.untagged_vlan is not None:
vlans.append(instance.untagged_vlan)
vlans[0].tagged = False
for vlan in instance.tagged_vlans.restrict(request.user).prefetch_related('site', 'group', 'tenant', 'role'):
vlan.tagged = True
vlans.append(vlan)
vlan_table = InterfaceVLANTable(
interface=instance,
data=vlans,
orderable=False
)
vlan_table.configure(request)
return { return {
'child_interfaces_table': child_interfaces_tables, 'child_interfaces_table': child_interfaces_tables,
'vlan_table': vlan_table,
'vlan_translation_table': vlan_translation_table, 'vlan_translation_table': vlan_translation_table,
} }