Przeglądaj źródła

Finished merging writable serializers

Jeremy Stretch 7 lat temu
rodzic
commit
821fb1e01e

+ 2 - 2
netbox/circuits/api/serializers.py

@@ -59,7 +59,7 @@ class CircuitSerializer(CustomFieldModelSerializer):
     provider = NestedProviderSerializer()
     provider = NestedProviderSerializer()
     status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES, required=False)
     status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES, required=False)
     type = NestedCircuitTypeSerializer()
     type = NestedCircuitTypeSerializer()
-    tenant = NestedTenantSerializer(required=False)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = Circuit
         model = Circuit
@@ -84,7 +84,7 @@ class NestedCircuitSerializer(WritableNestedSerializer):
 class CircuitTerminationSerializer(ValidatedModelSerializer):
 class CircuitTerminationSerializer(ValidatedModelSerializer):
     circuit = NestedCircuitSerializer()
     circuit = NestedCircuitSerializer()
     site = NestedSiteSerializer()
     site = NestedSiteSerializer()
-    interface = InterfaceSerializer(required=False)
+    interface = InterfaceSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = CircuitTermination
         model = CircuitTermination

+ 21 - 35
netbox/dcim/api/serializers.py

@@ -37,7 +37,7 @@ class NestedRegionSerializer(WritableNestedSerializer):
 
 
 
 
 class RegionSerializer(serializers.ModelSerializer):
 class RegionSerializer(serializers.ModelSerializer):
-    parent = NestedRegionSerializer(required=False)
+    parent = NestedRegionSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = Region
         model = Region
@@ -50,8 +50,8 @@ class RegionSerializer(serializers.ModelSerializer):
 
 
 class SiteSerializer(CustomFieldModelSerializer):
 class SiteSerializer(CustomFieldModelSerializer):
     status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES, required=False)
     status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES, required=False)
-    region = NestedRegionSerializer(required=False)
-    tenant = NestedTenantSerializer(required=False)
+    region = NestedRegionSerializer(required=False, allow_null=True)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
     time_zone = TimeZoneField(required=False)
     time_zone = TimeZoneField(required=False)
 
 
     class Meta:
     class Meta:
@@ -117,9 +117,9 @@ class NestedRackRoleSerializer(WritableNestedSerializer):
 
 
 class RackSerializer(CustomFieldModelSerializer):
 class RackSerializer(CustomFieldModelSerializer):
     site = NestedSiteSerializer()
     site = NestedSiteSerializer()
-    group = NestedRackGroupSerializer(required=False)
-    tenant = NestedTenantSerializer(required=False)
-    role = NestedRackRoleSerializer(required=False)
+    group = NestedRackGroupSerializer(required=False, allow_null=True)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
+    role = NestedRackRoleSerializer(required=False, allow_null=True)
     type = ChoiceFieldSerializer(choices=RACK_TYPE_CHOICES, required=False)
     type = ChoiceFieldSerializer(choices=RACK_TYPE_CHOICES, required=False)
     width = ChoiceFieldSerializer(choices=RACK_WIDTH_CHOICES, required=False)
     width = ChoiceFieldSerializer(choices=RACK_WIDTH_CHOICES, required=False)
 
 
@@ -186,7 +186,7 @@ class RackUnitSerializer(serializers.Serializer):
 class RackReservationSerializer(ValidatedModelSerializer):
 class RackReservationSerializer(ValidatedModelSerializer):
     rack = NestedRackSerializer()
     rack = NestedRackSerializer()
     user = NestedUserSerializer()
     user = NestedUserSerializer()
-    tenant = NestedTenantSerializer(required=False)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = RackReservation
         model = RackReservation
@@ -337,7 +337,7 @@ class NestedDeviceRoleSerializer(WritableNestedSerializer):
 #
 #
 
 
 class PlatformSerializer(ValidatedModelSerializer):
 class PlatformSerializer(ValidatedModelSerializer):
-    manufacturer = NestedManufacturerSerializer(required=False)
+    manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = Platform
         model = Platform
@@ -387,18 +387,18 @@ class DeviceVirtualChassisSerializer(serializers.ModelSerializer):
 class DeviceSerializer(CustomFieldModelSerializer):
 class DeviceSerializer(CustomFieldModelSerializer):
     device_type = NestedDeviceTypeSerializer()
     device_type = NestedDeviceTypeSerializer()
     device_role = NestedDeviceRoleSerializer()
     device_role = NestedDeviceRoleSerializer()
-    tenant = NestedTenantSerializer(required=False)
-    platform = NestedPlatformSerializer(required=False)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
+    platform = NestedPlatformSerializer(required=False, allow_null=True)
     site = NestedSiteSerializer()
     site = NestedSiteSerializer()
