Parcourir la source

Implemented static writable ModelSerializers for all models

Jeremy Stretch il y a 8 ans
Parent
commit
bb1f97abc2

+ 22 - 13
netbox/circuits/api/serializers.py

@@ -2,7 +2,7 @@ from rest_framework import serializers
 
 from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
 from dcim.api.serializers import NestedSiteSerializer, DeviceInterfaceSerializer
-from extras.api.serializers import CustomFieldSerializer
+from extras.api.serializers import CustomFieldValueSerializer
 from tenancy.api.serializers import NestedTenantSerializer
 
 
@@ -10,17 +10,18 @@ from tenancy.api.serializers import NestedTenantSerializer
 # Providers
 #
 
-class ProviderSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class ProviderSerializer(serializers.ModelSerializer):
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = Provider
         fields = [
             'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
-            'custom_fields',
+            'custom_field_values',
         ]
 
 
-class NestedProviderSerializer(serializers.HyperlinkedModelSerializer):
+class NestedProviderSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
 
     class Meta:
@@ -28,6 +29,15 @@ class NestedProviderSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'name', 'slug']
 
 
+class WritableProviderSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Provider
+        fields = [
+            'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
+        ]
+
+
 #
 # Circuit types
 #
@@ -39,7 +49,7 @@ class CircuitTypeSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug']
 
 
-class NestedCircuitTypeSerializer(serializers.HyperlinkedModelSerializer):
+class NestedCircuitTypeSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail')
 
     class Meta:
@@ -64,20 +74,21 @@ class CircuitTerminationSerializer(serializers.ModelSerializer):
 # Circuits
 #
 
-class CircuitSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class CircuitSerializer(serializers.ModelSerializer):
     provider = NestedProviderSerializer()
     type = NestedCircuitTypeSerializer()
     tenant = NestedTenantSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = Circuit
         fields = [
             'id', 'cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
-            'custom_fields',
+            'custom_field_values',
         ]
 
 
-class NestedCircuitSerializer(serializers.HyperlinkedModelSerializer):
+class NestedCircuitSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
 
     class Meta:
@@ -85,12 +96,10 @@ class NestedCircuitSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'cid']
 
 
-# TODO: Delete this
-class CircuitDetailSerializer(CircuitSerializer):
-    terminations = CircuitTerminationSerializer(many=True)
+class WritableCircuitSerializer(serializers.ModelSerializer):
 
-    class Meta(CircuitSerializer.Meta):
+    class Meta:
+        model = Circuit
         fields = [
             'id', 'cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
-            'terminations', 'custom_fields',
         ]

+ 0 - 3
netbox/circuits/api/urls.py

@@ -2,9 +2,6 @@ from django.conf.urls import include, url
 
 from rest_framework import routers
 
-from extras.models import GRAPH_TYPE_PROVIDER
-from extras.api.views import GraphListView
-
 from . import views
 
 

+ 3 - 1
netbox/circuits/api/views.py

@@ -21,9 +21,10 @@ from . import serializers
 # Providers
 #
 
-class ProviderViewSet(CustomFieldModelViewSet):
+class ProviderViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = Provider.objects.all()
     serializer_class = serializers.ProviderSerializer
+    write_serializer_class = serializers.WritableProviderSerializer
 
     @detail_route()
     def graphs(self, request, pk=None):
@@ -49,6 +50,7 @@ class CircuitTypeViewSet(ModelViewSet):
 class CircuitViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = Circuit.objects.select_related('type', 'tenant', 'provider')
     serializer_class = serializers.CircuitSerializer
+    write_serializer_class = serializers.WritableCircuitSerializer
     filter_class = CircuitFilter
 
 

+ 90 - 55
netbox/dcim/api/serializers.py

@@ -4,10 +4,10 @@ from ipam.models import IPAddress
 from dcim.models import (
     ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceType,
     DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
-    PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
-    SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
+    PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, Site, SUBDEVICE_ROLE_CHILD,
+    SUBDEVICE_ROLE_PARENT,
 )
-from extras.api.serializers import CustomFieldSerializer
+from extras.api.serializers import CustomFieldValueSerializer
 from tenancy.api.serializers import NestedTenantSerializer
 
 
