diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 04ac3a3d2..3f43afa40 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1099,7 +1099,7 @@ class DeviceFilterSet( label=_('Is full depth'), ) mac_address = MultiValueMACAddressFilter( - field_name='interfaces__mac_address', + field_name='interfaces___mac_address', label=_('MAC address'), ) serial = MultiValueCharFilter( @@ -1689,7 +1689,7 @@ class InterfaceFilterSet( duplex = django_filters.MultipleChoiceFilter( choices=InterfaceDuplexChoices ) - mac_address = MultiValueMACAddressFilter() + # mac_address = MultiValueMACAddressFilter() wwn = MultiValueWWNFilter() poe_mode = django_filters.MultipleChoiceFilter( choices=InterfacePoEModeChoices diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index ac20173f2..546d282be 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1392,7 +1392,7 @@ class PowerOutletBulkEditForm( class InterfaceBulkEditForm( ComponentBulkEditForm, form_from_model(Interface, [ - 'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only', + 'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', '_mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans' ]) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index e34db0016..8be9a5c44 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -906,7 +906,7 @@ class InterfaceImportForm(NetBoxModelImportForm): model = Interface fields = ( 'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled', - 'mark_connected', 'mac_address', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode', + 'mark_connected', '_mac_address', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode', 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'tags' ) diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py index cd863837a..75c927ffd 100644 --- a/netbox/dcim/graphql/types.py +++ b/netbox/dcim/graphql/types.py @@ -377,7 +377,7 @@ class FrontPortTemplateType(ModularComponentTemplateType): filters=InterfaceFilter ) class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin): - mac_address: str | None + _mac_address: str | None wwn: str | None parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None bridge: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index a5bc2f604..d635219f6 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -12,7 +12,7 @@ from dcim.choices import * from dcim.constants import * from dcim.fields import MACAddressField, WWNField from netbox.choices import ColorChoices -from netbox.models import OrganizationalModel, NetBoxModel +from netbox.models import OrganizationalModel, NetBoxModel, PrimaryModel from utilities.fields import ColorField, NaturalOrderingField from utilities.mptt import TreeManager from utilities.ordering import naturalize_interface @@ -31,6 +31,7 @@ __all__ = ( 'Interface', 'InventoryItem', 'InventoryItemRole', + 'MACAddress', 'ModuleBay', 'PathEndpoint', 'PowerOutlet', @@ -509,7 +510,7 @@ class BaseInterface(models.Model): verbose_name=_('enabled'), default=True ) - mac_address = MACAddressField( + _mac_address = MACAddressField( null=True, blank=True, verbose_name=_('MAC address') @@ -575,6 +576,12 @@ class BaseInterface(models.Model): def count_fhrp_groups(self): return self.fhrp_group_assignments.count() + @property + def mac_address(self): + if macaddress := self.macaddress_set.first(): + return macaddress.mac_address + return None + class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint, TrackingModelMixin): """ @@ -1323,3 +1330,29 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin): def get_status_color(self): return InventoryItemStatusChoices.colors.get(self.status) + + +class MACAddress(PrimaryModel): + mac_address = MACAddressField( + null=True, + blank=True, + verbose_name=_('MAC address') + ) + interface = models.ForeignKey( + to='dcim.Interface', + on_delete=models.PROTECT, + null=True, + blank=True, + verbose_name=_('Interface') + ) + vm_interface = models.ForeignKey( + to='virtualization.VMInterface', + on_delete=models.PROTECT, + null=True, + blank=True, + verbose_name=_('VM Interface') + ) + is_primary = models.BooleanField( + verbose_name=_('is primary for interface'), + default=False + ) diff --git a/netbox/virtualization/filtersets.py b/netbox/virtualization/filtersets.py index ec0831f9f..699661c0c 100644 --- a/netbox/virtualization/filtersets.py +++ b/netbox/virtualization/filtersets.py @@ -226,7 +226,7 @@ class VirtualMachineFilterSet( label=_('Platform (slug)'), ) mac_address = MultiValueMACAddressFilter( - field_name='interfaces__mac_address', + field_name='interfaces___mac_address', label=_('MAC address'), ) has_primary_ip = django_filters.BooleanFilter( @@ -297,7 +297,7 @@ class VMInterfaceFilterSet(NetBoxModelFilterSet, CommonInterfaceFilterSet): queryset=VMInterface.objects.all(), label=_('Bridged interface (ID)'), ) - mac_address = MultiValueMACAddressFilter( + _mac_address = MultiValueMACAddressFilter( label=_('MAC address'), ) diff --git a/netbox/virtualization/forms/bulk_import.py b/netbox/virtualization/forms/bulk_import.py index 17efc567a..68d41a645 100644 --- a/netbox/virtualization/forms/bulk_import.py +++ b/netbox/virtualization/forms/bulk_import.py @@ -178,7 +178,7 @@ class VMInterfaceImportForm(NetBoxModelImportForm): class Meta: model = VMInterface fields = ( - 'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode', + 'virtual_machine', 'name', 'parent', 'bridge', 'enabled', '_mac_address', 'mtu', 'description', 'mode', 'vrf', 'tags' ) diff --git a/netbox/virtualization/graphql/types.py b/netbox/virtualization/graphql/types.py index 2d872322b..663fa6bd4 100644 --- a/netbox/virtualization/graphql/types.py +++ b/netbox/virtualization/graphql/types.py @@ -95,7 +95,7 @@ class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType): filters=VMInterfaceFilter ) class VMInterfaceType(IPAddressesMixin, ComponentType): - mac_address: str | None + _mac_address: str | None parent: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None bridge: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None