-    rack = NestedRackSerializer(required=False)
+    rack = NestedRackSerializer(required=False, allow_null=True)
     face = ChoiceFieldSerializer(choices=RACK_FACE_CHOICES, required=False)
     face = ChoiceFieldSerializer(choices=RACK_FACE_CHOICES, required=False)
     status = ChoiceFieldSerializer(choices=DEVICE_STATUS_CHOICES, required=False)
     status = ChoiceFieldSerializer(choices=DEVICE_STATUS_CHOICES, required=False)
     primary_ip = DeviceIPAddressSerializer(read_only=True)
     primary_ip = DeviceIPAddressSerializer(read_only=True)
-    primary_ip4 = DeviceIPAddressSerializer(required=False)
-    primary_ip6 = DeviceIPAddressSerializer(required=False)
+    primary_ip4 = DeviceIPAddressSerializer(required=False, allow_null=True)
+    primary_ip6 = DeviceIPAddressSerializer(required=False, allow_null=True)
     parent_device = serializers.SerializerMethodField()
     parent_device = serializers.SerializerMethodField()
-    cluster = NestedClusterSerializer(required=False)
-    virtual_chassis = DeviceVirtualChassisSerializer(required=False)
+    cluster = NestedClusterSerializer(required=False, allow_null=True)
+    virtual_chassis = DeviceVirtualChassisSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = Device
         model = Device
@@ -462,7 +462,7 @@ class NestedConsoleServerPortSerializer(WritableNestedSerializer):
 
 
 class ConsolePortSerializer(ValidatedModelSerializer):
 class ConsolePortSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
-    cs_port = NestedConsoleServerPortSerializer(required=False)
+    cs_port = NestedConsoleServerPortSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = ConsolePort
         model = ConsolePort
@@ -497,7 +497,7 @@ class NestedPowerOutletSerializer(WritableNestedSerializer):
 
 
 class PowerPortSerializer(ValidatedModelSerializer):
 class PowerPortSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
-    power_outlet = NestedPowerOutletSerializer(required=False)
+    power_outlet = NestedPowerOutletSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = PowerPort
         model = PowerPort
@@ -547,11 +547,11 @@ class InterfaceVLANSerializer(WritableNestedSerializer):
 class InterfaceSerializer(ValidatedModelSerializer):
 class InterfaceSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
     form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES, required=False)
     form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES, required=False)
-    lag = NestedInterfaceSerializer(required=False)
+    lag = NestedInterfaceSerializer(required=False, allow_null=True)
     is_connected = serializers.SerializerMethodField(read_only=True)
     is_connected = serializers.SerializerMethodField(read_only=True)
     interface_connection = serializers.SerializerMethodField(read_only=True)
     interface_connection = serializers.SerializerMethodField(read_only=True)
-    circuit_termination = InterfaceCircuitTerminationSerializer(required=False)
-    untagged_vlan = InterfaceVLANSerializer(required=False)
+    circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True)
+    untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
     mode = ChoiceFieldSerializer(choices=IFACE_MODE_CHOICES, required=False)
     mode = ChoiceFieldSerializer(choices=IFACE_MODE_CHOICES, required=False)
     tagged_vlans = InterfaceVLANSerializer(many=True, required=False)
     tagged_vlans = InterfaceVLANSerializer(many=True, required=False)
 
 
@@ -603,27 +603,13 @@ class InterfaceSerializer(ValidatedModelSerializer):
         return None
         return None
 
 
 
 
-# class PeerInterfaceSerializer(serializers.ModelSerializer):
-#     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
-#     device = NestedDeviceSerializer()
-#     form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES)
-#     lag = NestedInterfaceSerializer()
-#
-#     class Meta:
-#         model = Interface
-#         fields = [
-#             'id', 'url', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only',
-#             'description',
-#         ]
-
-
 #
 #
 # Device bays
 # Device bays
 #
 #
 
 
 class DeviceBaySerializer(ValidatedModelSerializer):
 class DeviceBaySerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
-    installed_device = NestedDeviceSerializer(required=False)
+    installed_device = NestedDeviceSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = DeviceBay
         model = DeviceBay

+ 53 - 114
netbox/ipam/api/serializers.py

@@ -14,7 +14,7 @@ from ipam.constants import (
 )
 )
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 from tenancy.api.serializers import NestedTenantSerializer
 from tenancy.api.serializers import NestedTenantSerializer
-from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
+from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer, WritableNestedSerializer
 from virtualization.api.serializers import NestedVirtualMachineSerializer
 from virtualization.api.serializers import NestedVirtualMachineSerializer
 
 
 
 
@@ -23,7 +23,7 @@ from virtualization.api.serializers import NestedVirtualMachineSerializer
 #
 #
 
 
 class VRFSerializer(CustomFieldModelSerializer):
 class VRFSerializer(CustomFieldModelSerializer):
