diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index f6a9ae3a1..a0cf3c80c 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -4,10 +4,8 @@ from circuits.choices import CircuitStatusChoices from circuits.models import Provider, Circuit, CircuitTermination, CircuitType from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer from dcim.api.serializers import CableTerminationSerializer, ConnectedEndpointSerializer -from netbox.api.serializers import CustomFieldModelSerializer -from extras.api.serializers import TaggedObjectSerializer from netbox.api import ChoiceField -from netbox.api.serializers import OrganizationalModelSerializer, WritableNestedSerializer +from netbox.api.serializers import OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer from tenancy.api.nested_serializers import NestedTenantSerializer from .nested_serializers import * @@ -16,7 +14,7 @@ from .nested_serializers import * # Providers # -class ProviderSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class ProviderSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail') circuit_count = serializers.IntegerField(read_only=True) @@ -55,7 +53,7 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer, ConnectedEnd ] -class CircuitSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class CircuitSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail') provider = NestedProviderSerializer() status = ChoiceField(choices=CircuitStatusChoices, required=False) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 642858d28..31db14f63 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -7,13 +7,12 @@ from rest_framework.validators import UniqueTogetherValidator from dcim.choices import * from dcim.constants import * from dcim.models import * -from netbox.api.serializers import CustomFieldModelSerializer -from extras.api.serializers import TaggedObjectSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.models import VLAN from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField from netbox.api.serializers import ( - NestedGroupModelSerializer, OrganizationalModelSerializer, ValidatedModelSerializer, WritableNestedSerializer, + NestedGroupModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer, + WritableNestedSerializer, ) from tenancy.api.nested_serializers import NestedTenantSerializer from users.api.nested_serializers import NestedUserSerializer @@ -43,7 +42,7 @@ class CableTerminationSerializer(serializers.ModelSerializer): return None -class ConnectedEndpointSerializer(CustomFieldModelSerializer): +class ConnectedEndpointSerializer(serializers.ModelSerializer): connected_endpoint_type = serializers.SerializerMethodField(read_only=True) connected_endpoint = serializers.SerializerMethodField(read_only=True) connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True) @@ -101,7 +100,7 @@ class SiteGroupSerializer(NestedGroupModelSerializer): ] -class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class SiteSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail') status = ChoiceField(choices=SiteStatusChoices, required=False) region = NestedRegionSerializer(required=False, allow_null=True) @@ -155,7 +154,7 @@ class RackRoleSerializer(OrganizationalModelSerializer): ] -class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class RackSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail') site = NestedSiteSerializer() location = NestedLocationSerializer(required=False, allow_null=True, default=None) @@ -206,7 +205,7 @@ class RackUnitSerializer(serializers.Serializer): occupied = serializers.BooleanField(read_only=True) -class RackReservationSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class RackReservationSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackreservation-detail') rack = NestedRackSerializer() user = NestedUserSerializer() @@ -271,7 +270,7 @@ class ManufacturerSerializer(OrganizationalModelSerializer): ] -class DeviceTypeSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class DeviceTypeSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail') manufacturer = NestedManufacturerSerializer() subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False) @@ -434,7 +433,7 @@ class PlatformSerializer(OrganizationalModelSerializer): ] -class DeviceSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class DeviceSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail') device_type = NestedDeviceTypeSerializer() device_role = NestedDeviceRoleSerializer() @@ -506,7 +505,11 @@ class DeviceNAPALMSerializer(serializers.Serializer): method = serializers.DictField() -class ConsoleServerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): +# +# Device components +# + +class ConsoleServerPortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail') device = NestedDeviceSerializer() type = ChoiceField( @@ -530,7 +533,7 @@ class ConsoleServerPortSerializer(TaggedObjectSerializer, CableTerminationSerial ] -class ConsolePortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): +class ConsolePortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail') device = NestedDeviceSerializer() type = ChoiceField( @@ -554,7 +557,7 @@ class ConsolePortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ] -class PowerOutletSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): +class PowerOutletSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail') device = NestedDeviceSerializer() type = ChoiceField( @@ -583,7 +586,7 @@ class PowerOutletSerializer(TaggedObjectSerializer, CableTerminationSerializer, ] -class PowerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): +class PowerPortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail') device = NestedDeviceSerializer() type = ChoiceField( @@ -602,7 +605,7 @@ class PowerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co ] -class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): +class InterfaceSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail') device = NestedDeviceSerializer() type = ChoiceField(choices=InterfaceTypeChoices) @@ -643,7 +646,7 @@ class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co return super().validate(data) -class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, CustomFieldModelSerializer): +class RearPortSerializer(PrimaryModelSerializer, CableTerminationSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') device = NestedDeviceSerializer() type = ChoiceField(choices=PortTypeChoices) @@ -668,7 +671,7 @@ class FrontPortRearPortSerializer(WritableNestedSerializer): fields = ['id', 'url', 'name', 'label'] -class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, CustomFieldModelSerializer): +class FrontPortSerializer(PrimaryModelSerializer, CableTerminationSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail') device = NestedDeviceSerializer() type = ChoiceField(choices=PortTypeChoices) @@ -684,7 +687,7 @@ class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Cu ] -class DeviceBaySerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class DeviceBaySerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail') device = NestedDeviceSerializer() installed_device = NestedDeviceSerializer(required=False, allow_null=True) @@ -701,7 +704,7 @@ class DeviceBaySerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # Inventory items # -class InventoryItemSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class InventoryItemSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail') device = NestedDeviceSerializer() # Provide a default value to satisfy UniqueTogetherValidator @@ -721,7 +724,7 @@ class InventoryItemSerializer(TaggedObjectSerializer, CustomFieldModelSerializer # Cables # -class CableSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class CableSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail') termination_a_type = ContentTypeField( queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS) @@ -851,7 +854,7 @@ class InterfaceConnectionSerializer(ValidatedModelSerializer): # Virtual chassis # -class VirtualChassisSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class VirtualChassisSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail') master = NestedDeviceSerializer(required=False) member_count = serializers.IntegerField(read_only=True) @@ -865,7 +868,7 @@ class VirtualChassisSerializer(TaggedObjectSerializer, CustomFieldModelSerialize # Power panels # -class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class PowerPanelSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail') site = NestedSiteSerializer() location = NestedLocationSerializer( @@ -880,12 +883,7 @@ class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): fields = ['id', 'url', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count'] -class PowerFeedSerializer( - TaggedObjectSerializer, - CableTerminationSerializer, - ConnectedEndpointSerializer, - CustomFieldModelSerializer -): +class PowerFeedSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerfeed-detail') power_panel = NestedPowerPanelSerializer() rack = NestedRackSerializer( diff --git a/netbox/extras/api/nested_serializers.py b/netbox/extras/api/nested_serializers.py index f5f01d789..03ea33cc0 100644 --- a/netbox/extras/api/nested_serializers.py +++ b/netbox/extras/api/nested_serializers.py @@ -2,6 +2,7 @@ from rest_framework import serializers from extras import choices, models from netbox.api import ChoiceField, WritableNestedSerializer +from netbox.api.serializers import NestedTagSerializer from users.api.nested_serializers import NestedUserSerializer __all__ = [ @@ -11,7 +12,7 @@ __all__ = [ 'NestedExportTemplateSerializer', 'NestedImageAttachmentSerializer', 'NestedJobResultSerializer', - 'NestedTagSerializer', + 'NestedTagSerializer', # Defined in netbox.api.serializers 'NestedWebhookSerializer', ] @@ -64,14 +65,6 @@ class NestedImageAttachmentSerializer(WritableNestedSerializer): fields = ['id', 'url', 'name', 'image'] -class NestedTagSerializer(WritableNestedSerializer): - url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail') - - class Meta: - model = models.Tag - fields = ['id', 'url', 'name', 'slug', 'color'] - - class NestedJobResultSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='extras-api:jobresult-detail') status = ChoiceField(choices=choices.JobResultStatusChoices) diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 25b37db7f..8ba1c0700 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -21,7 +21,6 @@ from virtualization.api.nested_serializers import NestedClusterGroupSerializer, from virtualization.models import Cluster, ClusterGroup from .nested_serializers import * - __all__ = ( 'ConfigContextSerializer', 'ContentTypeSerializer', @@ -39,7 +38,6 @@ __all__ = ( 'ScriptOutputSerializer', 'ScriptSerializer', 'TagSerializer', - 'TaggedObjectSerializer', 'WebhookSerializer', ) @@ -131,38 +129,6 @@ class TagSerializer(ValidatedModelSerializer): fields = ['id', 'url', 'name', 'slug', 'color', 'description', 'tagged_items'] -class TaggedObjectSerializer(serializers.Serializer): - tags = NestedTagSerializer(many=True, required=False) - - def create(self, validated_data): - tags = validated_data.pop('tags', None) - instance = super().create(validated_data) - - if tags is not None: - return self._save_tags(instance, tags) - return instance - - def update(self, instance, validated_data): - tags = validated_data.pop('tags', None) - - # Cache tags on instance for change logging - instance._tags = tags or [] - - instance = super().update(instance, validated_data) - - if tags is not None: - return self._save_tags(instance, tags) - return instance - - def _save_tags(self, instance, tags): - if tags: - instance.tags.set(*[t.name for t in tags]) - else: - instance.tags.clear() - - return instance - - # # Image attachments # diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 002ad3b89..77139d2b1 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -6,13 +6,12 @@ from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer -from netbox.api.serializers import CustomFieldModelSerializer -from extras.api.serializers import TaggedObjectSerializer from ipam.choices import * from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField from netbox.api.serializers import OrganizationalModelSerializer +from netbox.api.serializers import PrimaryModelSerializer from tenancy.api.nested_serializers import NestedTenantSerializer from utilities.api import get_serializer_for_model from virtualization.api.nested_serializers import NestedVirtualMachineSerializer @@ -23,7 +22,7 @@ from .nested_serializers import * # VRFs # -class VRFSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class VRFSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail') tenant = NestedTenantSerializer(required=False, allow_null=True) import_targets = SerializedPKRelatedField( @@ -53,7 +52,7 @@ class VRFSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # Route targets # -class RouteTargetSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class RouteTargetSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:routetarget-detail') tenant = NestedTenantSerializer(required=False, allow_null=True) @@ -80,7 +79,7 @@ class RIRSerializer(OrganizationalModelSerializer): ] -class AggregateSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class AggregateSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail') family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) rir = NestedRIRSerializer() @@ -139,7 +138,7 @@ class VLANGroupSerializer(OrganizationalModelSerializer): return data -class VLANSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class VLANSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail') site = NestedSiteSerializer(required=False, allow_null=True) group = NestedVLANGroupSerializer(required=False, allow_null=True) @@ -174,7 +173,7 @@ class VLANSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # Prefixes # -class PrefixSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class PrefixSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail') family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) site = NestedSiteSerializer(required=False, allow_null=True) @@ -244,7 +243,7 @@ class AvailablePrefixSerializer(serializers.Serializer): # IP addresses # -class IPAddressSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class IPAddressSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) vrf = NestedVRFSerializer(required=False, allow_null=True) @@ -302,7 +301,7 @@ class AvailableIPSerializer(serializers.Serializer): # Services # -class ServiceSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class ServiceSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:service-detail') device = NestedDeviceSerializer(required=False, allow_null=True) virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True) diff --git a/netbox/netbox/api/serializers.py b/netbox/netbox/api/serializers.py index 18fc112c8..6be408de6 100644 --- a/netbox/netbox/api/serializers.py +++ b/netbox/netbox/api/serializers.py @@ -6,7 +6,7 @@ from rest_framework.exceptions import ValidationError from rest_framework.fields import CreateOnlyDefault from extras.api.customfields import CustomFieldsDataField, CustomFieldDefaultValues -from extras.models import CustomField +from extras.models import CustomField, Tag from utilities.utils import dict_to_filter_params @@ -70,19 +70,14 @@ class CustomFieldModelSerializer(ValidatedModelSerializer): instance.custom_fields[field.name] = instance.cf.get(field.name) -class OrganizationalModelSerializer(CustomFieldModelSerializer): - pass - - -class NestedGroupModelSerializer(CustomFieldModelSerializer): - _depth = serializers.IntegerField(source='level', read_only=True) - +# +# Nested serializers +# class WritableNestedSerializer(serializers.ModelSerializer): """ Returns a nested representation of an object on read, but accepts only a primary key on write. """ - def to_internal_value(self, data): if data is None: @@ -128,5 +123,71 @@ class WritableNestedSerializer(serializers.ModelSerializer): ) +# +# Nested tags serialization +# + +# Declared here for use by PrimaryModelSerializer, but should be imported from extras.api.nested_serializers +class NestedTagSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail') + + class Meta: + model = Tag + fields = ['id', 'url', 'name', 'slug', 'color'] + + +# +# Base model serializers +# + +class OrganizationalModelSerializer(CustomFieldModelSerializer): + """ + Adds support for custom fields. + """ + pass + + +class PrimaryModelSerializer(CustomFieldModelSerializer): + """ + Adds support for custom fields and tags. + """ + tags = NestedTagSerializer(many=True, required=False) + + def create(self, validated_data): + tags = validated_data.pop('tags', None) + instance = super().create(validated_data) + + if tags is not None: + return self._save_tags(instance, tags) + return instance + + def update(self, instance, validated_data): + tags = validated_data.pop('tags', None) + + # Cache tags on instance for change logging + instance._tags = tags or [] + + instance = super().update(instance, validated_data) + + if tags is not None: + return self._save_tags(instance, tags) + return instance + + def _save_tags(self, instance, tags): + if tags: + instance.tags.set(*[t.name for t in tags]) + else: + instance.tags.clear() + + return instance + + +class NestedGroupModelSerializer(CustomFieldModelSerializer): + """ + Extends OrganizationalModelSerializer to include MPTT support. + """ + _depth = serializers.IntegerField(source='level', read_only=True) + + class BulkOperationSerializer(serializers.Serializer): id = serializers.IntegerField() diff --git a/netbox/secrets/api/serializers.py b/netbox/secrets/api/serializers.py index 207836c4c..a17d70d4e 100644 --- a/netbox/secrets/api/serializers.py +++ b/netbox/secrets/api/serializers.py @@ -2,11 +2,10 @@ from django.contrib.contenttypes.models import ContentType from drf_yasg.utils import swagger_serializer_method from rest_framework import serializers -from netbox.api.serializers import CustomFieldModelSerializer -from extras.api.serializers import TaggedObjectSerializer +from netbox.api import ContentTypeField +from netbox.api.serializers import OrganizationalModelSerializer, PrimaryModelSerializer from secrets.constants import SECRET_ASSIGNMENT_MODELS from secrets.models import Secret, SecretRole -from netbox.api import ContentTypeField, ValidatedModelSerializer from utilities.api import get_serializer_for_model from .nested_serializers import * @@ -15,7 +14,7 @@ from .nested_serializers import * # Secrets # -class SecretRoleSerializer(CustomFieldModelSerializer): +class SecretRoleSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secretrole-detail') secret_count = serializers.IntegerField(read_only=True) @@ -26,7 +25,7 @@ class SecretRoleSerializer(CustomFieldModelSerializer): ] -class SecretSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class SecretSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secret-detail') assigned_object_type = ContentTypeField( queryset=ContentType.objects.filter(SECRET_ASSIGNMENT_MODELS) diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index c701e6b3b..96eb63a60 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -1,7 +1,6 @@ from rest_framework import serializers -from netbox.api.serializers import CustomFieldModelSerializer, NestedGroupModelSerializer -from extras.api.serializers import TaggedObjectSerializer +from netbox.api.serializers import NestedGroupModelSerializer, PrimaryModelSerializer from tenancy.models import Tenant, TenantGroup from .nested_serializers import * @@ -23,7 +22,7 @@ class TenantGroupSerializer(NestedGroupModelSerializer): ] -class TenantSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class TenantSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail') group = NestedTenantGroupSerializer(required=False, allow_null=True) circuit_count = serializers.IntegerField(read_only=True) diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index f2640811b..8f49e2b50 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -3,12 +3,10 @@ from rest_framework import serializers from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer from dcim.choices import InterfaceModeChoices -from netbox.api.serializers import CustomFieldModelSerializer -from extras.api.serializers import TaggedObjectSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.models import VLAN from netbox.api import ChoiceField, SerializedPKRelatedField -from netbox.api.serializers import OrganizationalModelSerializer, ValidatedModelSerializer +from netbox.api.serializers import OrganizationalModelSerializer, PrimaryModelSerializer from tenancy.api.nested_serializers import NestedTenantSerializer from virtualization.choices import * from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface @@ -41,7 +39,7 @@ class ClusterGroupSerializer(OrganizationalModelSerializer): ] -class ClusterSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class ClusterSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail') type = NestedClusterTypeSerializer() group = NestedClusterGroupSerializer(required=False, allow_null=True) @@ -62,7 +60,7 @@ class ClusterSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # Virtual machines # -class VirtualMachineSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class VirtualMachineSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail') status = ChoiceField(choices=VirtualMachineStatusChoices, required=False) site = NestedSiteSerializer(read_only=True) @@ -103,7 +101,7 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer): # VM interfaces # -class VMInterfaceSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): +class VMInterfaceSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:vminterface-detail') virtual_machine = NestedVirtualMachineSerializer() mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)