Bläddra i källkod

Fixes #3315: Enable filtering devices/interfaces by multiple MAC addresses

Jeremy Stretch 6 år sedan
förälder
incheckning
86b6b9bf8b
4 ändrade filer med 39 tillägg och 29 borttagningar
  1. 1 0
      CHANGELOG.md
  2. 5 29
      netbox/dcim/filters.py
  3. 24 0
      netbox/dcim/forms.py
  4. 9 0
      netbox/utilities/filters.py

+ 1 - 0
CHANGELOG.md

@@ -8,6 +8,7 @@ v2.6.2 (FUTURE)
 ## Bug Fixes
 ## Bug Fixes
 
 
 * [#3293](https://github.com/netbox-community/netbox/issues/3293) - Enable filtering device components by multiple device IDs
 * [#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
 * [#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
 * [#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
 * [#3342](https://github.com/netbox-community/netbox/issues/3342) - Fix cluster delete button

+ 5 - 29
netbox/dcim/filters.py

@@ -2,15 +2,14 @@ import django_filters
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
 from django.db.models import Q
 from django.db.models import Q
-from netaddr import EUI
-from netaddr.core import AddrFormatError
 
 
 from extras.filters import CustomFieldFilterSet
 from extras.filters import CustomFieldFilterSet
 from tenancy.filtersets import TenancyFilterSet
 from tenancy.filtersets import TenancyFilterSet
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.constants import COLOR_CHOICES
 from utilities.constants import COLOR_CHOICES
 from utilities.filters import (
 from utilities.filters import (
-    MultiValueNumberFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
+    MultiValueMACAddressFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter,
+    TreeNodeMultipleChoiceFilter,
 )
 )
 from virtualization.models import Cluster
 from virtualization.models import Cluster
 from .constants import *
 from .constants import *
@@ -516,8 +515,8 @@ class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet):
         field_name='device_type__is_full_depth',
         field_name='device_type__is_full_depth',
         label='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',
         label='MAC address',
     )
     )
     has_primary_ip = django_filters.BooleanFilter(
     has_primary_ip = django_filters.BooleanFilter(
@@ -574,16 +573,6 @@ class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet):
             Q(comments__icontains=value)
             Q(comments__icontains=value)
         ).distinct()
         ).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):
     def _has_primary_ip(self, queryset, name, value):
         if value:
         if value:
             return queryset.filter(
             return queryset.filter(
@@ -726,10 +715,7 @@ class InterfaceFilter(django_filters.FilterSet):
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         label='LAG interface (ID)',
         label='LAG interface (ID)',
     )
     )
-    mac_address = django_filters.CharFilter(
-        method='_mac_address',
-        label='MAC address',
-    )
+    mac_address = MultiValueMACAddressFilter()
     tag = TagFilter()
     tag = TagFilter()
     vlan_id = django_filters.CharFilter(
     vlan_id = django_filters.CharFilter(
         method='filter_vlan_id',
         method='filter_vlan_id',
@@ -801,16 +787,6 @@ class InterfaceFilter(django_filters.FilterSet):
             'wireless': queryset.filter(type__in=WIRELESS_IFACE_TYPES),
             'wireless': queryset.filter(type__in=WIRELESS_IFACE_TYPES),
         }.get(value, queryset.none())
         }.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):
 class FrontPortFilter(DeviceComponentFilterSet):
     cabled = django_filters.BooleanFilter(
     cabled = django_filters.BooleanFilter(

+ 24 - 0
netbox/dcim/forms.py

@@ -7,6 +7,8 @@ from django.contrib.postgres.forms.array import SimpleArrayField
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
 from django.db.models import Q
 from django.db.models import Q
 from mptt.forms import TreeNodeChoiceField
 from mptt.forms import TreeNodeChoiceField
+from netaddr import EUI
+from netaddr.core import AddrFormatError
 from taggit.forms import TagField
 from taggit.forms import TagField
 from timezone_field import TimeZoneFormField
 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
 # Regions
 #
 #

+ 9 - 0
netbox/utilities/filters.py

@@ -3,6 +3,7 @@ from django import forms
 from django.conf import settings
 from django.conf import settings
 from django.db import models
 from django.db import models
 
 
+from dcim.forms import MACAddressField
 from extras.models import Tag
 from extras.models import Tag
 
 
 
 
@@ -49,6 +50,14 @@ class MultiValueTimeFilter(django_filters.MultipleChoiceFilter):
     field_class = multivalue_field_factory(forms.TimeField)
     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):
 class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
     """
     """
     Filters for a set of Models, including all descendant models within a Tree.  Example: [<Region: R1>,<Region: R2>]
     Filters for a set of Models, including all descendant models within a Tree.  Example: [<Region: R1>,<Region: R2>]