-    tenant = NestedTenantSerializer()
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = VRF
         model = VRF
@@ -33,7 +33,7 @@ class VRFSerializer(CustomFieldModelSerializer):
         ]
         ]
 
 
 
 
-class NestedVRFSerializer(serializers.ModelSerializer):
+class NestedVRFSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail')
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail')
 
 
     class Meta:
     class Meta:
@@ -41,15 +41,6 @@ class NestedVRFSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'name', 'rd']
         fields = ['id', 'url', 'name', 'rd']
 
 
 
 
-class WritableVRFSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = VRF
-        fields = [
-            'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'custom_fields', 'created', 'last_updated',
-        ]
-
-
 #
 #
 # Roles
 # Roles
 #
 #
@@ -61,7 +52,7 @@ class RoleSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug', 'weight']
         fields = ['id', 'name', 'slug', 'weight']
 
 
 
 
-class NestedRoleSerializer(serializers.ModelSerializer):
+class NestedRoleSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:role-detail')
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:role-detail')
 
 
     class Meta:
     class Meta:
@@ -80,7 +71,7 @@ class RIRSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug', 'is_private']
         fields = ['id', 'name', 'slug', 'is_private']
 
 
 
 
-class NestedRIRSerializer(serializers.ModelSerializer):
+class NestedRIRSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail')
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail')
 
 
     class Meta:
     class Meta:
@@ -100,9 +91,10 @@ class AggregateSerializer(CustomFieldModelSerializer):
         fields = [
         fields = [
             'id', 'family', 'prefix', 'rir', 'date_added', 'description', 'custom_fields', 'created', 'last_updated',
             'id', 'family', 'prefix', 'rir', 'date_added', 'description', 'custom_fields', 'created', 'last_updated',
         ]
         ]
+        read_only_fields = ['family']
 
 
 
 
-class NestedAggregateSerializer(serializers.ModelSerializer):
+class NestedAggregateSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail')
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail')
 
 
     class Meta(AggregateSerializer.Meta):
     class Meta(AggregateSerializer.Meta):
@@ -110,34 +102,12 @@ class NestedAggregateSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'family', 'prefix']
         fields = ['id', 'url', 'family', 'prefix']
 
 
 
 
-class WritableAggregateSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = Aggregate
-        fields = ['id', 'prefix', 'rir', 'date_added', 'description', 'custom_fields', 'created', 'last_updated']
-
-
 #
 #
 # VLAN groups
 # VLAN groups
 #
 #
 
 
-class VLANGroupSerializer(serializers.ModelSerializer):
-    site = NestedSiteSerializer()
-
-    class Meta:
-        model = VLANGroup
-        fields = ['id', 'name', 'slug', 'site']
-
-
-class NestedVLANGroupSerializer(serializers.ModelSerializer):
-    url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail')
-
-    class Meta:
-        model = VLANGroup
-        fields = ['id', 'url', 'name', 'slug']
-
-
-class WritableVLANGroupSerializer(serializers.ModelSerializer):
+class VLANGroupSerializer(ValidatedModelSerializer):
+    site = NestedSiteSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = VLANGroup
         model = VLANGroup
@@ -154,21 +124,29 @@ class WritableVLANGroupSerializer(serializers.ModelSerializer):
                 validator(data)
                 validator(data)
 
 
         # Enforce model validation
         # Enforce model validation
-        super(WritableVLANGroupSerializer, self).validate(data)
+        super(VLANGroupSerializer, self).validate(data)
 
 
         return data
         return data
 
 
 
 
+class NestedVLANGroupSerializer(WritableNestedSerializer):
+    url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail')
+
+    class Meta:
+        model = VLANGroup
+        fields = ['id', 'url', 'name', 'slug']
+
+
 #
 #
 # VLANs
 # VLANs
 #
 #
 
 
 class VLANSerializer(CustomFieldModelSerializer):
 class VLANSerializer(CustomFieldModelSerializer):
-    site = NestedSiteSerializer()
-    group = NestedVLANGroupSerializer()
-    tenant = NestedTenantSerializer()
-    status = ChoiceFieldSerializer(choices=VLAN_STATUS_CHOICES)
-    role = NestedRoleSerializer()
+    site = NestedSiteSerializer(required=False, allow_null=True)
+    group = NestedVLANGroupSerializer(required=False, allow_null=True)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
+    status = ChoiceFieldSerializer(choices=VLAN_STATUS_CHOICES, required=False)
+    role = NestedRoleSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = VLAN
         model = VLAN
@@ -176,24 +154,6 @@ class VLANSerializer(CustomFieldModelSerializer):
             'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'display_name',
             'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'display_name',
             'custom_fields', 'created', 'last_updated',
             'custom_fields', 'created', 'last_updated',
         ]
         ]
