Fixes #18978 - Allow filtering of Interfaces in the GUI by 802.1Q Mode (#19183)

* feat(dcim): Add VLAN mode filter to CommonInterface

Introduces a new FilterSet for VLAN mode in CommonInterfaceFilterSet.
This allows filtering interfaces based on their VLAN mode using defined
choices.

* feat(dcim): Add VLAN mode filter to Interface FilterForm

Add a field to InterfaceFilterSet to filter interfaces by 802.1Q VLAN
mode.

* feat(virtualization): Add VLAN mode filter to VMInterface

Add a field to VMInterfaceFilterSet to filter interfaces by 802.1Q VLAN
mode.

* fix(dcim): Correct mode filter parameter type in tests

Updates the `mode` filter parameter to accept a list instead of a single
value in `test_filtersets.py`. Ensures proper count assertion for
accurate test behavior.

* feat(virtualization): Add tests for VLAN mode filtering

Introduces tests to validate filtering by `mode` for VMInterface.
Ensures correct filtering for 802.1Q VLAN mode.

* refactor(virtualization): Reorganize FieldSets in FilterSets

Splits the 'Attributes' FieldSet into two distinct FieldSets for better
clarity: 'Attributes' and 'Addressing'. This improves form organization
and makes it more intuitive for users.
This commit is contained in:
Martin Hauser 2025-04-15 19:47:51 +02:00 committed by GitHub
parent 70cc7c7563
commit 1f93471659
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 26 additions and 2 deletions

View File

@ -1689,6 +1689,10 @@ class MACAddressFilterSet(NetBoxModelFilterSet):
class CommonInterfaceFilterSet(django_filters.FilterSet):
mode = django_filters.MultipleChoiceFilter(
choices=InterfaceModeChoices,
label=_('802.1Q Mode')
)
vlan_id = django_filters.CharFilter(
method='filter_vlan_id',
label=_('Assigned VLAN')

View File

@ -1332,6 +1332,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
FieldSet('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only', name=_('Attributes')),
FieldSet('vrf_id', 'l2vpn_id', 'mac_address', 'wwn', name=_('Addressing')),
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
FieldSet('mode', name=_('802.1Q Switching')),
FieldSet('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', name=_('Wireless')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
@ -1403,6 +1404,11 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
required=False,
label=_('PoE type')
)
mode = forms.MultipleChoiceField(
choices=InterfaceModeChoices,
required=False,
label=_('802.1Q mode')
)
rf_role = forms.MultipleChoiceField(
choices=WirelessRoleChoices,
required=False,

View File

@ -4153,7 +4153,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_mode(self):
params = {'mode': InterfaceModeChoices.MODE_ACCESS}
params = {'mode': [InterfaceModeChoices.MODE_ACCESS]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_description(self):

View File

@ -1,6 +1,7 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from dcim.choices import *
from dcim.models import Device, DeviceRole, Location, Platform, Region, Site, SiteGroup
from extras.forms import LocalConfigContextFilterForm
from extras.models import ConfigTemplate
@ -200,7 +201,9 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('cluster_id', 'virtual_machine_id', name=_('Virtual Machine')),
FieldSet('enabled', 'mac_address', 'vrf_id', 'l2vpn_id', name=_('Attributes')),
FieldSet('enabled', name=_('Attributes')),
FieldSet('vrf_id', 'l2vpn_id', 'mac_address', name=_('Addressing')),
FieldSet('mode', name=_('802.1Q Switching')),
)
selector_fields = ('filter_id', 'q', 'virtual_machine_id')
cluster_id = DynamicModelMultipleChoiceField(
@ -237,6 +240,11 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('L2VPN')
)
mode = forms.MultipleChoiceField(
choices=InterfaceModeChoices,
required=False,
label=_('802.1Q mode')
)
tag = TagFilterField(model)

View File

@ -605,6 +605,7 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
mtu=100,
vrf=vrfs[0],
description='foobar1',
mode=InterfaceModeChoices.MODE_ACCESS,
vlan_translation_policy=vlan_translation_policies[0],
),
VMInterface(
@ -614,6 +615,7 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
mtu=200,
vrf=vrfs[1],
description='foobar2',
mode=InterfaceModeChoices.MODE_TAGGED,
vlan_translation_policy=vlan_translation_policies[0],
),
VMInterface(
@ -699,6 +701,10 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_mode(self):
params = {'mode': [InterfaceModeChoices.MODE_ACCESS]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_vlan(self):
vlan = VLAN.objects.filter(qinq_role=VLANQinQRoleChoices.ROLE_SERVICE).first()
params = {'vlan_id': vlan.pk}