diff --git a/CHANGELOG.md b/CHANGELOG.md index d02babd3d..cdaca9e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ v2.6.2 (FUTURE) ## Bug Fixes * [#3293](https://github.com/netbox-community/netbox/issues/3293) - Enable filtering device components by multiple device IDs +* [#3315](https://github.com/netbox-community/netbox/issues/3315) - Enable filtering devices/interfaces by multiple MAC addresses * [#3317](https://github.com/netbox-community/netbox/issues/3317) - Fix permissions for ConfigContextBulkDeleteView * [#3323](https://github.com/netbox-community/netbox/issues/3323) - Fix permission evaluation for interface connections view * [#3342](https://github.com/netbox-community/netbox/issues/3342) - Fix cluster delete button diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 911f3c0a3..a063a6b83 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -2,15 +2,14 @@ import django_filters from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q -from netaddr import EUI -from netaddr.core import AddrFormatError from extras.filters import CustomFieldFilterSet from tenancy.filtersets import TenancyFilterSet from tenancy.models import Tenant from utilities.constants import COLOR_CHOICES from utilities.filters import ( - MultiValueNumberFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter, + MultiValueMACAddressFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, + TreeNodeMultipleChoiceFilter, ) from virtualization.models import Cluster from .constants import * @@ -516,8 +515,8 @@ class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet): field_name='device_type__is_full_depth', label='Is full depth', ) - mac_address = django_filters.CharFilter( - method='_mac_address', + mac_address = MultiValueMACAddressFilter( + field_name='interfaces__mac_address', label='MAC address', ) has_primary_ip = django_filters.BooleanFilter( @@ -574,16 +573,6 @@ class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet): Q(comments__icontains=value) ).distinct() - def _mac_address(self, queryset, name, value): - value = value.strip() - if not value: - return queryset - try: - mac = EUI(value.strip()) - return queryset.filter(interfaces__mac_address=mac).distinct() - except AddrFormatError: - return queryset.none() - def _has_primary_ip(self, queryset, name, value): if value: return queryset.filter( @@ -726,10 +715,7 @@ class InterfaceFilter(django_filters.FilterSet): queryset=Interface.objects.all(), label='LAG interface (ID)', ) - mac_address = django_filters.CharFilter( - method='_mac_address', - label='MAC address', - ) + mac_address = MultiValueMACAddressFilter() tag = TagFilter() vlan_id = django_filters.CharFilter( method='filter_vlan_id', @@ -801,16 +787,6 @@ class InterfaceFilter(django_filters.FilterSet): 'wireless': queryset.filter(type__in=WIRELESS_IFACE_TYPES), }.get(value, queryset.none()) - def _mac_address(self, queryset, name, value): - value = value.strip() - if not value: - return queryset - try: - mac = EUI(value.strip()) - return queryset.filter(mac_address=mac) - except AddrFormatError: - return queryset.none() - class FrontPortFilter(DeviceComponentFilterSet): cabled = django_filters.BooleanFilter( diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 06e1b35b1..d2628ab50 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -7,6 +7,8 @@ from django.contrib.postgres.forms.array import SimpleArrayField from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q from mptt.forms import TreeNodeChoiceField +from netaddr import EUI +from netaddr.core import AddrFormatError from taggit.forms import TagField from timezone_field import TimeZoneFormField @@ -76,6 +78,28 @@ class BulkRenameForm(forms.Form): }) +# +# Fields +# + +class MACAddressField(forms.Field): + widget = forms.CharField + default_error_messages = { + 'invalid': 'MAC address must be in EUI-48 format', + } + + def to_python(self, value): + value = super().to_python(value) + + # Validate MAC address format + try: + value = EUI(value.strip()) + except AddrFormatError: + raise forms.ValidationError(self.error_messages['invalid'], code='invalid') + + return value + + # # Regions # diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 8ccdf2583..7ba008c70 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -3,6 +3,7 @@ from django import forms from django.conf import settings from django.db import models +from dcim.forms import MACAddressField from extras.models import Tag @@ -49,6 +50,14 @@ class MultiValueTimeFilter(django_filters.MultipleChoiceFilter): field_class = multivalue_field_factory(forms.TimeField) +class MACAddressFilter(django_filters.CharFilter): + field_class = MACAddressField + + +class MultiValueMACAddressFilter(django_filters.MultipleChoiceFilter): + field_class = multivalue_field_factory(MACAddressField) + + class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter): """ Filters for a set of Models, including all descendant models within a Tree. Example: [,]