-
-
-class NestedVLANSerializer(serializers.ModelSerializer):
-    url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
-
-    class Meta:
-        model = VLAN
-        fields = ['id', 'url', 'vid', 'name', 'display_name']
-
-
-class WritableVLANSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = VLAN
-        fields = [
-            'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'custom_fields', 'created',
-            'last_updated',
-        ]
         validators = []
         validators = []
 
 
     def validate(self, data):
     def validate(self, data):
@@ -206,22 +166,30 @@ class WritableVLANSerializer(CustomFieldModelSerializer):
                 validator(data)
                 validator(data)
 
 
         # Enforce model validation
         # Enforce model validation
-        super(WritableVLANSerializer, self).validate(data)
+        super(VLANSerializer, self).validate(data)
 
 
         return data
         return data
 
 
 
 
+class NestedVLANSerializer(WritableNestedSerializer):
+    url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
+
+    class Meta:
+        model = VLAN
+        fields = ['id', 'url', 'vid', 'name', 'display_name']
+
+
 #
 #
 # Prefixes
 # Prefixes
 #
 #
 
 
 class PrefixSerializer(CustomFieldModelSerializer):
 class PrefixSerializer(CustomFieldModelSerializer):
-    site = NestedSiteSerializer()
-    vrf = NestedVRFSerializer()
-    tenant = NestedTenantSerializer()
-    vlan = NestedVLANSerializer()
-    status = ChoiceFieldSerializer(choices=PREFIX_STATUS_CHOICES)
-    role = NestedRoleSerializer()
+    site = NestedSiteSerializer(required=False, allow_null=True)
+    vrf = NestedVRFSerializer(required=False, allow_null=True)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
+    vlan = NestedVLANSerializer(required=False, allow_null=True)
+    status = ChoiceFieldSerializer(choices=PREFIX_STATUS_CHOICES, required=False)
+    role = NestedRoleSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = Prefix
         model = Prefix
@@ -229,9 +197,10 @@ class PrefixSerializer(CustomFieldModelSerializer):
             'id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description',
             'id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description',
             'custom_fields', 'created', 'last_updated',
             'custom_fields', 'created', 'last_updated',
         ]
         ]
+        read_only_fields = ['family']
 
 
 
 
-class NestedPrefixSerializer(serializers.ModelSerializer):
+class NestedPrefixSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail')
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail')
 
 
     class Meta:
     class Meta:
@@ -239,16 +208,6 @@ class NestedPrefixSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'family', 'prefix']
         fields = ['id', 'url', 'family', 'prefix']
 
 
 
 
-class WritablePrefixSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = Prefix
-        fields = [
-            'id', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description',
-            'custom_fields', 'created', 'last_updated',
-        ]
-
-
 class AvailablePrefixSerializer(serializers.Serializer):
 class AvailablePrefixSerializer(serializers.Serializer):
 
 
     def to_representation(self, instance):
     def to_representation(self, instance):
@@ -288,11 +247,11 @@ class IPAddressInterfaceSerializer(serializers.ModelSerializer):
 
 
 
 
 class IPAddressSerializer(CustomFieldModelSerializer):
 class IPAddressSerializer(CustomFieldModelSerializer):
-    vrf = NestedVRFSerializer()
-    tenant = NestedTenantSerializer()
-    status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES)
-    role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES)
-    interface = IPAddressInterfaceSerializer()
+    vrf = NestedVRFSerializer(required=False, allow_null=True)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
+    status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES, required=False)
+    role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES, required=False)
+    interface = IPAddressInterfaceSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = IPAddress
         model = IPAddress
@@ -300,9 +259,10 @@ class IPAddressSerializer(CustomFieldModelSerializer):
             'id', 'family', 'address', 'vrf', 'tenant', 'status', 'role', 'interface', 'description', 'nat_inside',
             'id', 'family', 'address', 'vrf', 'tenant', 'status', 'role', 'interface', 'description', 'nat_inside',
             'nat_outside', 'custom_fields', 'created', 'last_updated',
             'nat_outside', 'custom_fields', 'created', 'last_updated',
         ]
         ]
+        read_only_fields = ['family']
 
 
 
 
-class NestedIPAddressSerializer(serializers.ModelSerializer):
+class NestedIPAddressSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
 
 
     class Meta:
     class Meta:
@@ -310,18 +270,8 @@ class NestedIPAddressSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'family', 'address']
         fields = ['id', 'url', 'family', 'address']
 
 
 
 