@@ -15,19 +15,20 @@ from tenancy.api.serializers import NestedTenantSerializer
 # Sites
 #
 
-class SiteSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class SiteSerializer(serializers.ModelSerializer):
     tenant = NestedTenantSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = Site
         fields = [
             'id', 'name', 'slug', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'contact_name',
-            'contact_phone', 'contact_email', 'comments', 'custom_fields', 'count_prefixes', 'count_vlans',
+            'contact_phone', 'contact_email', 'comments', 'custom_field_values', 'count_prefixes', 'count_vlans',
             'count_racks', 'count_devices', 'count_circuits',
         ]
 
 
-class NestedSiteSerializer(serializers.HyperlinkedModelSerializer):
+class NestedSiteSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
 
     class Meta:
@@ -35,6 +36,16 @@ class NestedSiteSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'name', 'slug']
 
 
+class WritableSiteSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Site
+        fields = [
+            'id', 'name', 'slug', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'contact_name',
+            'contact_phone', 'contact_email', 'comments',
+        ]
+
+
 #
 # Rack groups
 #
@@ -47,7 +58,7 @@ class RackGroupSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'site']
 
 
-class NestedRackGroupSerializer(serializers.HyperlinkedModelSerializer):
+class NestedRackGroupSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
 
     class Meta:
@@ -55,6 +66,13 @@ class NestedRackGroupSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'name', 'slug']
 
 
+class WritableRackGroupSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = RackGroup
+        fields = ['id', 'name', 'slug', 'site']
+
+
 #
 # Rack roles
 #
@@ -66,7 +84,7 @@ class RackRoleSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'color']
 
 
-class NestedRackRoleSerializer(serializers.HyperlinkedModelSerializer):
+class NestedRackRoleSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
 
     class Meta:
@@ -79,21 +97,22 @@ class NestedRackRoleSerializer(serializers.HyperlinkedModelSerializer):
 #
 
 
-class RackSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class RackSerializer(serializers.ModelSerializer):
     site = NestedSiteSerializer()
     group = NestedRackGroupSerializer()
     tenant = NestedTenantSerializer()
     role = NestedRackRoleSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = Rack
         fields = [
             'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'type', 'width', 'u_height',
-            'desc_units', 'comments', 'custom_fields',
+            'desc_units', 'comments', 'custom_field_values',
         ]
 
 
-class NestedRackSerializer(serializers.HyperlinkedModelSerializer):
+class NestedRackSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
 
     class Meta:
@@ -101,28 +120,15 @@ class NestedRackSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'name', 'display_name']
 
 
-class RackDetailSerializer(RackSerializer):
-    front_units = serializers.SerializerMethodField()
-    rear_units = serializers.SerializerMethodField()
+class WritableRackSerializer(serializers.ModelSerializer):
 
-    class Meta(RackSerializer.Meta):
+    class Meta:
+        model = Rack
         fields = [
-            'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'type', 'width', 'u_height',
-            'desc_units', 'comments', 'custom_fields', 'front_units', 'rear_units',
+            'id', 'name', 'facility_id', 'site', 'group', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units',
+            'comments',
         ]
 
-    def get_front_units(self, obj):
-        units = obj.get_rack_units(face=RACK_FACE_FRONT)
-        for u in units:
-            u['device'] = NestedDeviceSerializer(u['device']).data if u['device'] else None
-        return units
-
-    def get_rear_units(self, obj):
-        units = obj.get_rack_units(face=RACK_FACE_REAR)
-        for u in units:
-            u['device'] = NestedDeviceSerializer(u['device']).data if u['device'] else None
-        return units
-
 
 #
 # Manufacturers
@@ -135,7 +141,7 @@ class ManufacturerSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug']
 
 
-class NestedManufacturerSerializer(serializers.HyperlinkedModelSerializer):
+class NestedManufacturerSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
 
     class Meta:
@@ -147,16 +153,17 @@ class NestedManufacturerSerializer(serializers.HyperlinkedModelSerializer):
 # Device types
 #
 
-class DeviceTypeSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class DeviceTypeSerializer(serializers.ModelSerializer):
     manufacturer = NestedManufacturerSerializer()
     subdevice_role = serializers.SerializerMethodField()
     instance_count = serializers.IntegerField(source='instances.count', read_only=True)
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = DeviceType
         fields = [
             'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
-            'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'custom_fields',
+            'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'custom_field_values',
             'instance_count',
         ]
 
