mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-09 01:49:35 -06:00
feat(filtersets): Add assigned and primary filters for MACAddress (#20620)
Introduce Boolean filters `assigned` and `primary` to the MACAddress filterset, improving filtering capabilities. Update forms, tables, and GraphQL queries to incorporate the new filters. Add tests to validate the correct functionality. Fixes #20399
This commit is contained in:
parent
e4c74ce6a3
commit
bbb330becf
@ -14,16 +14,16 @@ from netbox.filtersets import (
|
|||||||
AttributeFiltersMixin, BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet,
|
AttributeFiltersMixin, BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet,
|
||||||
OrganizationalModelFilterSet,
|
OrganizationalModelFilterSet,
|
||||||
)
|
)
|
||||||
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
|
from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
|
||||||
from tenancy.models import *
|
from tenancy.models import *
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
|
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
|
||||||
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster, ClusterGroup, VMInterface, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
|
||||||
from vpn.models import L2VPN
|
from vpn.models import L2VPN
|
||||||
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
|
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
||||||
from wireless.models import WirelessLAN, WirelessLink
|
from wireless.models import WirelessLAN, WirelessLink
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
@ -1807,6 +1807,14 @@ class MACAddressFilterSet(NetBoxModelFilterSet):
|
|||||||
queryset=VMInterface.objects.all(),
|
queryset=VMInterface.objects.all(),
|
||||||
label=_('VM interface (ID)'),
|
label=_('VM interface (ID)'),
|
||||||
)
|
)
|
||||||
|
assigned = django_filters.BooleanFilter(
|
||||||
|
method='filter_assigned',
|
||||||
|
label=_('Is assigned'),
|
||||||
|
)
|
||||||
|
primary = django_filters.BooleanFilter(
|
||||||
|
method='filter_primary',
|
||||||
|
label=_('Is primary'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MACAddress
|
model = MACAddress
|
||||||
@ -1843,6 +1851,29 @@ class MACAddressFilterSet(NetBoxModelFilterSet):
|
|||||||
vminterface__in=interface_ids
|
vminterface__in=interface_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def filter_assigned(self, queryset, name, value):
|
||||||
|
params = {
|
||||||
|
'assigned_object_type__isnull': True,
|
||||||
|
'assigned_object_id__isnull': True,
|
||||||
|
}
|
||||||
|
if value:
|
||||||
|
return queryset.exclude(**params)
|
||||||
|
else:
|
||||||
|
return queryset.filter(**params)
|
||||||
|
|
||||||
|
def filter_primary(self, queryset, name, value):
|
||||||
|
interface_mac_ids = Interface.objects.filter(primary_mac_address_id__isnull=False).values_list(
|
||||||
|
'primary_mac_address_id', flat=True
|
||||||
|
)
|
||||||
|
vminterface_mac_ids = VMInterface.objects.filter(primary_mac_address_id__isnull=False).values_list(
|
||||||
|
'primary_mac_address_id', flat=True
|
||||||
|
)
|
||||||
|
query = Q(pk__in=interface_mac_ids) | Q(pk__in=vminterface_mac_ids)
|
||||||
|
if value:
|
||||||
|
return queryset.filter(query)
|
||||||
|
else:
|
||||||
|
return queryset.exclude(query)
|
||||||
|
|
||||||
|
|
||||||
class CommonInterfaceFilterSet(django_filters.FilterSet):
|
class CommonInterfaceFilterSet(django_filters.FilterSet):
|
||||||
mode = django_filters.MultipleChoiceFilter(
|
mode = django_filters.MultipleChoiceFilter(
|
||||||
|
|||||||
@ -1676,12 +1676,16 @@ class MACAddressFilterForm(NetBoxModelFilterSetForm):
|
|||||||
model = MACAddress
|
model = MACAddress
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id', 'tag'),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
FieldSet('mac_address', 'device_id', 'virtual_machine_id', name=_('MAC address')),
|
FieldSet('mac_address', name=_('Attributes')),
|
||||||
|
FieldSet(
|
||||||
|
'device_id', 'virtual_machine_id', 'assigned', 'primary',
|
||||||
|
name=_('Assignments'),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'device_id', 'virtual_machine_id')
|
selector_fields = ('filter_id', 'q', 'device_id', 'virtual_machine_id')
|
||||||
mac_address = forms.CharField(
|
mac_address = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('MAC address')
|
label=_('MAC address'),
|
||||||
)
|
)
|
||||||
device_id = DynamicModelMultipleChoiceField(
|
device_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
@ -1693,6 +1697,20 @@ class MACAddressFilterForm(NetBoxModelFilterSetForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('Assigned VM'),
|
label=_('Assigned VM'),
|
||||||
)
|
)
|
||||||
|
assigned = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
label=_('Assigned to an interface'),
|
||||||
|
widget=forms.Select(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
),
|
||||||
|
)
|
||||||
|
primary = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
label=_('Primary MAC of an interface'),
|
||||||
|
widget=forms.Select(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
),
|
||||||
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,9 @@ from netbox.graphql.filter_mixins import (
|
|||||||
ImageAttachmentFilterMixin,
|
ImageAttachmentFilterMixin,
|
||||||
WeightFilterMixin,
|
WeightFilterMixin,
|
||||||
)
|
)
|
||||||
from tenancy.graphql.filter_mixins import TenancyFilterMixin, ContactFilterMixin
|
from tenancy.graphql.filter_mixins import ContactFilterMixin, TenancyFilterMixin
|
||||||
|
from virtualization.models import VMInterface
|
||||||
|
|
||||||
from .filter_mixins import (
|
from .filter_mixins import (
|
||||||
CabledObjectModelFilterMixin,
|
CabledObjectModelFilterMixin,
|
||||||
ComponentModelFilterMixin,
|
ComponentModelFilterMixin,
|
||||||
@ -419,6 +421,24 @@ class MACAddressFilter(PrimaryModelFilterMixin):
|
|||||||
)
|
)
|
||||||
assigned_object_id: ID | None = strawberry_django.filter_field()
|
assigned_object_id: ID | None = strawberry_django.filter_field()
|
||||||
|
|
||||||
|
@strawberry_django.filter_field()
|
||||||
|
def assigned(self, value: bool, prefix) -> Q:
|
||||||
|
return Q(**{f'{prefix}assigned_object_id__isnull': (not value)})
|
||||||
|
|
||||||
|
@strawberry_django.filter_field()
|
||||||
|
def primary(self, value: bool, prefix) -> Q:
|
||||||
|
interface_mac_ids = models.Interface.objects.filter(primary_mac_address_id__isnull=False).values_list(
|
||||||
|
'primary_mac_address_id', flat=True
|
||||||
|
)
|
||||||
|
vminterface_mac_ids = VMInterface.objects.filter(primary_mac_address_id__isnull=False).values_list(
|
||||||
|
'primary_mac_address_id', flat=True
|
||||||
|
)
|
||||||
|
query = Q(**{f'{prefix}pk__in': interface_mac_ids}) | Q(**{f'{prefix}pk__in': vminterface_mac_ids})
|
||||||
|
if value:
|
||||||
|
return Q(query)
|
||||||
|
else:
|
||||||
|
return ~Q(query)
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.filter_type(models.Interface, lookups=True)
|
@strawberry_django.filter_type(models.Interface, lookups=True)
|
||||||
class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin, CabledObjectModelFilterMixin):
|
class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin, CabledObjectModelFilterMixin):
|
||||||
|
|||||||
@ -1174,6 +1174,9 @@ class MACAddressTable(NetBoxTable):
|
|||||||
orderable=False,
|
orderable=False,
|
||||||
verbose_name=_('Parent')
|
verbose_name=_('Parent')
|
||||||
)
|
)
|
||||||
|
is_primary = columns.BooleanColumn(
|
||||||
|
verbose_name=_('Primary')
|
||||||
|
)
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='dcim:macaddress_list'
|
url_name='dcim:macaddress_list'
|
||||||
)
|
)
|
||||||
@ -1184,7 +1187,7 @@ class MACAddressTable(NetBoxTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = models.MACAddress
|
model = models.MACAddress
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description', 'comments', 'tags',
|
'pk', 'id', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description', 'is_primary',
|
||||||
'created', 'last_updated',
|
'comments', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description')
|
default_columns = ('pk', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description')
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from netbox.choices import ColorChoices, WeightUnitChoices
|
|||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
||||||
from virtualization.models import Cluster, ClusterType, ClusterGroup, VMInterface, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||||
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
||||||
from wireless.models import WirelessLink
|
from wireless.models import WirelessLink
|
||||||
|
|
||||||
@ -7164,9 +7164,20 @@ class MACAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
MACAddress(mac_address='00-00-00-05-01-01', assigned_object=vm_interfaces[1]),
|
MACAddress(mac_address='00-00-00-05-01-01', assigned_object=vm_interfaces[1]),
|
||||||
MACAddress(mac_address='00-00-00-06-01-01', assigned_object=vm_interfaces[2]),
|
MACAddress(mac_address='00-00-00-06-01-01', assigned_object=vm_interfaces[2]),
|
||||||
MACAddress(mac_address='00-00-00-06-01-02', assigned_object=vm_interfaces[2]),
|
MACAddress(mac_address='00-00-00-06-01-02', assigned_object=vm_interfaces[2]),
|
||||||
|
# unassigned
|
||||||
|
MACAddress(mac_address='00-00-00-07-01-01'),
|
||||||
)
|
)
|
||||||
MACAddress.objects.bulk_create(mac_addresses)
|
MACAddress.objects.bulk_create(mac_addresses)
|
||||||
|
|
||||||
|
# Set MAC addresses as primary
|
||||||
|
for idx, interface in enumerate(interfaces):
|
||||||
|
interface.primary_mac_address = mac_addresses[idx]
|
||||||
|
interface.save()
|
||||||
|
for idx, vm_interface in enumerate(vm_interfaces):
|
||||||
|
# Offset by 4 for device MACs
|
||||||
|
vm_interface.primary_mac_address = mac_addresses[idx + 4]
|
||||||
|
vm_interface.save()
|
||||||
|
|
||||||
def test_mac_address(self):
|
def test_mac_address(self):
|
||||||
params = {'mac_address': ['00-00-00-01-01-01', '00-00-00-02-01-01']}
|
params = {'mac_address': ['00-00-00-01-01-01', '00-00-00-02-01-01']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -7198,3 +7209,15 @@ class MACAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'vminterface': [vm_interfaces[0].name, vm_interfaces[1].name]}
|
params = {'vminterface': [vm_interfaces[0].name, vm_interfaces[1].name]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_assigned(self):
|
||||||
|
params = {'assigned': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
params = {'assigned': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_primary(self):
|
||||||
|
params = {'primary': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
params = {'primary': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user