-IPAddressSerializer._declared_fields['nat_inside'] = NestedIPAddressSerializer()
-IPAddressSerializer._declared_fields['nat_outside'] = NestedIPAddressSerializer()
-
-
-class WritableIPAddressSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = IPAddress
-        fields = [
-            'id', 'address', 'vrf', 'tenant', 'status', 'role', 'interface', 'description', 'nat_inside',
-            'custom_fields', 'created', 'last_updated',
-        ]
+IPAddressSerializer._declared_fields['nat_inside'] = NestedIPAddressSerializer(required=False, allow_null=True)
+IPAddressSerializer._declared_fields['nat_outside'] = NestedIPAddressSerializer(read_only=True)
 
 
 
 
 class AvailableIPSerializer(serializers.Serializer):
 class AvailableIPSerializer(serializers.Serializer):
@@ -342,22 +292,11 @@ class AvailableIPSerializer(serializers.Serializer):
 # Services
 # Services
 #
 #
 
 
-class ServiceSerializer(serializers.ModelSerializer):
-    device = NestedDeviceSerializer()
-    virtual_machine = NestedVirtualMachineSerializer()
+class ServiceSerializer(ValidatedModelSerializer):
+    device = NestedDeviceSerializer(required=False, allow_null=True)
+    virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True)
     protocol = ChoiceFieldSerializer(choices=IP_PROTOCOL_CHOICES)
     protocol = ChoiceFieldSerializer(choices=IP_PROTOCOL_CHOICES)
-    ipaddresses = NestedIPAddressSerializer(many=True)
-
-    class Meta:
-        model = Service
-        fields = [
-            'id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description', 'created',
-            'last_updated',
-        ]
-
-
-# TODO: Figure out how to use model validation with ManyToManyFields. Calling clean() yields a ValueError.
-class WritableServiceSerializer(serializers.ModelSerializer):
+    ipaddresses = NestedIPAddressSerializer(many=True, required=False)
 
 
     class Meta:
     class Meta:
         model = Service
         model = Service

+ 4 - 11
netbox/ipam/api/views.py

@@ -35,7 +35,6 @@ class IPAMFieldChoicesViewSet(FieldChoicesViewSet):
 class VRFViewSet(CustomFieldModelViewSet):
 class VRFViewSet(CustomFieldModelViewSet):
     queryset = VRF.objects.select_related('tenant')
     queryset = VRF.objects.select_related('tenant')
     serializer_class = serializers.VRFSerializer
     serializer_class = serializers.VRFSerializer
-    write_serializer_class = serializers.WritableVRFSerializer
     filter_class = filters.VRFFilter
     filter_class = filters.VRFFilter
 
 
 
 
@@ -56,7 +55,6 @@ class RIRViewSet(ModelViewSet):
 class AggregateViewSet(CustomFieldModelViewSet):
 class AggregateViewSet(CustomFieldModelViewSet):
     queryset = Aggregate.objects.select_related('rir')
     queryset = Aggregate.objects.select_related('rir')
     serializer_class = serializers.AggregateSerializer
     serializer_class = serializers.AggregateSerializer
-    write_serializer_class = serializers.WritableAggregateSerializer
     filter_class = filters.AggregateFilter
     filter_class = filters.AggregateFilter
 
 
 
 
@@ -77,7 +75,6 @@ class RoleViewSet(ModelViewSet):
 class PrefixViewSet(CustomFieldModelViewSet):
 class PrefixViewSet(CustomFieldModelViewSet):
     queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
     queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
     serializer_class = serializers.PrefixSerializer
     serializer_class = serializers.PrefixSerializer
-    write_serializer_class = serializers.WritablePrefixSerializer
     filter_class = filters.PrefixFilter
     filter_class = filters.PrefixFilter
 
 
     @detail_route(url_path='available-prefixes', methods=['get', 'post'])
     @detail_route(url_path='available-prefixes', methods=['get', 'post'])
@@ -120,9 +117,9 @@ class PrefixViewSet(CustomFieldModelViewSet):
 
 
             # Initialize the serializer with a list or a single object depending on what was requested
             # Initialize the serializer with a list or a single object depending on what was requested
             if isinstance(request.data, list):
             if isinstance(request.data, list):
-                serializer = serializers.WritablePrefixSerializer(data=requested_prefixes, many=True)
+                serializer = serializers.PrefixSerializer(data=requested_prefixes, many=True)
             else:
             else:
-                serializer = serializers.WritablePrefixSerializer(data=requested_prefixes[0])
+                serializer = serializers.PrefixSerializer(data=requested_prefixes[0])
 
 
             # Create the new Prefix(es)
             # Create the new Prefix(es)
             if serializer.is_valid():
             if serializer.is_valid():
@@ -177,9 +174,9 @@ class PrefixViewSet(CustomFieldModelViewSet):
 
 
             # Initialize the serializer with a list or a single object depending on what was requested
             # Initialize the serializer with a list or a single object depending on what was requested
             if isinstance(request.data, list):
             if isinstance(request.data, list):
