4867 multiple mac addresses (#17902)

* Create MACAddress model and migrations to convert existing .mac_address fields to standalone objects

* Add migrations

* All views/filtering working and documentation done; no unit tests yet

* Redo migrations following VLAN Translation

* Remove mac_address filter fields and add table columns for device/vm

* Remove unnecessary "bulk rename"

* Fix filterset tests for Device

* Fix filterset tests for Interface

* Fix tests on single-object forms

* Fix serializer tests

* Fix filterset tests for VMInterface

* Fix filterset tests for Device and VirtualMachine

* Move new field check into lookup_map iteration

* Fix general MACAddress filter tests

* Add GraphQL types/filters/schema

* Fix bulk edit/create tests (bulk editing Interfaces will be unsupported because of inheritance from ComponentBulkEditForm)

* Make mac_address read_only on InterfaceSerializer/VMInterfaceSerializer

* Undo unrelated work

* Cleanup unused IPAddress derived stuff

* API endpoints

* Add serializer objects to interface serializers

* Clean up unnecessary bulk create forms/views/routes

* Add SearchIndex and adjust indexable fields for Interface and VMInterface

* Reorganize MACAddress classes out of association with DeviceComponents

* Move MACAddressSerializer

* Enforce saving only a single is_primary MACAddress per interface/vminterface

* Perform is_primary validation on MACAddress model and just check if one already exists for the interface

* Remove form-level validation

* Fix check for current is_primary setting when reassigning

* Model cleanup

* Documentation notes and cleanup

* Simplify serializer and add ip_addresses

* Add to VMInterfaceSerializer too

* Style cleanup

* Standardize "MAC Address" instead of "MAC"

* Remove unused views

* Add is_primary field for bulk edit

* HTML cleanup and add copy-to-clipboard button

* Remove mac_address from Interface and VMInterface bulk-edit forms

* Add device and VM filtering

* Use combined assigned_object_parent in table to match structure of IPAddressTable

* Add GFK fields to MACAddressSerializer

* Reorganize "Addressing" sections to remove from proximity to "Device Components" and related groupings

* Clean up migrations

* Misc cleanup

* Add filterset test

* Remove mac_address field from interface forms

* Designate primary MAC address via a ForeignKey on the interface models

* Add serializer fields for primary_mac_address

* Update docs

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
bctiemann
2024-11-18 15:11:24 -05:00
committed by GitHub
parent b4f15092db
commit 353214098b
46 changed files with 1156 additions and 173 deletions

View File

@@ -21,7 +21,7 @@ from wireless.choices import *
from wireless.models import WirelessLAN
from .base import ConnectedEndpointsSerializer
from .cables import CabledObjectSerializer
from .devices import DeviceSerializer, ModuleSerializer, VirtualDeviceContextSerializer
from .devices import DeviceSerializer, MACAddressSerializer, ModuleSerializer, VirtualDeviceContextSerializer
from .manufacturers import ManufacturerSerializer
from .nested import NestedInterfaceSerializer
from .roles import InventoryItemRoleSerializer
@@ -210,24 +210,23 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
)
count_ipaddresses = serializers.IntegerField(read_only=True)
count_fhrp_groups = serializers.IntegerField(read_only=True)
mac_address = serializers.CharField(
required=False,
default=None,
allow_blank=True,
allow_null=True
)
# Maintains backward compatibility with NetBox <v4.2
mac_address = serializers.CharField(allow_null=True, read_only=True)
primary_mac_address = MACAddressSerializer(nested=True, required=False, allow_null=True)
mac_addresses = MACAddressSerializer(many=True, nested=True, read_only=True, allow_null=True)
wwn = serializers.CharField(required=False, default=None, allow_blank=True, allow_null=True)
class Meta:
model = Interface
fields = [
'id', 'url', 'display_url', 'display', 'device', 'vdcs', 'module', 'name', 'label', 'type', 'enabled',
'parent', 'bridge', 'lag', 'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description',
'mode', 'rf_role', 'rf_channel', 'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width',
'tx_power', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'mark_connected',
'cable', 'cable_end', 'wireless_link', 'link_peers', 'link_peers_type', 'wireless_lans', 'vrf',
'l2vpn_termination', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable',
'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
'parent', 'bridge', 'lag', 'mtu', 'mac_address', 'primary_mac_address', 'mac_addresses', 'speed', 'duplex',
'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'poe_mode', 'poe_type',
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan',
'vlan_translation_policy', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers',
'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints',
'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
]
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')

View File

@@ -1,16 +1,19 @@
import decimal
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from dcim.choices import *
from dcim.models import Device, DeviceBay, Module, VirtualDeviceContext
from dcim.constants import MACADDRESS_ASSIGNMENT_MODELS
from dcim.models import Device, DeviceBay, MACAddress, Module, VirtualDeviceContext
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from ipam.api.serializers_.ip import IPAddressSerializer
from netbox.api.fields import ChoiceField, RelatedObjectCountField
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from utilities.api import get_serializer_for_model
from virtualization.api.serializers_.clusters import ClusterSerializer
from .devicetypes import *
from .platforms import PlatformSerializer
@@ -23,6 +26,7 @@ from .virtualchassis import VirtualChassisSerializer
__all__ = (
'DeviceSerializer',
'DeviceWithConfigContextSerializer',
'MACAddressSerializer',
'ModuleSerializer',
'VirtualDeviceContextSerializer',
)
@@ -153,3 +157,28 @@ class ModuleSerializer(NetBoxModelSerializer):
'asset_tag', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'device', 'module_bay', 'module_type', 'description')
class MACAddressSerializer(NetBoxModelSerializer):
assigned_object_type = ContentTypeField(
queryset=ContentType.objects.filter(MACADDRESS_ASSIGNMENT_MODELS),
required=False,
allow_null=True
)
assigned_object = serializers.SerializerMethodField(read_only=True)
class Meta:
model = MACAddress
fields = [
'id', 'url', 'display_url', 'display', 'mac_address', 'assigned_object_type', 'assigned_object',
'description', 'comments',
]
brief_fields = ('id', 'url', 'display', 'mac_address', 'description')
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_assigned_object(self, obj):
if obj.assigned_object is None:
return None
serializer = get_serializer_for_model(obj.assigned_object)
context = {'request': self.context['request']}
return serializer(obj.assigned_object, nested=True, context=context).data