Jeremy Stretch 5 лет назад
Родитель
Сommit
fca5accba8

+ 3 - 5
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)

+ 25 - 27
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(

+ 2 - 9
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)

+ 0 - 34
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
 #

+ 8 - 9
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)

+ 70 - 9
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()

+ 4 - 5
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)

+ 2 - 3
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)

+ 4 - 6
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)