-                serializer = serializers.WritableIPAddressSerializer(data=requested_ips, many=True)
+                serializer = serializers.IPAddressSerializer(data=requested_ips, many=True)
             else:
             else:
-                serializer = serializers.WritableIPAddressSerializer(data=requested_ips[0])
+                serializer = serializers.IPAddressSerializer(data=requested_ips[0])
 
 
             # Create the new IP address(es)
             # Create the new IP address(es)
             if serializer.is_valid():
             if serializer.is_valid():
@@ -223,7 +220,6 @@ class IPAddressViewSet(CustomFieldModelViewSet):
         'nat_outside'
         'nat_outside'
     )
     )
     serializer_class = serializers.IPAddressSerializer
     serializer_class = serializers.IPAddressSerializer
-    write_serializer_class = serializers.WritableIPAddressSerializer
     filter_class = filters.IPAddressFilter
     filter_class = filters.IPAddressFilter
 
 
 
 
@@ -234,7 +230,6 @@ class IPAddressViewSet(CustomFieldModelViewSet):
 class VLANGroupViewSet(ModelViewSet):
 class VLANGroupViewSet(ModelViewSet):
     queryset = VLANGroup.objects.select_related('site')
     queryset = VLANGroup.objects.select_related('site')
     serializer_class = serializers.VLANGroupSerializer
     serializer_class = serializers.VLANGroupSerializer
-    write_serializer_class = serializers.WritableVLANGroupSerializer
     filter_class = filters.VLANGroupFilter
     filter_class = filters.VLANGroupFilter
 
 
 
 
@@ -245,7 +240,6 @@ class VLANGroupViewSet(ModelViewSet):
 class VLANViewSet(CustomFieldModelViewSet):
 class VLANViewSet(CustomFieldModelViewSet):
     queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
     queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
     serializer_class = serializers.VLANSerializer
     serializer_class = serializers.VLANSerializer
-    write_serializer_class = serializers.WritableVLANSerializer
     filter_class = filters.VLANFilter
     filter_class = filters.VLANFilter
 
 
 
 
@@ -256,5 +250,4 @@ class VLANViewSet(CustomFieldModelViewSet):
 class ServiceViewSet(ModelViewSet):
 class ServiceViewSet(ModelViewSet):
     queryset = Service.objects.select_related('device')
     queryset = Service.objects.select_related('device')
     serializer_class = serializers.ServiceSerializer
     serializer_class = serializers.ServiceSerializer
-    write_serializer_class = serializers.WritableServiceSerializer
     filter_class = filters.ServiceFilter
     filter_class = filters.ServiceFilter

+ 4 - 11
netbox/secrets/api/serializers.py

@@ -5,7 +5,7 @@ from rest_framework.validators import UniqueTogetherValidator
 
 
 from dcim.api.serializers import NestedDeviceSerializer
 from dcim.api.serializers import NestedDeviceSerializer
 from secrets.models import Secret, SecretRole
 from secrets.models import Secret, SecretRole
-from utilities.api import ValidatedModelSerializer
+from utilities.api import ValidatedModelSerializer, WritableNestedSerializer
 
 
 
 
 #
 #
@@ -19,7 +19,7 @@ class SecretRoleSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class NestedSecretRoleSerializer(serializers.ModelSerializer):
+class NestedSecretRoleSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secretrole-detail')
     url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secretrole-detail')
 
 
     class Meta:
     class Meta:
@@ -31,16 +31,9 @@ class NestedSecretRoleSerializer(serializers.ModelSerializer):
 # Secrets
 # Secrets
 #
 #
 
 
-class SecretSerializer(serializers.ModelSerializer):
+class SecretSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
     role = NestedSecretRoleSerializer()
     role = NestedSecretRoleSerializer()
-
-    class Meta:
-        model = Secret
-        fields = ['id', 'device', 'role', 'name', 'plaintext', 'hash', 'created', 'last_updated']
-
-
-class WritableSecretSerializer(serializers.ModelSerializer):
     plaintext = serializers.CharField()
     plaintext = serializers.CharField()
 
 
     class Meta:
     class Meta:
@@ -64,6 +57,6 @@ class WritableSecretSerializer(serializers.ModelSerializer):
             validator(data)
             validator(data)
 
 
         # Enforce model validation
         # Enforce model validation
-        super(WritableSecretSerializer, self).validate(data)
+        super(SecretSerializer, self).validate(data)
 
 
         return data
         return data

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

@@ -51,7 +51,6 @@ class SecretViewSet(ModelViewSet):
         'role__users', 'role__groups',
         'role__users', 'role__groups',
     )
     )
     serializer_class = serializers.SecretSerializer
     serializer_class = serializers.SecretSerializer
-    write_serializer_class = serializers.WritableSecretSerializer
     filter_class = filters.SecretFilter
     filter_class = filters.SecretFilter
 
 
     master_key = None
     master_key = None