@@ -168,7 +175,7 @@ class DeviceTypeSerializer(CustomFieldSerializer, serializers.ModelSerializer):
         }[obj.subdevice_role]
 
 
-class NestedDeviceTypeSerializer(serializers.HyperlinkedModelSerializer):
+class NestedDeviceTypeSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
     manufacturer = NestedManufacturerSerializer()
 
@@ -177,6 +184,16 @@ class NestedDeviceTypeSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'manufacturer', 'model', 'slug']
 
 
+class WritableDeviceTypeSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = DeviceType
+        fields = [
+            'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
+            'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments',
+        ]
+
+
 class ConsolePortTemplateSerializer(serializers.ModelSerializer):
 
     class Meta:
@@ -230,7 +247,7 @@ class DeviceRoleSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'color']
 
 
-class NestedDeviceRoleSerializer(serializers.HyperlinkedModelSerializer):
+class NestedDeviceRoleSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
 
     class Meta:
@@ -249,7 +266,7 @@ class PlatformSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'rpc_client']
 
 
-class NestedPlatformSerializer(serializers.HyperlinkedModelSerializer):
+class NestedPlatformSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
 
     class Meta:
@@ -262,7 +279,7 @@ class NestedPlatformSerializer(serializers.HyperlinkedModelSerializer):
 #
 
 # Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency
-class DeviceIPAddressSerializer(serializers.HyperlinkedModelSerializer):
+class DeviceIPAddressSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
 
     class Meta:
@@ -270,7 +287,7 @@ class DeviceIPAddressSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'family', 'address']
 
 
-class DeviceSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class DeviceSerializer(serializers.ModelSerializer):
     device_type = NestedDeviceTypeSerializer()
     device_role = NestedDeviceRoleSerializer()
     tenant = NestedTenantSerializer()
@@ -280,13 +297,14 @@ class DeviceSerializer(CustomFieldSerializer, serializers.ModelSerializer):
     primary_ip4 = DeviceIPAddressSerializer()
     primary_ip6 = DeviceIPAddressSerializer()
     parent_device = serializers.SerializerMethodField()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = Device
         fields = [
             'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial',  'asset_tag',
             'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
-            'comments', 'custom_fields',
+            'comments', 'custom_field_values',
         ]
 
     def get_parent_device(self, obj):
@@ -304,7 +322,7 @@ class DeviceSerializer(CustomFieldSerializer, serializers.ModelSerializer):
         }
 
 
-class NestedDeviceSerializer(serializers.HyperlinkedModelSerializer):
+class NestedDeviceSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
 
     class Meta:
@@ -312,19 +330,29 @@ class NestedDeviceSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'name', 'display_name']
 
 
+class WritableDeviceSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Device
+        fields = [
+            'id', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial',  'asset_tag', 'rack',
+            'position', 'face', 'status', 'primary_ip4', 'primary_ip6', 'comments',
+        ]
+
+
 #
 # Console server ports
 #
 
 class ConsoleServerPortSerializer(serializers.ModelSerializer):
-    device = NestedDeviceSerializer()
+    device = NestedDeviceSerializer(read_only=True)
 
     class Meta:
         model = ConsoleServerPort
         fields = ['id', 'device', 'name', 'connected_console']
 
 
-class DeviceConsoleServerPortSerializer(serializers.HyperlinkedModelSerializer):
+class DeviceConsoleServerPortSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
 
     class Meta:
@@ -338,7 +366,7 @@ class DeviceConsoleServerPortSerializer(serializers.HyperlinkedModelSerializer):
 #
 
 class ConsolePortSerializer(serializers.ModelSerializer):
-    device = NestedDeviceSerializer()
+    device = NestedDeviceSerializer(read_only=True)
     cs_port = ConsoleServerPortSerializer()
 
     class Meta:
@@ -346,7 +374,7 @@ class ConsolePortSerializer(serializers.ModelSerializer):
         fields = ['id', 'device', 'name', 'cs_port', 'connection_status']
 
 
-class DeviceConsolePortSerializer(serializers.HyperlinkedModelSerializer):
+class DeviceConsolePortSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
 
     class Meta:
@@ -360,14 +388,14 @@ class DeviceConsolePortSerializer(serializers.HyperlinkedModelSerializer):
 #
 
 class PowerOutletSerializer(serializers.ModelSerializer):
-    device = NestedDeviceSerializer()
+    device = NestedDeviceSerializer(read_only=True)
 
     class Meta:
         model = PowerOutlet
         fields = ['id', 'device', 'name', 'connected_port']
 
 
-class DevicePowerOutletSerializer(serializers.HyperlinkedModelSerializer):
+class DevicePowerOutletSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
 
     class Meta:
@@ -381,7 +409,7 @@ class DevicePowerOutletSerializer(serializers.HyperlinkedModelSerializer):
 #
 
 class PowerPortSerializer(serializers.ModelSerializer):
-    device = NestedDeviceSerializer()
+    device = NestedDeviceSerializer(read_only=True)
     power_outlet = PowerOutletSerializer()
 
     class Meta:
@@ -389,7 +417,7 @@ class PowerPortSerializer(serializers.ModelSerializer):
         fields = ['id', 'device', 'name', 'power_outlet', 'connection_status']
 
 
-class DevicePowerPortSerializer(serializers.HyperlinkedModelSerializer):
+class DevicePowerPortSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
 
     class Meta:
@@ -404,7 +432,7 @@ class DevicePowerPortSerializer(serializers.HyperlinkedModelSerializer):
 
 
 class InterfaceSerializer(serializers.ModelSerializer):
-    device = NestedDeviceSerializer()
+    device = NestedDeviceSerializer(read_only=True)
     connection = serializers.SerializerMethodField(read_only=True)
     connected_interface = serializers.SerializerMethodField(read_only=True)
 
@@ -426,7 +454,7 @@ class InterfaceSerializer(serializers.ModelSerializer):
         return None
 
 
-class PeerInterfaceSerializer(serializers.HyperlinkedModelSerializer):
+class PeerInterfaceSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
     device = NestedDeviceSerializer()
 
@@ -435,7 +463,7 @@ class PeerInterfaceSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'device', 'name', 'form_factor', 'mac_address', 'mgmt_only', 'description']
 
 
-class DeviceInterfaceSerializer(serializers.HyperlinkedModelSerializer):
+class DeviceInterfaceSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
     connection = serializers.SerializerMethodField()
 
@@ -462,7 +490,7 @@ class InterfaceConnectionSerializer(serializers.ModelSerializer):
         fields = ['id', 'interface_a', 'interface_b', 'connection_status']
 
 
-class NestedInterfaceConnectionSerializer(serializers.HyperlinkedModelSerializer):
+class NestedInterfaceConnectionSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
 
     class Meta:
@@ -470,12 +498,19 @@ class NestedInterfaceConnectionSerializer(serializers.HyperlinkedModelSerializer
         fields = ['id', 'url', 'connection_status']
 
 
+class WritableInterfaceConnectionSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = InterfaceConnection
+        fields = ['id', 'interface_a', 'interface_b', 'connection_status']
+
+
 #
 # Device bays
 #
 
 class DeviceBaySerializer(serializers.ModelSerializer):
-    device = NestedDeviceSerializer()
+    device = NestedDeviceSerializer(read_only=True)
     installed_device = NestedDeviceSerializer()
 
     class Meta:
@@ -483,7 +518,7 @@ class DeviceBaySerializer(serializers.ModelSerializer):
         fields = ['id', 'device', 'name', 'installed_device']
 
 
-class DeviceDeviceBaySerializer(serializers.HyperlinkedModelSerializer):
+class DeviceDeviceBaySerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
 
     class Meta:
@@ -497,7 +532,7 @@ class DeviceDeviceBaySerializer(serializers.HyperlinkedModelSerializer):
 #
 
 class ModuleSerializer(serializers.ModelSerializer):
-    device = NestedDeviceSerializer()
+    device = NestedDeviceSerializer(read_only=True)
     manufacturer = NestedManufacturerSerializer()
 
     class Meta:
@@ -505,7 +540,7 @@ class ModuleSerializer(serializers.ModelSerializer):
         fields = ['id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'discovered']
 
 
-class DeviceModuleSerializer(serializers.HyperlinkedModelSerializer):
+class DeviceModuleSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
     manufacturer = NestedManufacturerSerializer()
 

+ 1 - 3
netbox/dcim/api/urls.py

@@ -2,10 +2,8 @@ from django.conf.urls import include, url
 
 from rest_framework import routers
 
-from extras.models import GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
-from extras.api.views import GraphListView, TopologyMapView
+from extras.api.views import TopologyMapView
 from ipam.api.views import ServiceViewSet, DeviceServiceViewSet
-
 from . import views
 
 

+ 7 - 1
netbox/dcim/api/views.py

@@ -33,6 +33,7 @@ from . import serializers
 class SiteViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = Site.objects.select_related('tenant')
     serializer_class = serializers.SiteSerializer
+    write_serializer_class = serializers.WritableSiteSerializer
 
     @detail_route()
     def graphs(self, request, pk=None):
@@ -50,6 +51,7 @@ class RackGroupViewSet(WritableSerializerMixin, ModelViewSet):
     queryset = RackGroup.objects.select_related('site')
     serializer_class = serializers.RackGroupSerializer
     filter_class = filters.RackGroupFilter
+    write_serializer_class = serializers.WritableRackGroupSerializer
 
 
 #
@@ -68,6 +70,7 @@ class RackRoleViewSet(ModelViewSet):
 class RackViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = Rack.objects.select_related('site', 'group__site', 'tenant')
     serializer_class = serializers.RackSerializer
+    write_serializer_class = serializers.WritableRackSerializer
     filter_class = filters.RackFilter
 
     @detail_route(url_path='rack-units')
@@ -112,6 +115,7 @@ class ManufacturerViewSet(ModelViewSet):
 class DeviceTypeViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = DeviceType.objects.select_related('manufacturer')
     serializer_class = serializers.DeviceTypeSerializer
+    write_serializer_class = serializers.WritableDeviceTypeSerializer
 
 
 #
@@ -143,6 +147,7 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
         'primary_ip4__nat_outside', 'primary_ip6__nat_outside',
     )
     serializer_class = serializers.DeviceSerializer
+    write_serializer_class = serializers.WritableDeviceSerializer
     filter_class = filters.DeviceFilter
     renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer]
 
@@ -335,9 +340,10 @@ class DeviceModuleViewSet(CreateModelMixin, ListModelMixin, GenericViewSet):
 # Interface connections
 #
 
-class InterfaceConnectionViewSet(ModelViewSet):
+class InterfaceConnectionViewSet(WritableSerializerMixin, ModelViewSet):
     queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')
     serializer_class = serializers.InterfaceConnectionSerializer
+    write_serializer_class = serializers.WritableInterfaceConnectionSerializer
 
 
 #

+ 36 - 29
netbox/extras/api/serializers.py

@@ -1,35 +1,42 @@
 from rest_framework import serializers
 
-from extras.models import CF_TYPE_SELECT, CustomFieldChoice, Graph
+from extras.models import CF_TYPE_SELECT, CustomFieldChoice, CustomFieldValue, Graph
+
+
+# class CustomFieldSerializer(serializers.ModelSerializer):
+#     """
+#     Extends ModelSerializer to render any CustomFields and their values associated with an object.
+#     """
+#     custom_fields = serializers.SerializerMethodField()
+#
+#     def get_custom_fields(self, obj):
+#
+#         # Gather all CustomFields applicable to this object
+#         fields = {cf.name: None for cf in self.context['custom_fields']}
+#         custom_field_choices = self.context['custom_field_choices']
+#
+#         # Attach any defined CustomFieldValues to their respective CustomFields
+#         for cfv in obj.custom_field_values.all():
+#
+#             # Attempt to suppress database lookups for CustomFieldChoices by using the cached choice set from the view
+#             # context.
+#             if cfv.field.type == CF_TYPE_SELECT:
+#                 cfc = {
+#                     'id': int(cfv.serialized_value),
+#                     'value': custom_field_choices[int(cfv.serialized_value)]
+#                 }
+#                 fields[cfv.field.name] = CustomFieldChoiceSerializer(instance=cfc).data
+#             else:
+#                 fields[cfv.field.name] = cfv.value
+#
+#         return fields
+
+
+class CustomFieldValueSerializer(serializers.ModelSerializer):
 
