Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
AttributeFiltersMixin, BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet,
OrganizationalModelFilterSet,
)
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
from tenancy.models import *
from users.models import User
from utilities.filters import (
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
)
from virtualization.models import Cluster, ClusterGroup, VMInterface, VirtualMachine
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
from vpn.models import L2VPN
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
from wireless.models import WirelessLAN, WirelessLink
from .choices import *
from .constants import *
Expand Down Expand Up @@ -1807,6 +1807,14 @@ class MACAddressFilterSet(NetBoxModelFilterSet):
queryset=VMInterface.objects.all(),
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:
model = MACAddress
Expand Down Expand Up @@ -1843,6 +1851,29 @@ def filter_virtual_machine(self, queryset, name, value):
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):
mode = django_filters.MultipleChoiceFilter(
Expand Down
22 changes: 20 additions & 2 deletions netbox/dcim/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1676,12 +1676,16 @@ class MACAddressFilterForm(NetBoxModelFilterSetForm):
model = MACAddress
fieldsets = (
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')
mac_address = forms.CharField(
required=False,
label=_('MAC address')
label=_('MAC address'),
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
Expand All @@ -1693,6 +1697,20 @@ class MACAddressFilterForm(NetBoxModelFilterSetForm):
required=False,
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)


Expand Down
22 changes: 21 additions & 1 deletion netbox/dcim/graphql/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
ImageAttachmentFilterMixin,
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 (
CabledObjectModelFilterMixin,
ComponentModelFilterMixin,
Expand Down Expand Up @@ -419,6 +421,24 @@ class MACAddressFilter(PrimaryModelFilterMixin):
)
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
)
Comment on lines +430 to +435
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!
Quick note here: I imported VMInterface directly, while Interface is referenced via dcim.models (models.Interface). If you’d prefer consistent namespacing, I can switch to from virtualization import models as virtualization_models and use virtualization_models.VMInterface. Happy to adjust either way!

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)
class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin, CabledObjectModelFilterMixin):
Expand Down
7 changes: 5 additions & 2 deletions netbox/dcim/tables/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,9 @@ class MACAddressTable(NetBoxTable):
orderable=False,
verbose_name=_('Parent')
)
is_primary = columns.BooleanColumn(
verbose_name=_('Primary')
)
tags = columns.TagColumn(
url_name='dcim:macaddress_list'
)
Expand All @@ -1184,7 +1187,7 @@ class MACAddressTable(NetBoxTable):
class Meta(DeviceComponentTable.Meta):
model = models.MACAddress
fields = (
'pk', 'id', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description', 'comments', 'tags',
'created', 'last_updated',
'pk', 'id', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description', 'is_primary',
'comments', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description')
25 changes: 24 additions & 1 deletion netbox/dcim/tests/test_filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from tenancy.models import Tenant, TenantGroup
from users.models import User
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.models import WirelessLink

Expand Down Expand Up @@ -7164,9 +7164,20 @@ def setUpTestData(cls):
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-02', assigned_object=vm_interfaces[2]),
# unassigned
MACAddress(mac_address='00-00-00-07-01-01'),
)
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):
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)
Expand Down Expand Up @@ -7198,3 +7209,15 @@ def test_vminterface(self):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'vminterface': [vm_interfaces[0].name, vm_interfaces[1].name]}
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)