+ 3 - 9
netbox/utilities/api.py

@@ -131,6 +131,8 @@ class WritableNestedSerializer(ModelSerializer):
     Returns a nested representation of an object on read, but accepts only a primary key on write.
     Returns a nested representation of an object on read, but accepts only a primary key on write.
     """
     """
     def to_internal_value(self, data):
     def to_internal_value(self, data):
+        if data is None:
+            return None
         try:
         try:
             return self.Meta.model.objects.get(pk=data)
             return self.Meta.model.objects.get(pk=data)
         except ObjectDoesNotExist:
         except ObjectDoesNotExist:
@@ -148,16 +150,8 @@ class ModelViewSet(mixins.CreateModelMixin,
                    mixins.ListModelMixin,
                    mixins.ListModelMixin,
                    GenericViewSet):
                    GenericViewSet):
     """
     """
-    Substitute DRF's built-in ModelViewSet for our own, which introduces a bit of additional functionality:
-    1. Use an alternate serializer (if provided) for write operations
-    2. Accept either a single object or a list of objects to create
+    Accept either a single object or a list of objects to create.
     """
     """
-    def get_serializer_class(self):
-        # Check for a different serializer to use for write operations
-        if self.action in WRITE_OPERATIONS and hasattr(self, 'write_serializer_class'):
-            return self.write_serializer_class
-        return self.serializer_class
-
     def get_serializer(self, *args, **kwargs):
     def get_serializer(self, *args, **kwargs):
         # If a list of objects has been provided, initialize the serializer with many=True
         # If a list of objects has been provided, initialize the serializer with many=True
         if isinstance(kwargs.get('data', {}), list):
         if isinstance(kwargs.get('data', {}), list):

+ 18 - 44
netbox/virtualization/api/serializers.py

@@ -8,7 +8,7 @@ from dcim.models import Interface
 from extras.api.customfields import CustomFieldModelSerializer
 from extras.api.customfields import CustomFieldModelSerializer
 from ipam.models import IPAddress
 from ipam.models import IPAddress
 from tenancy.api.serializers import NestedTenantSerializer
 from tenancy.api.serializers import NestedTenantSerializer
-from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
+from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer, WritableNestedSerializer
 from virtualization.constants import VM_STATUS_CHOICES
 from virtualization.constants import VM_STATUS_CHOICES
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
@@ -24,7 +24,7 @@ class ClusterTypeSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class NestedClusterTypeSerializer(serializers.ModelSerializer):
+class NestedClusterTypeSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustertype-detail')
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustertype-detail')
 
 
     class Meta:
     class Meta:
@@ -43,7 +43,7 @@ class ClusterGroupSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class NestedClusterGroupSerializer(serializers.ModelSerializer):
+class NestedClusterGroupSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustergroup-detail')
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustergroup-detail')
 
 
     class Meta:
     class Meta:
@@ -57,15 +57,15 @@ class NestedClusterGroupSerializer(serializers.ModelSerializer):
 
 
 class ClusterSerializer(CustomFieldModelSerializer):
 class ClusterSerializer(CustomFieldModelSerializer):
     type = NestedClusterTypeSerializer()
     type = NestedClusterTypeSerializer()
-    group = NestedClusterGroupSerializer()
-    site = NestedSiteSerializer()
+    group = NestedClusterGroupSerializer(required=False, allow_null=True)
+    site = NestedSiteSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = Cluster
         model = Cluster
         fields = ['id', 'name', 'type', 'group', 'site', 'comments', 'custom_fields', 'created', 'last_updated']
         fields = ['id', 'name', 'type', 'group', 'site', 'comments', 'custom_fields', 'created', 'last_updated']
 
 
 
 
-class NestedClusterSerializer(serializers.ModelSerializer):
+class NestedClusterSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail')
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail')
 
 
     class Meta:
     class Meta:
@@ -73,13 +73,6 @@ class NestedClusterSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'name']
         fields = ['id', 'url', 'name']
 
 
 
 
-class WritableClusterSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = Cluster
-        fields = ['id', 'name', 'type', 'group', 'site', 'comments', 'custom_fields', 'created', 'last_updated']
-
-
 #
 #
 # Virtual machines
 # Virtual machines
 #
 #
@@ -94,14 +87,14 @@ class VirtualMachineIPAddressSerializer(serializers.ModelSerializer):
 
 
 
 
 class VirtualMachineSerializer(CustomFieldModelSerializer):
 class VirtualMachineSerializer(CustomFieldModelSerializer):