-
-class CustomFieldSerializer(serializers.Serializer):
-    """
-    Extends a ModelSerializer to render any CustomFields and their values associated with an object.
-    """
-    custom_fields = serializers.SerializerMethodField()
-
-    def get_custom_fields(self, obj):
-
-        # Gather all CustomFields applicable to this object
-        fields = {cf.name: None for cf in self.context['custom_fields']}
-        custom_field_choices = self.context['custom_field_choices']
-
-        # Attach any defined CustomFieldValues to their respective CustomFields
-        for cfv in obj.custom_field_values.all():
-
-            # Attempt to suppress database lookups for CustomFieldChoices by using the cached choice set from the view
-            # context.
-            if cfv.field.type == CF_TYPE_SELECT:
-                cfc = {
-                    'id': int(cfv.serialized_value),
-                    'value': custom_field_choices[int(cfv.serialized_value)]
-                }
-                fields[cfv.field.name] = CustomFieldChoiceSerializer(instance=cfc).data
-            else:
-                fields[cfv.field.name] = cfv.value
-
-        return fields
+    class Meta:
+        model = CustomFieldValue
+        fields = ['field', 'serialized_value']
 
 
 class CustomFieldChoiceSerializer(serializers.ModelSerializer):

+ 72 - 22
netbox/ipam/api/serializers.py

@@ -1,25 +1,25 @@
 from rest_framework import serializers
 
 from dcim.api.serializers import NestedDeviceSerializer, DeviceInterfaceSerializer, NestedSiteSerializer
-from extras.api.serializers import CustomFieldSerializer
+from extras.api.serializers import CustomFieldValueSerializer
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 from tenancy.api.serializers import NestedTenantSerializer
-from utilities.api import WritableSerializerMixin
 
 
 #
 # VRFs
 #
 
-class VRFSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class VRFSerializer(serializers.ModelSerializer):
     tenant = NestedTenantSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = VRF
-        fields = ['id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'custom_fields']
+        fields = ['id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'custom_field_values']
 
 
-class NestedVRFSerializer(serializers.HyperlinkedModelSerializer):
+class NestedVRFSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail')
 
     class Meta:
@@ -27,6 +27,13 @@ class NestedVRFSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'name', 'rd']
 
 
+class WritableVRFSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = VRF
+        fields = ['id', 'name', 'rd', 'tenant', 'enforce_unique', 'description']
+
+
 #
 # Roles
 #
@@ -38,7 +45,7 @@ class RoleSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'weight']
 
 
-class NestedRoleSerializer(serializers.HyperlinkedModelSerializer):
+class NestedRoleSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:role-detail')
 
     class Meta:
@@ -57,7 +64,7 @@ class RIRSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'is_private']
 
 
-class NestedRIRSerializer(serializers.HyperlinkedModelSerializer):
+class NestedRIRSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail')
 
     class Meta:
@@ -69,15 +76,16 @@ class NestedRIRSerializer(serializers.HyperlinkedModelSerializer):
 # Aggregates
 #
 
-class AggregateSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class AggregateSerializer(serializers.ModelSerializer):
     rir = NestedRIRSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = Aggregate
-        fields = ['id', 'family', 'prefix', 'rir', 'date_added', 'description', 'custom_fields']
+        fields = ['id', 'family', 'prefix', 'rir', 'date_added', 'description', 'custom_field_values']
 
 
-class NestedAggregateSerializer(serializers.HyperlinkedModelSerializer):
+class NestedAggregateSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail')
 
     class Meta(AggregateSerializer.Meta):
@@ -85,11 +93,18 @@ class NestedAggregateSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'family', 'prefix']
 
 
+class WritableAggregateSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Aggregate
+        fields = ['id', 'family', 'prefix', 'rir', 'date_added', 'description']
+
+
 #
 # VLAN groups
 #
 
-class VLANGroupSerializer(WritableSerializerMixin, serializers.ModelSerializer):
+class VLANGroupSerializer(serializers.ModelSerializer):
     site = NestedSiteSerializer()
 
     class Meta:
