diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py
index a4ceefb72..645dc9c40 100644
--- a/netbox/dcim/filtersets.py
+++ b/netbox/dcim/filtersets.py
@@ -1099,10 +1099,10 @@ class DeviceFilterSet(
field_name='device_type__is_full_depth',
label=_('Is full depth'),
)
- # mac_address = MultiValueMACAddressFilter(
- # field_name='interfaces___mac_address',
- # label=_('MAC address'),
- # )
+ mac_address = MultiValueMACAddressFilter(
+ field_name='interfaces__mac_addresses__mac_address',
+ label=_('MAC address'),
+ )
serial = MultiValueCharFilter(
lookup_expr='iexact'
)
@@ -1849,7 +1849,10 @@ class InterfaceFilterSet(
duplex = django_filters.MultipleChoiceFilter(
choices=InterfaceDuplexChoices
)
- # mac_address = MultiValueMACAddressFilter()
+ mac_address = MultiValueMACAddressFilter(
+ field_name='mac_addresses__mac_address',
+ label=_('MAC Address')
+ )
wwn = MultiValueWWNFilter()
poe_mode = django_filters.MultipleChoiceFilter(
choices=InterfacePoEModeChoices
diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py
index 1816ef444..cdb7e2a71 100644
--- a/netbox/dcim/forms/filtersets.py
+++ b/netbox/dcim/forms/filtersets.py
@@ -1344,7 +1344,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only', name=_('Attributes')),
- FieldSet('vrf_id', 'l2vpn_id', 'wwn', name=_('Addressing')),
+ FieldSet('vrf_id', 'l2vpn_id', 'mac_address', 'wwn', name=_('Addressing')),
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
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')),
@@ -1399,10 +1399,10 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
- # mac_address = forms.CharField(
- # required=False,
- # label=_('MAC address')
- # )
+ mac_address = forms.CharField(
+ required=False,
+ label=_('MAC address')
+ )
wwn = forms.CharField(
required=False,
label=_('WWN')
diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py
index d3b62c232..ae3fbe049 100644
--- a/netbox/dcim/tests/test_filtersets.py
+++ b/netbox/dcim/tests/test_filtersets.py
@@ -3988,6 +3988,10 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
params = {'kind': 'virtual'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
+ def test_mac_address(self):
+ params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
def test_type(self):
params = {'type': [InterfaceTypeChoices.TYPE_1GE_FIXED, InterfaceTypeChoices.TYPE_1GE_GBIC]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py
index 637a40bf1..8be3db059 100644
--- a/netbox/netbox/filtersets.py
+++ b/netbox/netbox/filtersets.py
@@ -168,6 +168,8 @@ class BaseFilterSet(django_filters.FilterSet):
# Get properties of the existing filter for later use
field_name = existing_filter.field_name
field = get_model_field(cls._meta.model, field_name)
+ if field is None:
+ raise ValueError('Invalid field name/lookup on {}: {}'.format(existing_filter_name, field_name))
# Create new filters for each lookup expression in the map
for lookup_name, lookup_expr in lookup_map.items():
diff --git a/netbox/templates/dcim/macaddress.html b/netbox/templates/dcim/macaddress.html
index b0cf1bb62..d62edd550 100644
--- a/netbox/templates/dcim/macaddress.html
+++ b/netbox/templates/dcim/macaddress.html
@@ -5,46 +5,46 @@
{% load i18n %}
{% block content %}
-
-
+
+
-
-
-
- {% trans "Description" %} |
- {{ object.description|placeholder }} |
-
-
- {% trans "Assignment" %} |
-
- {% if object.assigned_object %}
- {% if object.assigned_object.parent_object %}
- {{ object.assigned_object.parent_object|linkify }} /
- {% endif %}
- {{ object.assigned_object|linkify }}
- {% else %}
- {{ ''|placeholder }}
- {% endif %}
- |
-
-
- {% trans "Primary MAC for Interface" %} |
- {% checkmark object.is_primary %} |
-
-
+
+
+
+ {% trans "Description" %} |
+ {{ object.description|placeholder }} |
+
+
+ {% trans "Assignment" %} |
+
+ {% if object.assigned_object %}
+ {% if object.assigned_object.parent_object %}
+ {{ object.assigned_object.parent_object|linkify }} /
+ {% endif %}
+ {{ object.assigned_object|linkify }}
+ {% else %}
+ {{ ''|placeholder }}
+ {% endif %}
+ |
+
+
+ {% trans "Primary MAC for Interface" %} |
+ {% checkmark object.is_primary %} |
+
+
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% plugin_left_page object %}
-
-
+
+
{% include 'inc/panels/comments.html' %}
{% plugin_right_page object %}
-
-
-
-
- {% plugin_full_width_page object %}
+
+
+
+
+ {% plugin_full_width_page object %}
+
-
{% endblock %}
diff --git a/netbox/virtualization/filtersets.py b/netbox/virtualization/filtersets.py
index 17f574db9..84e828295 100644
--- a/netbox/virtualization/filtersets.py
+++ b/netbox/virtualization/filtersets.py
@@ -9,7 +9,7 @@ from extras.models import ConfigTemplate
from ipam.filtersets import PrimaryIPFilterSet
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
-from utilities.filters import MultiValueCharFilter, TreeNodeMultipleChoiceFilter
+from utilities.filters import MultiValueCharFilter, TreeNodeMultipleChoiceFilter, MultiValueMACAddressFilter
from .choices import *
from .models import *
@@ -297,9 +297,10 @@ class VMInterfaceFilterSet(NetBoxModelFilterSet, CommonInterfaceFilterSet):
queryset=VMInterface.objects.all(),
label=_('Bridged interface (ID)'),
)
- # _mac_address = MultiValueMACAddressFilter(
- # label=_('MAC address'),
- # )
+ mac_address = MultiValueMACAddressFilter(
+ field_name='mac_addresses__mac_address',
+ label=_('MAC address'),
+ )
class Meta:
model = VMInterface
diff --git a/netbox/virtualization/forms/filtersets.py b/netbox/virtualization/forms/filtersets.py
index 70266cb5f..7c040d948 100644
--- a/netbox/virtualization/forms/filtersets.py
+++ b/netbox/virtualization/forms/filtersets.py
@@ -195,7 +195,7 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('cluster_id', 'virtual_machine_id', name=_('Virtual Machine')),
- FieldSet('enabled', 'vrf_id', 'l2vpn_id', name=_('Attributes')),
+ FieldSet('enabled', 'mac_address', 'vrf_id', 'l2vpn_id', name=_('Attributes')),
)
selector_fields = ('filter_id', 'q', 'virtual_machine_id')
cluster_id = DynamicModelMultipleChoiceField(
@@ -218,10 +218,10 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
- # mac_address = forms.CharField(
- # required=False,
- # label=_('MAC address')
- # )
+ mac_address = forms.CharField(
+ required=False,
+ label=_('MAC address')
+ )
vrf_id = DynamicModelMultipleChoiceField(
queryset=VRF.objects.all(),
required=False,
diff --git a/netbox/virtualization/tests/test_filtersets.py b/netbox/virtualization/tests/test_filtersets.py
index cd598274f..0543121bf 100644
--- a/netbox/virtualization/tests/test_filtersets.py
+++ b/netbox/virtualization/tests/test_filtersets.py
@@ -1,6 +1,6 @@
from django.test import TestCase
-from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
+from dcim.models import Device, DeviceRole, MACAddress, Platform, Region, Site, SiteGroup
from ipam.models import IPAddress, VLANTranslationPolicy, VRF
from tenancy.models import Tenant, TenantGroup
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
@@ -568,13 +568,19 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
)
VLANTranslationPolicy.objects.bulk_create(vlan_translation_policies)
+ mac_addresses = (
+ MACAddress(mac_address='00-00-00-00-00-01'),
+ MACAddress(mac_address='00-00-00-00-00-02'),
+ MACAddress(mac_address='00-00-00-00-00-03'),
+ )
+ MACAddress.objects.bulk_create(mac_addresses)
+
interfaces = (
VMInterface(
virtual_machine=vms[0],
name='Interface 1',
enabled=True,
mtu=100,
- mac_address='00-00-00-00-00-01',
vrf=vrfs[0],
description='foobar1',
vlan_translation_policy=vlan_translation_policies[0],
@@ -584,7 +590,6 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
name='Interface 2',
enabled=True,
mtu=200,
- mac_address='00-00-00-00-00-02',
vrf=vrfs[1],
description='foobar2',
vlan_translation_policy=vlan_translation_policies[0],
@@ -594,13 +599,16 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
name='Interface 3',
enabled=False,
mtu=300,
- mac_address='00-00-00-00-00-03',
vrf=vrfs[2],
description='foobar3'
),
)
VMInterface.objects.bulk_create(interfaces)
+ interfaces[0].mac_addresses.set([mac_addresses[0]])
+ interfaces[1].mac_addresses.set([mac_addresses[1]])
+ interfaces[2].mac_addresses.set([mac_addresses[2]])
+
def test_q(self):
params = {'q': 'foobar1'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)