-    status = ChoiceFieldSerializer(choices=VM_STATUS_CHOICES)
-    cluster = NestedClusterSerializer()
-    role = NestedDeviceRoleSerializer()
-    tenant = NestedTenantSerializer()
-    platform = NestedPlatformSerializer()
-    primary_ip = VirtualMachineIPAddressSerializer()
-    primary_ip4 = VirtualMachineIPAddressSerializer()
-    primary_ip6 = VirtualMachineIPAddressSerializer()
+    status = ChoiceFieldSerializer(choices=VM_STATUS_CHOICES, required=False)
+    cluster = NestedClusterSerializer(required=False, allow_null=True)
+    role = NestedDeviceRoleSerializer(required=False, allow_null=True)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
+    platform = NestedPlatformSerializer(required=False, allow_null=True)
+    primary_ip = VirtualMachineIPAddressSerializer(read_only=True)
+    primary_ip4 = VirtualMachineIPAddressSerializer(required=False, allow_null=True)
+    primary_ip6 = VirtualMachineIPAddressSerializer(required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = VirtualMachine
         model = VirtualMachine
@@ -111,7 +104,7 @@ class VirtualMachineSerializer(CustomFieldModelSerializer):
         ]
         ]
 
 
 
 
-class NestedVirtualMachineSerializer(serializers.ModelSerializer):
+class NestedVirtualMachineSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail')
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail')
 
 
     class Meta:
     class Meta:
@@ -119,22 +112,13 @@ class NestedVirtualMachineSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'name']
         fields = ['id', 'url', 'name']
 
 
 
 
-class WritableVirtualMachineSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = VirtualMachine
-        fields = [
-            'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus',
-            'memory', 'disk', 'comments', 'custom_fields', 'created', 'last_updated',
-        ]
-
-
 #
 #
 # VM interfaces
 # VM interfaces
 #
 #
 
 
-class InterfaceSerializer(serializers.ModelSerializer):
+class InterfaceSerializer(ValidatedModelSerializer):
     virtual_machine = NestedVirtualMachineSerializer()
     virtual_machine = NestedVirtualMachineSerializer()
+    form_factor = serializers.IntegerField(default=IFACE_FF_VIRTUAL)
 
 
     class Meta:
     class Meta:
         model = Interface
         model = Interface
@@ -143,19 +127,9 @@ class InterfaceSerializer(serializers.ModelSerializer):
         ]
         ]
 
 
 
 
-class NestedInterfaceSerializer(serializers.ModelSerializer):
+class NestedInterfaceSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:interface-detail')
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:interface-detail')
 
 
     class Meta:
     class Meta:
         model = Interface
         model = Interface
         fields = ['id', 'url', 'name']
         fields = ['id', 'url', 'name']
-
-
-class WritableInterfaceSerializer(ValidatedModelSerializer):
-    form_factor = serializers.IntegerField(default=IFACE_FF_VIRTUAL)
-
-    class Meta:
-        model = Interface
-        fields = [
-            'id', 'name', 'virtual_machine', 'form_factor', 'enabled', 'mac_address', 'mtu', 'description',
-        ]

+ 0 - 3
netbox/virtualization/api/views.py

@@ -37,7 +37,6 @@ class ClusterGroupViewSet(ModelViewSet):
 class ClusterViewSet(CustomFieldModelViewSet):
 class ClusterViewSet(CustomFieldModelViewSet):
     queryset = Cluster.objects.select_related('type', 'group')
     queryset = Cluster.objects.select_related('type', 'group')
     serializer_class = serializers.ClusterSerializer
     serializer_class = serializers.ClusterSerializer
-    write_serializer_class = serializers.WritableClusterSerializer
     filter_class = filters.ClusterFilter
     filter_class = filters.ClusterFilter
 
 
 
 
@@ -48,12 +47,10 @@ class ClusterViewSet(CustomFieldModelViewSet):
 class VirtualMachineViewSet(CustomFieldModelViewSet):
 class VirtualMachineViewSet(CustomFieldModelViewSet):
     queryset = VirtualMachine.objects.all()
     queryset = VirtualMachine.objects.all()
     serializer_class = serializers.VirtualMachineSerializer
     serializer_class = serializers.VirtualMachineSerializer
-    write_serializer_class = serializers.WritableVirtualMachineSerializer
     filter_class = filters.VirtualMachineFilter
     filter_class = filters.VirtualMachineFilter
 
 
 
 
 class InterfaceViewSet(ModelViewSet):
 class InterfaceViewSet(ModelViewSet):
     queryset = Interface.objects.filter(virtual_machine__isnull=False).select_related('virtual_machine')
     queryset = Interface.objects.filter(virtual_machine__isnull=False).select_related('virtual_machine')
     serializer_class = serializers.InterfaceSerializer
     serializer_class = serializers.InterfaceSerializer
-    write_serializer_class = serializers.WritableInterfaceSerializer
     filter_class = filters.InterfaceFilter
     filter_class = filters.InterfaceFilter