@@ -97,7 +112,7 @@ class VLANGroupSerializer(WritableSerializerMixin, serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'site']
 
 
-class NestedVLANGroupSerializer(serializers.HyperlinkedModelSerializer):
+class NestedVLANGroupSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail')
 
     class Meta:
@@ -105,25 +120,33 @@ class NestedVLANGroupSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'name', 'slug']
 
 
+class WritableVLANGroupSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = VLANGroup
+        fields = ['id', 'name', 'slug', 'site']
+
+
 #
 # VLANs
 #
 
-class VLANSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class VLANSerializer(serializers.ModelSerializer):
     site = NestedSiteSerializer()
     group = NestedVLANGroupSerializer()
     tenant = NestedTenantSerializer()
     role = NestedRoleSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = VLAN
         fields = [
             'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'display_name',
-            'custom_fields',
+            'custom_field_values',
         ]
 
 
-class NestedVLANSerializer(serializers.HyperlinkedModelSerializer):
+class NestedVLANSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
 
     class Meta:
@@ -131,26 +154,36 @@ class NestedVLANSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'vid', 'name', 'display_name']
 
 
+class WritableVLANSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = VLAN
+        fields = [
+            'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description',
+        ]
+
+
 #
 # Prefixes
 #
 
-class PrefixSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class PrefixSerializer(serializers.ModelSerializer):
     site = NestedSiteSerializer()
     vrf = NestedVRFSerializer()
     tenant = NestedTenantSerializer()
     vlan = NestedVLANSerializer()
     role = NestedRoleSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = Prefix
         fields = [
             'id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description',
-            'custom_fields',
+            'custom_field_values',
         ]
 
 
-class NestedPrefixSerializer(serializers.HyperlinkedModelSerializer):
+class NestedPrefixSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail')
 
     class Meta:
@@ -158,24 +191,34 @@ class NestedPrefixSerializer(serializers.HyperlinkedModelSerializer):
         fields = ['id', 'url', 'family', 'prefix']
 
 
+class WritablePrefixSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Prefix
+        fields = [
+            'id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description',
+        ]
+
+
 #
 # IP addresses
 #
 
-class IPAddressSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class IPAddressSerializer(serializers.ModelSerializer):
     vrf = NestedVRFSerializer()
     tenant = NestedTenantSerializer()
     interface = DeviceInterfaceSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = IPAddress
         fields = [
             'id', 'family', 'address', 'vrf', 'tenant', 'status', 'interface', 'description', 'nat_inside',
-            'nat_outside', 'custom_fields',
+            'nat_outside', 'custom_field_values',
         ]
 
 
-class NestedIPAddressSerializer(serializers.HyperlinkedModelSerializer):
+class NestedIPAddressSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
 
     class Meta:
@@ -186,6 +229,13 @@ IPAddressSerializer._declared_fields['nat_inside'] = NestedIPAddressSerializer()
 IPAddressSerializer._declared_fields['nat_outside'] = NestedIPAddressSerializer()
 
 
+class WritableIPAddressSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = IPAddress
+        fields = ['id', 'family', 'address', 'vrf', 'tenant', 'status', 'interface', 'description', 'nat_inside']
+
+
 #
 # Services
 #
@@ -199,7 +249,7 @@ class ServiceSerializer(serializers.ModelSerializer):
         fields = ['id', 'device', 'name', 'port', 'protocol', 'ipaddresses', 'description']
 
 
-class DeviceServiceSerializer(serializers.HyperlinkedModelSerializer):
+class DeviceServiceSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:service-detail')
     ipaddresses = NestedIPAddressSerializer(many=True)
 

+ 6 - 0
netbox/ipam/api/views.py

@@ -20,6 +20,7 @@ from . import serializers
 class VRFViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = VRF.objects.select_related('tenant')
     serializer_class = serializers.VRFSerializer
+    write_serializer_class = serializers.WritableVRFSerializer
     filter_class = filters.VRFFilter
 
 
@@ -48,6 +49,7 @@ class RIRViewSet(ModelViewSet):
 class AggregateViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = Aggregate.objects.select_related('rir')
     serializer_class = serializers.AggregateSerializer
+    write_serializer_class = serializers.WritableAggregateSerializer
     filter_class = filters.AggregateFilter
 
 
@@ -58,6 +60,7 @@ class AggregateViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
     serializer_class = serializers.PrefixSerializer
+    write_serializer_class = serializers.WritablePrefixSerializer
     filter_class = filters.PrefixFilter
 
 
@@ -68,6 +71,7 @@ class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside')
     serializer_class = serializers.IPAddressSerializer
+    write_serializer_class = serializers.WritableIPAddressSerializer
     filter_class = filters.IPAddressFilter
 
 
@@ -78,6 +82,7 @@ class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 class VLANGroupViewSet(WritableSerializerMixin, ModelViewSet):
     queryset = VLANGroup.objects.select_related('site')
     serializer_class = serializers.VLANGroupSerializer
+    write_serializer_class = serializers.WritableVLANGroupSerializer
     filter_class = filters.VLANGroupFilter
 
 
@@ -88,6 +93,7 @@ class VLANGroupViewSet(WritableSerializerMixin, ModelViewSet):
 class VLANViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
     serializer_class = serializers.VLANSerializer
+    write_serializer_class = serializers.WritableVLANSerializer
     filter_class = filters.VLANFilter
 
 

+ 1 - 1
netbox/secrets/api/serializers.py

@@ -15,7 +15,7 @@ class SecretRoleSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug']
 
 
-class NestedSecretRoleSerializer(serializers.HyperlinkedModelSerializer):
+class NestedSecretRoleSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secretrole-detail')
 
     class Meta:

+ 13 - 5
netbox/tenancy/api/serializers.py

@@ -1,6 +1,6 @@
 from rest_framework import serializers
 
-from extras.api.serializers import CustomFieldSerializer
+from extras.api.serializers import CustomFieldValueSerializer
 from tenancy.models import Tenant, TenantGroup
 
 
@@ -15,7 +15,7 @@ class TenantGroupSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug']
 
 
-class NestedTenantGroupSerializer(serializers.HyperlinkedModelSerializer):
+class NestedTenantGroupSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail')
 
     class Meta:
@@ -27,17 +27,25 @@ class NestedTenantGroupSerializer(serializers.HyperlinkedModelSerializer):
 # Tenants
 #
 
-class TenantSerializer(CustomFieldSerializer, serializers.ModelSerializer):
+class TenantSerializer(serializers.ModelSerializer):
     group = NestedTenantGroupSerializer()
+    custom_field_values = CustomFieldValueSerializer(many=True)
 
     class Meta:
         model = Tenant
-        fields = ['id', 'name', 'slug', 'group', 'description', 'comments', 'custom_fields']
+        fields = ['id', 'name', 'slug', 'group', 'description', 'comments', 'custom_field_values']
 
 
-class NestedTenantSerializer(serializers.HyperlinkedModelSerializer):
+class NestedTenantSerializer(serializers.ModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail')
 
     class Meta:
         model = Tenant
         fields = ['id', 'url', 'name', 'slug']
+
+
+class WritableTenantSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Tenant
+        fields = ['id', 'name', 'slug', 'group', 'description', 'comments']

+ 1 - 0
netbox/tenancy/api/views.py

@@ -24,4 +24,5 @@ class TenantGroupViewSet(ModelViewSet):
 class TenantViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     queryset = Tenant.objects.select_related('group')
     serializer_class = serializers.TenantSerializer
+    write_serializer_class = serializers.WritableTenantSerializer
     filter_class = TenantFilter

+ 3 - 11
netbox/utilities/api.py

@@ -12,18 +12,10 @@ class ServiceUnavailable(APIException):
 
 class WritableSerializerMixin(object):
     """
-    Returns a flat Serializer from the given model suitable for write operations (POST, PUT, PATCH). This is necessary
-    to allow write operations on objects which utilize nested serializers.
+    Allow for the use of an alternate, writable serializer class for write operations (e.g. POST, PUT).
     """
 
     def get_serializer_class(self):
-
-        class WritableSerializer(ModelSerializer):
-
-            class Meta(self.serializer_class.Meta):
-                pass
-
-        if self.action in WRITE_OPERATIONS:
-            return WritableSerializer
-
+        if self.action in WRITE_OPERATIONS and hasattr(self, 'write_serializer_class'):
+            return self.write_serializer_class
         return self.serializer_class