Przeglądaj źródła

Started merging writable serializers (WIP)

Jeremy Stretch 7 lat temu
rodzic
commit
7241783249

+ 8 - 37
netbox/circuits/api/serializers.py

@@ -7,7 +7,7 @@ from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
 from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer
 from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer
 from extras.api.customfields import CustomFieldModelSerializer
 from extras.api.customfields import CustomFieldModelSerializer
 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
 
 
 
 
 #
 #
@@ -24,7 +24,7 @@ class ProviderSerializer(CustomFieldModelSerializer):
         ]
         ]
 
 
 
 
-class NestedProviderSerializer(serializers.ModelSerializer):
+class NestedProviderSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
 
 
     class Meta:
     class Meta:
@@ -32,16 +32,6 @@ class NestedProviderSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'name', 'slug']
         fields = ['id', 'url', 'name', 'slug']
 
 
 
 
-class WritableProviderSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = Provider
-        fields = [
-            'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
-            'custom_fields', 'created', 'last_updated',
-        ]
-
-
 #
 #
 # Circuit types
 # Circuit types
 #
 #
@@ -53,7 +43,7 @@ class CircuitTypeSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class NestedCircuitTypeSerializer(serializers.ModelSerializer):
+class NestedCircuitTypeSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail')
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail')
 
 
     class Meta:
     class Meta:
@@ -67,9 +57,9 @@ class NestedCircuitTypeSerializer(serializers.ModelSerializer):
 
 
 class CircuitSerializer(CustomFieldModelSerializer):
 class CircuitSerializer(CustomFieldModelSerializer):
     provider = NestedProviderSerializer()
     provider = NestedProviderSerializer()
-    status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES)
+    status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES, required=False)
     type = NestedCircuitTypeSerializer()
     type = NestedCircuitTypeSerializer()
-    tenant = NestedTenantSerializer()
+    tenant = NestedTenantSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = Circuit
         model = Circuit
@@ -79,7 +69,7 @@ class CircuitSerializer(CustomFieldModelSerializer):
         ]
         ]
 
 
 
 
-class NestedCircuitSerializer(serializers.ModelSerializer):
+class NestedCircuitSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
 
 
     class Meta:
     class Meta:
@@ -87,33 +77,14 @@ class NestedCircuitSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'cid']
         fields = ['id', 'url', 'cid']
 
 
 
 
-class WritableCircuitSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = Circuit
-        fields = [
-            'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
-            'comments', 'custom_fields', 'created', 'last_updated',
-        ]
-
-
 #
 #
 # Circuit Terminations
 # Circuit Terminations
 #
 #
 
 
-class CircuitTerminationSerializer(serializers.ModelSerializer):
+class CircuitTerminationSerializer(ValidatedModelSerializer):
     circuit = NestedCircuitSerializer()
     circuit = NestedCircuitSerializer()
     site = NestedSiteSerializer()
     site = NestedSiteSerializer()
-    interface = InterfaceSerializer()
-
-    class Meta:
-        model = CircuitTermination
-        fields = [
-            'id', 'circuit', 'term_side', 'site', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
-        ]
-
-
-class WritableCircuitTerminationSerializer(ValidatedModelSerializer):
+    interface = InterfaceSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = CircuitTermination
         model = CircuitTermination

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

@@ -30,7 +30,6 @@ class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
 class ProviderViewSet(CustomFieldModelViewSet):
 class ProviderViewSet(CustomFieldModelViewSet):
     queryset = Provider.objects.all()
     queryset = Provider.objects.all()
     serializer_class = serializers.ProviderSerializer
     serializer_class = serializers.ProviderSerializer
-    write_serializer_class = serializers.WritableProviderSerializer
     filter_class = filters.ProviderFilter
     filter_class = filters.ProviderFilter
 
 
     @detail_route()
     @detail_route()
@@ -61,7 +60,6 @@ class CircuitTypeViewSet(ModelViewSet):
 class CircuitViewSet(CustomFieldModelViewSet):
 class CircuitViewSet(CustomFieldModelViewSet):
     queryset = Circuit.objects.select_related('type', 'tenant', 'provider')
     queryset = Circuit.objects.select_related('type', 'tenant', 'provider')
     serializer_class = serializers.CircuitSerializer
     serializer_class = serializers.CircuitSerializer
-    write_serializer_class = serializers.WritableCircuitSerializer
     filter_class = filters.CircuitFilter
     filter_class = filters.CircuitFilter
 
 
 
 
@@ -72,5 +70,4 @@ class CircuitViewSet(CustomFieldModelViewSet):
 class CircuitTerminationViewSet(ModelViewSet):
 class CircuitTerminationViewSet(ModelViewSet):
     queryset = CircuitTermination.objects.select_related('circuit', 'site', 'interface__device')
     queryset = CircuitTermination.objects.select_related('circuit', 'site', 'interface__device')
     serializer_class = serializers.CircuitTerminationSerializer
     serializer_class = serializers.CircuitTerminationSerializer
-    write_serializer_class = serializers.WritableCircuitTerminationSerializer
     filter_class = filters.CircuitTerminationFilter
     filter_class = filters.CircuitTerminationFilter

+ 135 - 298
netbox/dcim/api/serializers.py

@@ -20,7 +20,7 @@ from extras.api.customfields import CustomFieldModelSerializer
 from ipam.models import IPAddress, VLAN
 from ipam.models import IPAddress, VLAN
 from tenancy.api.serializers import NestedTenantSerializer
 from tenancy.api.serializers import NestedTenantSerializer
 from users.api.serializers import NestedUserSerializer
 from users.api.serializers import NestedUserSerializer
-from utilities.api import ChoiceFieldSerializer, TimeZoneField, ValidatedModelSerializer
+from utilities.api import ChoiceFieldSerializer, TimeZoneField, ValidatedModelSerializer, WritableNestedSerializer
 from virtualization.models import Cluster
 from virtualization.models import Cluster
 
 
 
 
@@ -28,7 +28,7 @@ from virtualization.models import Cluster
 # Regions
 # Regions
 #
 #
 
 
-class NestedRegionSerializer(serializers.ModelSerializer):
+class NestedRegionSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
 
 
     class Meta:
     class Meta:
@@ -37,14 +37,7 @@ class NestedRegionSerializer(serializers.ModelSerializer):
 
 
 
 
 class RegionSerializer(serializers.ModelSerializer):
 class RegionSerializer(serializers.ModelSerializer):
-    parent = NestedRegionSerializer()
-
-    class Meta:
-        model = Region
-        fields = ['id', 'name', 'slug', 'parent']
-
-
-class WritableRegionSerializer(ValidatedModelSerializer):
+    parent = NestedRegionSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = Region
         model = Region
@@ -56,9 +49,9 @@ class WritableRegionSerializer(ValidatedModelSerializer):
 #
 #
 
 
 class SiteSerializer(CustomFieldModelSerializer):
 class SiteSerializer(CustomFieldModelSerializer):
-    status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES)
-    region = NestedRegionSerializer()
-    tenant = NestedTenantSerializer()
+    status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES, required=False)
+    region = NestedRegionSerializer(required=False)
+    tenant = NestedTenantSerializer(required=False)
     time_zone = TimeZoneField(required=False)
     time_zone = TimeZoneField(required=False)
 
 
     class Meta:
     class Meta:
@@ -71,7 +64,7 @@ class SiteSerializer(CustomFieldModelSerializer):
         ]
         ]
 
 
 
 
-class NestedSiteSerializer(serializers.ModelSerializer):
+class NestedSiteSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
 
 
     class Meta:
     class Meta:
@@ -79,23 +72,11 @@ class NestedSiteSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'name', 'slug']
         fields = ['id', 'url', 'name', 'slug']
 
 
 
 
-class WritableSiteSerializer(CustomFieldModelSerializer):
-    time_zone = TimeZoneField(required=False)
-
-    class Meta:
-        model = Site
-        fields = [
-            'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description',
-            'physical_address', 'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments',
-            'custom_fields', 'created', 'last_updated',
-        ]
-
-
 #
 #
 # Rack groups
 # Rack groups
 #
 #
 
 
-class RackGroupSerializer(serializers.ModelSerializer):
+class RackGroupSerializer(ValidatedModelSerializer):
     site = NestedSiteSerializer()
     site = NestedSiteSerializer()
 
 
     class Meta:
     class Meta:
@@ -103,7 +84,7 @@ class RackGroupSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'site']
         fields = ['id', 'name', 'slug', 'site']
 
 
 
 
-class NestedRackGroupSerializer(serializers.ModelSerializer):
+class NestedRackGroupSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
 
 
     class Meta:
     class Meta:
@@ -111,13 +92,6 @@ class NestedRackGroupSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'name', 'slug']
         fields = ['id', 'url', 'name', 'slug']
 
 
 
 
-class WritableRackGroupSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = RackGroup
-        fields = ['id', 'name', 'slug', 'site']
-
-
 #
 #
 # Rack roles
 # Rack roles
 #
 #
@@ -129,7 +103,7 @@ class RackRoleSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug', 'color']
         fields = ['id', 'name', 'slug', 'color']
 
 
 
 
-class NestedRackRoleSerializer(serializers.ModelSerializer):
+class NestedRackRoleSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
 
 
     class Meta:
     class Meta:
@@ -143,11 +117,11 @@ class NestedRackRoleSerializer(serializers.ModelSerializer):
 
 
 class RackSerializer(CustomFieldModelSerializer):
 class RackSerializer(CustomFieldModelSerializer):
     site = NestedSiteSerializer()
     site = NestedSiteSerializer()
-    group = NestedRackGroupSerializer()
-    tenant = NestedTenantSerializer()
-    role = NestedRackRoleSerializer()
-    type = ChoiceFieldSerializer(choices=RACK_TYPE_CHOICES)
-    width = ChoiceFieldSerializer(choices=RACK_WIDTH_CHOICES)
+    group = NestedRackGroupSerializer(required=False)
+    tenant = NestedTenantSerializer(required=False)
+    role = NestedRackRoleSerializer(required=False)
+    type = ChoiceFieldSerializer(choices=RACK_TYPE_CHOICES, required=False)
+    width = ChoiceFieldSerializer(choices=RACK_WIDTH_CHOICES, required=False)
 
 
     class Meta:
     class Meta:
         model = Rack
         model = Rack
@@ -155,24 +129,6 @@ class RackSerializer(CustomFieldModelSerializer):
             'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
             'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
             'u_height', 'desc_units', 'comments', 'custom_fields', 'created', 'last_updated',
             'u_height', 'desc_units', 'comments', 'custom_fields', 'created', 'last_updated',
         ]
         ]
-
-
-class NestedRackSerializer(serializers.ModelSerializer):
-    url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
-
-    class Meta:
-        model = Rack
-        fields = ['id', 'url', 'name', 'display_name']
-
-
-class WritableRackSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = Rack
-        fields = [
-            'id', 'name', 'facility_id', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width', 'u_height',
-            'desc_units', 'comments', 'custom_fields', 'created', 'last_updated',
-        ]
         # Omit the UniqueTogetherValidator that would be automatically added to validate (site, facility_id). This
         # Omit the UniqueTogetherValidator that would be automatically added to validate (site, facility_id). This
         # prevents facility_id from being interpreted as a required field.
         # prevents facility_id from being interpreted as a required field.
         validators = [
         validators = [
@@ -188,16 +144,24 @@ class WritableRackSerializer(CustomFieldModelSerializer):
             validator(data)
             validator(data)
 
 
         # Enforce model validation
         # Enforce model validation
-        super(WritableRackSerializer, self).validate(data)
+        super(RackSerializer, self).validate(data)
 
 
         return data
         return data
 
 
 
 
+class NestedRackSerializer(WritableNestedSerializer):
+    url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
+
+    class Meta:
+        model = Rack
+        fields = ['id', 'url', 'name', 'display_name']
+
+
 #
 #
 # Rack units
 # Rack units
 #
 #
 
 
-class NestedDeviceSerializer(serializers.ModelSerializer):
+class NestedDeviceSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
 
 
     class Meta:
     class Meta:
@@ -219,23 +183,16 @@ class RackUnitSerializer(serializers.Serializer):
 # Rack reservations
 # Rack reservations
 #
 #
 
 
-class RackReservationSerializer(serializers.ModelSerializer):
+class RackReservationSerializer(ValidatedModelSerializer):
     rack = NestedRackSerializer()
     rack = NestedRackSerializer()
     user = NestedUserSerializer()
     user = NestedUserSerializer()
-    tenant = NestedTenantSerializer()
+    tenant = NestedTenantSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = RackReservation
         model = RackReservation
         fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description']
         fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description']
 
 
 
 
-class WritableRackReservationSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = RackReservation
-        fields = ['id', 'rack', 'units', 'user', 'description']
-
-
 #
 #
 # Manufacturers
 # Manufacturers
 #
 #
@@ -247,7 +204,7 @@ class ManufacturerSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class NestedManufacturerSerializer(serializers.ModelSerializer):
+class NestedManufacturerSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
 
 
     class Meta:
     class Meta:
@@ -261,8 +218,8 @@ class NestedManufacturerSerializer(serializers.ModelSerializer):
 
 
 class DeviceTypeSerializer(CustomFieldModelSerializer):
 class DeviceTypeSerializer(CustomFieldModelSerializer):
     manufacturer = NestedManufacturerSerializer()
     manufacturer = NestedManufacturerSerializer()
-    interface_ordering = ChoiceFieldSerializer(choices=IFACE_ORDERING_CHOICES)
-    subdevice_role = ChoiceFieldSerializer(choices=SUBDEVICE_ROLE_CHOICES)
+    interface_ordering = ChoiceFieldSerializer(choices=IFACE_ORDERING_CHOICES, required=False)
+    subdevice_role = ChoiceFieldSerializer(choices=SUBDEVICE_ROLE_CHOICES, required=False)
     instance_count = serializers.IntegerField(source='instances.count', read_only=True)
     instance_count = serializers.IntegerField(source='instances.count', read_only=True)
 
 
     class Meta:
     class Meta:
@@ -274,30 +231,20 @@ class DeviceTypeSerializer(CustomFieldModelSerializer):
         ]
         ]
 
 
 
 
-class NestedDeviceTypeSerializer(serializers.ModelSerializer):
+class NestedDeviceTypeSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
-    manufacturer = NestedManufacturerSerializer()
+    manufacturer = NestedManufacturerSerializer(read_only=True)
 
 
     class Meta:
     class Meta:
         model = DeviceType
         model = DeviceType
         fields = ['id', 'url', 'manufacturer', 'model', 'slug']
         fields = ['id', 'url', 'manufacturer', 'model', 'slug']
 
 
 
 
-class WritableDeviceTypeSerializer(CustomFieldModelSerializer):
-
-    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',
-        ]
-
-
 #
 #
 # Console port templates
 # Console port templates
 #
 #
 
 
-class ConsolePortTemplateSerializer(serializers.ModelSerializer):
+class ConsolePortTemplateSerializer(ValidatedModelSerializer):
     device_type = NestedDeviceTypeSerializer()
     device_type = NestedDeviceTypeSerializer()
 
 
     class Meta:
     class Meta:
@@ -305,18 +252,11 @@ class ConsolePortTemplateSerializer(serializers.ModelSerializer):
         fields = ['id', 'device_type', 'name']
         fields = ['id', 'device_type', 'name']
 
 
 
 
-class WritableConsolePortTemplateSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = ConsolePortTemplate
-        fields = ['id', 'device_type', 'name']
-
-
 #
 #
 # Console server port templates
 # Console server port templates
 #
 #
 
 
-class ConsoleServerPortTemplateSerializer(serializers.ModelSerializer):
+class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
     device_type = NestedDeviceTypeSerializer()
     device_type = NestedDeviceTypeSerializer()
 
 
     class Meta:
     class Meta:
@@ -324,18 +264,11 @@ class ConsoleServerPortTemplateSerializer(serializers.ModelSerializer):
         fields = ['id', 'device_type', 'name']
         fields = ['id', 'device_type', 'name']
 
 
 
 
-class WritableConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = ConsoleServerPortTemplate
-        fields = ['id', 'device_type', 'name']
-
-
 #
 #
 # Power port templates
 # Power port templates
 #
 #
 
 
-class PowerPortTemplateSerializer(serializers.ModelSerializer):
+class PowerPortTemplateSerializer(ValidatedModelSerializer):
     device_type = NestedDeviceTypeSerializer()
     device_type = NestedDeviceTypeSerializer()
 
 
     class Meta:
     class Meta:
@@ -343,18 +276,11 @@ class PowerPortTemplateSerializer(serializers.ModelSerializer):
         fields = ['id', 'device_type', 'name']
         fields = ['id', 'device_type', 'name']
 
 
 
 
-class WritablePowerPortTemplateSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = PowerPortTemplate
-        fields = ['id', 'device_type', 'name']
-
-
 #
 #
 # Power outlet templates
 # Power outlet templates
 #
 #
 
 
-class PowerOutletTemplateSerializer(serializers.ModelSerializer):
+class PowerOutletTemplateSerializer(ValidatedModelSerializer):
     device_type = NestedDeviceTypeSerializer()
     device_type = NestedDeviceTypeSerializer()
 
 
     class Meta:
     class Meta:
@@ -362,27 +288,13 @@ class PowerOutletTemplateSerializer(serializers.ModelSerializer):
         fields = ['id', 'device_type', 'name']
         fields = ['id', 'device_type', 'name']
 
 
 
 
-class WritablePowerOutletTemplateSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = PowerOutletTemplate
-        fields = ['id', 'device_type', 'name']
-
-
 #
 #
 # Interface templates
 # Interface templates
 #
 #
 
 
-class InterfaceTemplateSerializer(serializers.ModelSerializer):
+class InterfaceTemplateSerializer(ValidatedModelSerializer):
     device_type = NestedDeviceTypeSerializer()
     device_type = NestedDeviceTypeSerializer()
-    form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES)
-
-    class Meta:
-        model = InterfaceTemplate
-        fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only']
-
-
-class WritableInterfaceTemplateSerializer(ValidatedModelSerializer):
+    form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES, required=False)
 
 
     class Meta:
     class Meta:
         model = InterfaceTemplate
         model = InterfaceTemplate
@@ -393,7 +305,7 @@ class WritableInterfaceTemplateSerializer(ValidatedModelSerializer):
 # Device bay templates
 # Device bay templates
 #
 #
 
 
-class DeviceBayTemplateSerializer(serializers.ModelSerializer):
+class DeviceBayTemplateSerializer(ValidatedModelSerializer):
     device_type = NestedDeviceTypeSerializer()
     device_type = NestedDeviceTypeSerializer()
 
 
     class Meta:
     class Meta:
@@ -401,13 +313,6 @@ class DeviceBayTemplateSerializer(serializers.ModelSerializer):
         fields = ['id', 'device_type', 'name']
         fields = ['id', 'device_type', 'name']
 
 
 
 
-class WritableDeviceBayTemplateSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = DeviceBayTemplate
-        fields = ['id', 'device_type', 'name']
-
-
 #
 #
 # Device roles
 # Device roles
 #
 #
@@ -419,7 +324,7 @@ class DeviceRoleSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug', 'color', 'vm_role']
         fields = ['id', 'name', 'slug', 'color', 'vm_role']
 
 
 
 
-class NestedDeviceRoleSerializer(serializers.ModelSerializer):
+class NestedDeviceRoleSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
 
 
     class Meta:
     class Meta:
@@ -431,15 +336,15 @@ class NestedDeviceRoleSerializer(serializers.ModelSerializer):
 # Platforms
 # Platforms
 #
 #
 
 
-class PlatformSerializer(serializers.ModelSerializer):
-    manufacturer = NestedManufacturerSerializer()
+class PlatformSerializer(ValidatedModelSerializer):
+    manufacturer = NestedManufacturerSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = Platform
         model = Platform
         fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']
         fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']
 
 
 
 
-class NestedPlatformSerializer(serializers.ModelSerializer):
+class NestedPlatformSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
 
 
     class Meta:
     class Meta:
@@ -447,13 +352,6 @@ class NestedPlatformSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'name', 'slug']
         fields = ['id', 'url', 'name', 'slug']
 
 
 
 
-class WritablePlatformSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = Platform
-        fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']
-
-
 #
 #
 # Devices
 # Devices
 #
 #
@@ -489,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()
-    platform = NestedPlatformSerializer()
+    tenant = NestedTenantSerializer(required=False)
+    platform = NestedPlatformSerializer(required=False)
     site = NestedSiteSerializer()
     site = NestedSiteSerializer()
-    rack = NestedRackSerializer()
-    face = ChoiceFieldSerializer(choices=RACK_FACE_CHOICES)
-    status = ChoiceFieldSerializer(choices=DEVICE_STATUS_CHOICES)
-    primary_ip = DeviceIPAddressSerializer()
-    primary_ip4 = DeviceIPAddressSerializer()
-    primary_ip6 = DeviceIPAddressSerializer()
+    rack = NestedRackSerializer(required=False)
+    face = ChoiceFieldSerializer(choices=RACK_FACE_CHOICES, required=False)
+    status = ChoiceFieldSerializer(choices=DEVICE_STATUS_CHOICES, required=False)
+    primary_ip = DeviceIPAddressSerializer(read_only=True)
+    primary_ip4 = DeviceIPAddressSerializer(required=False)
+    primary_ip6 = DeviceIPAddressSerializer(required=False)
     parent_device = serializers.SerializerMethodField()
     parent_device = serializers.SerializerMethodField()
-    cluster = NestedClusterSerializer()
-    virtual_chassis = DeviceVirtualChassisSerializer()
+    cluster = NestedClusterSerializer(required=False)
+    virtual_chassis = DeviceVirtualChassisSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = Device
         model = Device
@@ -510,27 +408,6 @@ class DeviceSerializer(CustomFieldModelSerializer):
             'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'custom_fields', 'created',
             'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'custom_fields', 'created',
             'last_updated',
             'last_updated',
         ]
         ]
-
-    def get_parent_device(self, obj):
-        try:
-            device_bay = obj.parent_bay
-        except DeviceBay.DoesNotExist:
-            return None
-        context = {'request': self.context['request']}
-        data = NestedDeviceSerializer(instance=device_bay.device, context=context).data
-        data['device_bay'] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
-        return data
-
-
-class WritableDeviceSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = Device
-        fields = [
-            'id', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'site', 'rack',
-            'position', 'face', 'status', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position',
-            'vc_priority', 'comments', 'custom_fields', 'created', 'last_updated',
-        ]
         validators = []
         validators = []
 
 
     def validate(self, data):
     def validate(self, data):
@@ -542,8 +419,18 @@ class WritableDeviceSerializer(CustomFieldModelSerializer):
             validator(data)
             validator(data)
 
 
         # Enforce model validation
         # Enforce model validation
-        super(WritableDeviceSerializer, self).validate(data)
+        super(DeviceSerializer, self).validate(data)
+
+        return data
 
 
+    def get_parent_device(self, obj):
+        try:
+            device_bay = obj.parent_bay
+        except DeviceBay.DoesNotExist:
+            return None
+        context = {'request': self.context['request']}
+        data = NestedDeviceSerializer(instance=device_bay.device, context=context).data
+        data['device_bay'] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
         return data
         return data
 
 
 
 
@@ -551,7 +438,7 @@ class WritableDeviceSerializer(CustomFieldModelSerializer):
 # Console server ports
 # Console server ports
 #
 #
 
 
-class ConsoleServerPortSerializer(serializers.ModelSerializer):
+class ConsoleServerPortSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
 
 
     class Meta:
     class Meta:
@@ -560,27 +447,22 @@ class ConsoleServerPortSerializer(serializers.ModelSerializer):
         read_only_fields = ['connected_console']
         read_only_fields = ['connected_console']
 
 
 
 
-class WritableConsoleServerPortSerializer(ValidatedModelSerializer):
+class NestedConsoleServerPortSerializer(WritableNestedSerializer):
+    url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
+    device = NestedDeviceSerializer(read_only=True)
 
 
     class Meta:
     class Meta:
         model = ConsoleServerPort
         model = ConsoleServerPort
-        fields = ['id', 'device', 'name']
+        fields = ['id', 'url', 'device', 'name']
 
 
 
 
 #
 #
 # Console ports
 # Console ports
 #
 #
 
 
-class ConsolePortSerializer(serializers.ModelSerializer):
+class ConsolePortSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
-    cs_port = ConsoleServerPortSerializer()
-
-    class Meta:
-        model = ConsolePort
-        fields = ['id', 'device', 'name', 'cs_port', 'connection_status']
-
-
-class WritableConsolePortSerializer(ValidatedModelSerializer):
+    cs_port = NestedConsoleServerPortSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = ConsolePort
         model = ConsolePort
@@ -591,7 +473,7 @@ class WritableConsolePortSerializer(ValidatedModelSerializer):
 # Power outlets
 # Power outlets
 #
 #
 
 
-class PowerOutletSerializer(serializers.ModelSerializer):
+class PowerOutletSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
 
 
     class Meta:
     class Meta:
@@ -600,27 +482,22 @@ class PowerOutletSerializer(serializers.ModelSerializer):
         read_only_fields = ['connected_port']
         read_only_fields = ['connected_port']
 
 
 
 
-class WritablePowerOutletSerializer(ValidatedModelSerializer):
+class NestedPowerOutletSerializer(WritableNestedSerializer):
+    url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
+    device = NestedDeviceSerializer(read_only=True)
 
 
     class Meta:
     class Meta:
         model = PowerOutlet
         model = PowerOutlet
-        fields = ['id', 'device', 'name']
+        fields = ['id', 'url', 'device', 'name']
 
 
 
 
 #
 #
 # Power ports
 # Power ports
 #
 #
 
 
-class PowerPortSerializer(serializers.ModelSerializer):
+class PowerPortSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
-    power_outlet = PowerOutletSerializer()
-
-    class Meta:
-        model = PowerPort
-        fields = ['id', 'device', 'name', 'power_outlet', 'connection_status']
-
-
-class WritablePowerPortSerializer(ValidatedModelSerializer):
+    power_outlet = NestedPowerOutletSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = PowerPort
         model = PowerPort
@@ -631,12 +508,13 @@ class WritablePowerPortSerializer(ValidatedModelSerializer):
 # Interfaces
 # Interfaces
 #
 #
 
 
-class NestedInterfaceSerializer(serializers.ModelSerializer):
+class NestedInterfaceSerializer(WritableNestedSerializer):
+    device = NestedDeviceSerializer(read_only=True)
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
 
 
     class Meta:
     class Meta:
         model = Interface
         model = Interface
-        fields = ['id', 'url', 'name']
+        fields = ['id', 'url', 'device', 'name']
 
 
 
 
 class InterfaceNestedCircuitSerializer(serializers.ModelSerializer):
 class InterfaceNestedCircuitSerializer(serializers.ModelSerializer):
@@ -647,8 +525,8 @@ class InterfaceNestedCircuitSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'cid']
         fields = ['id', 'url', 'cid']
 
 
 
 
-class InterfaceCircuitTerminationSerializer(serializers.ModelSerializer):
-    circuit = InterfaceNestedCircuitSerializer()
+class InterfaceCircuitTerminationSerializer(WritableNestedSerializer):
+    circuit = InterfaceNestedCircuitSerializer(read_only=True)
 
 
     class Meta:
     class Meta:
         model = CircuitTermination
         model = CircuitTermination
@@ -658,7 +536,7 @@ class InterfaceCircuitTerminationSerializer(serializers.ModelSerializer):
 
 
 
 
 # Cannot import ipam.api.NestedVLANSerializer due to circular dependency
 # Cannot import ipam.api.NestedVLANSerializer due to circular dependency
-class InterfaceVLANSerializer(serializers.ModelSerializer):
+class InterfaceVLANSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
 
 
     class Meta:
     class Meta:
@@ -666,16 +544,16 @@ class InterfaceVLANSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'vid', 'name', 'display_name']
         fields = ['id', 'url', 'vid', 'name', 'display_name']
 
 
 
 
-class InterfaceSerializer(serializers.ModelSerializer):
+class InterfaceSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
-    form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES)
-    lag = NestedInterfaceSerializer()
+    form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES, required=False)
+    lag = NestedInterfaceSerializer(required=False)
     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()
-    untagged_vlan = InterfaceVLANSerializer()
-    mode = ChoiceFieldSerializer(choices=IFACE_MODE_CHOICES)
-    tagged_vlans = InterfaceVLANSerializer(many=True)
+    circuit_termination = InterfaceCircuitTerminationSerializer(required=False)
+    untagged_vlan = InterfaceVLANSerializer(required=False)
+    mode = ChoiceFieldSerializer(choices=IFACE_MODE_CHOICES, required=False)
+    tagged_vlans = InterfaceVLANSerializer(many=True, required=False)
 
 
     class Meta:
     class Meta:
         model = Interface
         model = Interface
@@ -684,6 +562,25 @@ class InterfaceSerializer(serializers.ModelSerializer):
             'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
             'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
         ]
         ]
 
 
+    def validate(self, data):
+
+        # All associated VLANs be global or assigned to the parent device's site.
+        device = self.instance.device if self.instance else data.get('device')
+        untagged_vlan = data.get('untagged_vlan')
+        if untagged_vlan and untagged_vlan.site not in [device.site, None]:
+            raise serializers.ValidationError({
+                'untagged_vlan': "VLAN {} must belong to the same site as the interface's parent device, or it must be "
+                                 "global.".format(untagged_vlan)
+            })
+        for vlan in data.get('tagged_vlans', []):
+            if vlan.site not in [device.site, None]:
+                raise serializers.ValidationError({
+                    'tagged_vlans': "VLAN {} must belong to the same site as the interface's parent device, or it must "
+                                    "be global.".format(vlan)
+                })
+
+        return super(InterfaceSerializer, self).validate(data)
+
     def get_is_connected(self, obj):
     def get_is_connected(self, obj):
         """
         """
         Return True if the interface has a connected interface or circuit termination.
         Return True if the interface has a connected interface or circuit termination.
@@ -700,69 +597,40 @@ class InterfaceSerializer(serializers.ModelSerializer):
     def get_interface_connection(self, obj):
     def get_interface_connection(self, obj):
         if obj.connection:
         if obj.connection:
             return OrderedDict((
             return OrderedDict((
-                ('interface', PeerInterfaceSerializer(obj.connected_interface, context=self.context).data),
+                ('interface', NestedInterfaceSerializer(obj.connected_interface, context=self.context).data),
                 ('status', obj.connection.connection_status),
                 ('status', obj.connection.connection_status),
             ))
             ))
         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',
-        ]
-
-
-class WritableInterfaceSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = Interface
-        fields = [
-            'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
-            'mode', 'untagged_vlan', 'tagged_vlans',
-        ]
-
-    def validate(self, data):
-
-        # All associated VLANs be global or assigned to the parent device's site.
-        device = self.instance.device if self.instance else data.get('device')
-        untagged_vlan = data.get('untagged_vlan')
-        if untagged_vlan and untagged_vlan.site not in [device.site, None]:
-            raise serializers.ValidationError({
-                'untagged_vlan': "VLAN {} must belong to the same site as the interface's parent device, or it must be "
-                                 "global.".format(untagged_vlan)
-            })
-        for vlan in data.get('tagged_vlans', []):
-            if vlan.site not in [device.site, None]:
-                raise serializers.ValidationError({
-                    'tagged_vlans': "VLAN {} must belong to the same site as the interface's parent device, or it must "
-                                    "be global.".format(vlan)
-                })
-
-        return super(WritableInterfaceSerializer, self).validate(data)
+# 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(serializers.ModelSerializer):
+class DeviceBaySerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
-    installed_device = NestedDeviceSerializer()
+    installed_device = NestedDeviceSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = DeviceBay
         model = DeviceBay
         fields = ['id', 'device', 'name', 'installed_device']
         fields = ['id', 'device', 'name', 'installed_device']
 
 
 
 
-class NestedDeviceBaySerializer(serializers.ModelSerializer):
+class NestedDeviceBaySerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
 
 
     class Meta:
     class Meta:
@@ -770,32 +638,15 @@ class NestedDeviceBaySerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'name']
         fields = ['id', 'url', 'name']
 
 
 
 
-class WritableDeviceBaySerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = DeviceBay
-        fields = ['id', 'device', 'name', 'installed_device']
-
-
 #
 #
 # Inventory items
 # Inventory items
 #
 #
 
 
-class InventoryItemSerializer(serializers.ModelSerializer):
+class InventoryItemSerializer(ValidatedModelSerializer):
     device = NestedDeviceSerializer()
     device = NestedDeviceSerializer()
-    manufacturer = NestedManufacturerSerializer()
-
-    class Meta:
-        model = InventoryItem
-        fields = [
-            'id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
-            'description',
-        ]
-
-
-class WritableInventoryItemSerializer(ValidatedModelSerializer):
     # Provide a default value to satisfy UniqueTogetherValidator
     # Provide a default value to satisfy UniqueTogetherValidator
     parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
     parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
+    manufacturer = NestedManufacturerSerializer()
 
 
     class Meta:
     class Meta:
         model = InventoryItem
         model = InventoryItem
@@ -809,17 +660,17 @@ class WritableInventoryItemSerializer(ValidatedModelSerializer):
 # Interface connections
 # Interface connections
 #
 #
 
 
-class InterfaceConnectionSerializer(serializers.ModelSerializer):
-    interface_a = PeerInterfaceSerializer()
-    interface_b = PeerInterfaceSerializer()
-    connection_status = ChoiceFieldSerializer(choices=CONNECTION_STATUS_CHOICES)
+class InterfaceConnectionSerializer(ValidatedModelSerializer):
+    interface_a = NestedInterfaceSerializer()
+    interface_b = NestedInterfaceSerializer()
+    connection_status = ChoiceFieldSerializer(choices=CONNECTION_STATUS_CHOICES, required=False)
 
 
     class Meta:
     class Meta:
         model = InterfaceConnection
         model = InterfaceConnection
         fields = ['id', 'interface_a', 'interface_b', 'connection_status']
         fields = ['id', 'interface_a', 'interface_b', 'connection_status']
 
 
 
 
-class NestedInterfaceConnectionSerializer(serializers.ModelSerializer):
+class NestedInterfaceConnectionSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
 
 
     class Meta:
     class Meta:
@@ -827,18 +678,11 @@ class NestedInterfaceConnectionSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'connection_status']
         fields = ['id', 'url', 'connection_status']
 
 
 
 
-class WritableInterfaceConnectionSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = InterfaceConnection
-        fields = ['id', 'interface_a', 'interface_b', 'connection_status']
-
-
 #
 #
 # Virtual chassis
 # Virtual chassis
 #
 #
 
 
-class VirtualChassisSerializer(serializers.ModelSerializer):
+class VirtualChassisSerializer(ValidatedModelSerializer):
     master = NestedDeviceSerializer()
     master = NestedDeviceSerializer()
 
 
     class Meta:
     class Meta:
@@ -846,16 +690,9 @@ class VirtualChassisSerializer(serializers.ModelSerializer):
         fields = ['id', 'master', 'domain']
         fields = ['id', 'master', 'domain']
 
 
 
 
-class NestedVirtualChassisSerializer(serializers.ModelSerializer):
+class NestedVirtualChassisSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
 
 
     class Meta:
     class Meta:
         model = VirtualChassis
         model = VirtualChassis
         fields = ['id', 'url']
         fields = ['id', 'url']
-
-
-class WritableVirtualChassisSerializer(ValidatedModelSerializer):
-
-    class Meta:
-        model = VirtualChassis
-        fields = ['id', 'master', 'domain']

+ 0 - 23
netbox/dcim/api/views.py

@@ -52,7 +52,6 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
 class RegionViewSet(ModelViewSet):
 class RegionViewSet(ModelViewSet):
     queryset = Region.objects.all()
     queryset = Region.objects.all()
     serializer_class = serializers.RegionSerializer
     serializer_class = serializers.RegionSerializer
-    write_serializer_class = serializers.WritableRegionSerializer
     filter_class = filters.RegionFilter
     filter_class = filters.RegionFilter
 
 
 
 
@@ -63,7 +62,6 @@ class RegionViewSet(ModelViewSet):
 class SiteViewSet(CustomFieldModelViewSet):
 class SiteViewSet(CustomFieldModelViewSet):
     queryset = Site.objects.select_related('region', 'tenant')
     queryset = Site.objects.select_related('region', 'tenant')
     serializer_class = serializers.SiteSerializer
     serializer_class = serializers.SiteSerializer
-    write_serializer_class = serializers.WritableSiteSerializer
     filter_class = filters.SiteFilter
     filter_class = filters.SiteFilter
 
 
     @detail_route()
     @detail_route()
@@ -84,7 +82,6 @@ class SiteViewSet(CustomFieldModelViewSet):
 class RackGroupViewSet(ModelViewSet):
 class RackGroupViewSet(ModelViewSet):
     queryset = RackGroup.objects.select_related('site')
     queryset = RackGroup.objects.select_related('site')
     serializer_class = serializers.RackGroupSerializer
     serializer_class = serializers.RackGroupSerializer
-    write_serializer_class = serializers.WritableRackGroupSerializer
     filter_class = filters.RackGroupFilter
     filter_class = filters.RackGroupFilter
 
 
 
 
@@ -105,7 +102,6 @@ class RackRoleViewSet(ModelViewSet):
 class RackViewSet(CustomFieldModelViewSet):
 class RackViewSet(CustomFieldModelViewSet):
     queryset = Rack.objects.select_related('site', 'group__site', 'tenant')
     queryset = Rack.objects.select_related('site', 'group__site', 'tenant')
     serializer_class = serializers.RackSerializer
     serializer_class = serializers.RackSerializer
-    write_serializer_class = serializers.WritableRackSerializer
     filter_class = filters.RackFilter
     filter_class = filters.RackFilter
 
 
     @detail_route()
     @detail_route()
@@ -136,7 +132,6 @@ class RackViewSet(CustomFieldModelViewSet):
 class RackReservationViewSet(ModelViewSet):
 class RackReservationViewSet(ModelViewSet):
     queryset = RackReservation.objects.select_related('rack', 'user', 'tenant')
     queryset = RackReservation.objects.select_related('rack', 'user', 'tenant')
     serializer_class = serializers.RackReservationSerializer
     serializer_class = serializers.RackReservationSerializer
-    write_serializer_class = serializers.WritableRackReservationSerializer
     filter_class = filters.RackReservationFilter
     filter_class = filters.RackReservationFilter
 
 
     # Assign user from request
     # Assign user from request
@@ -161,7 +156,6 @@ class ManufacturerViewSet(ModelViewSet):
 class DeviceTypeViewSet(CustomFieldModelViewSet):
 class DeviceTypeViewSet(CustomFieldModelViewSet):
     queryset = DeviceType.objects.select_related('manufacturer')
     queryset = DeviceType.objects.select_related('manufacturer')
     serializer_class = serializers.DeviceTypeSerializer
     serializer_class = serializers.DeviceTypeSerializer
-    write_serializer_class = serializers.WritableDeviceTypeSerializer
     filter_class = filters.DeviceTypeFilter
     filter_class = filters.DeviceTypeFilter
 
 
 
 
@@ -172,42 +166,36 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
 class ConsolePortTemplateViewSet(ModelViewSet):
 class ConsolePortTemplateViewSet(ModelViewSet):
     queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer')
     queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer')
     serializer_class = serializers.ConsolePortTemplateSerializer
     serializer_class = serializers.ConsolePortTemplateSerializer
-    write_serializer_class = serializers.WritableConsolePortTemplateSerializer
     filter_class = filters.ConsolePortTemplateFilter
     filter_class = filters.ConsolePortTemplateFilter
 
 
 
 
 class ConsoleServerPortTemplateViewSet(ModelViewSet):
 class ConsoleServerPortTemplateViewSet(ModelViewSet):
     queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer')
     queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer')
     serializer_class = serializers.ConsoleServerPortTemplateSerializer
     serializer_class = serializers.ConsoleServerPortTemplateSerializer
-    write_serializer_class = serializers.WritableConsoleServerPortTemplateSerializer
     filter_class = filters.ConsoleServerPortTemplateFilter
     filter_class = filters.ConsoleServerPortTemplateFilter
 
 
 
 
 class PowerPortTemplateViewSet(ModelViewSet):
 class PowerPortTemplateViewSet(ModelViewSet):
     queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer')
     queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer')
     serializer_class = serializers.PowerPortTemplateSerializer
     serializer_class = serializers.PowerPortTemplateSerializer
-    write_serializer_class = serializers.WritablePowerPortTemplateSerializer
     filter_class = filters.PowerPortTemplateFilter
     filter_class = filters.PowerPortTemplateFilter
 
 
 
 
 class PowerOutletTemplateViewSet(ModelViewSet):
 class PowerOutletTemplateViewSet(ModelViewSet):
     queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer')
     queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer')
     serializer_class = serializers.PowerOutletTemplateSerializer
     serializer_class = serializers.PowerOutletTemplateSerializer
-    write_serializer_class = serializers.WritablePowerOutletTemplateSerializer
     filter_class = filters.PowerOutletTemplateFilter
     filter_class = filters.PowerOutletTemplateFilter
 
 
 
 
 class InterfaceTemplateViewSet(ModelViewSet):
 class InterfaceTemplateViewSet(ModelViewSet):
     queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer')
     queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer')
     serializer_class = serializers.InterfaceTemplateSerializer
     serializer_class = serializers.InterfaceTemplateSerializer
-    write_serializer_class = serializers.WritableInterfaceTemplateSerializer
     filter_class = filters.InterfaceTemplateFilter
     filter_class = filters.InterfaceTemplateFilter
 
 
 
 
 class DeviceBayTemplateViewSet(ModelViewSet):
 class DeviceBayTemplateViewSet(ModelViewSet):
     queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer')
     queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer')
     serializer_class = serializers.DeviceBayTemplateSerializer
     serializer_class = serializers.DeviceBayTemplateSerializer
-    write_serializer_class = serializers.WritableDeviceBayTemplateSerializer
     filter_class = filters.DeviceBayTemplateFilter
     filter_class = filters.DeviceBayTemplateFilter
 
 
 
 
@@ -228,7 +216,6 @@ class DeviceRoleViewSet(ModelViewSet):
 class PlatformViewSet(ModelViewSet):
 class PlatformViewSet(ModelViewSet):
     queryset = Platform.objects.all()
     queryset = Platform.objects.all()
     serializer_class = serializers.PlatformSerializer
     serializer_class = serializers.PlatformSerializer
-    write_serializer_class = serializers.WritablePlatformSerializer
     filter_class = filters.PlatformFilter
     filter_class = filters.PlatformFilter
 
 
 
 
@@ -244,7 +231,6 @@ class DeviceViewSet(CustomFieldModelViewSet):
         'primary_ip4__nat_outside', 'primary_ip6__nat_outside',
         'primary_ip4__nat_outside', 'primary_ip6__nat_outside',
     )
     )
     serializer_class = serializers.DeviceSerializer
     serializer_class = serializers.DeviceSerializer
-    write_serializer_class = serializers.WritableDeviceSerializer
     filter_class = filters.DeviceFilter
     filter_class = filters.DeviceFilter
 
 
     @detail_route(url_path='napalm')
     @detail_route(url_path='napalm')
@@ -318,35 +304,30 @@ class DeviceViewSet(CustomFieldModelViewSet):
 class ConsolePortViewSet(ModelViewSet):
 class ConsolePortViewSet(ModelViewSet):
     queryset = ConsolePort.objects.select_related('device', 'cs_port__device')
     queryset = ConsolePort.objects.select_related('device', 'cs_port__device')
     serializer_class = serializers.ConsolePortSerializer
     serializer_class = serializers.ConsolePortSerializer
-    write_serializer_class = serializers.WritableConsolePortSerializer
     filter_class = filters.ConsolePortFilter
     filter_class = filters.ConsolePortFilter
 
 
 
 
 class ConsoleServerPortViewSet(ModelViewSet):
 class ConsoleServerPortViewSet(ModelViewSet):
     queryset = ConsoleServerPort.objects.select_related('device', 'connected_console__device')
     queryset = ConsoleServerPort.objects.select_related('device', 'connected_console__device')
     serializer_class = serializers.ConsoleServerPortSerializer
     serializer_class = serializers.ConsoleServerPortSerializer
-    write_serializer_class = serializers.WritableConsoleServerPortSerializer
     filter_class = filters.ConsoleServerPortFilter
     filter_class = filters.ConsoleServerPortFilter
 
 
 
 
 class PowerPortViewSet(ModelViewSet):
 class PowerPortViewSet(ModelViewSet):
     queryset = PowerPort.objects.select_related('device', 'power_outlet__device')
     queryset = PowerPort.objects.select_related('device', 'power_outlet__device')
     serializer_class = serializers.PowerPortSerializer
     serializer_class = serializers.PowerPortSerializer
-    write_serializer_class = serializers.WritablePowerPortSerializer
     filter_class = filters.PowerPortFilter
     filter_class = filters.PowerPortFilter
 
 
 
 
 class PowerOutletViewSet(ModelViewSet):
 class PowerOutletViewSet(ModelViewSet):
     queryset = PowerOutlet.objects.select_related('device', 'connected_port__device')
     queryset = PowerOutlet.objects.select_related('device', 'connected_port__device')
     serializer_class = serializers.PowerOutletSerializer
     serializer_class = serializers.PowerOutletSerializer
-    write_serializer_class = serializers.WritablePowerOutletSerializer
     filter_class = filters.PowerOutletFilter
     filter_class = filters.PowerOutletFilter
 
 
 
 
 class InterfaceViewSet(ModelViewSet):
 class InterfaceViewSet(ModelViewSet):
     queryset = Interface.objects.select_related('device')
     queryset = Interface.objects.select_related('device')
     serializer_class = serializers.InterfaceSerializer
     serializer_class = serializers.InterfaceSerializer
-    write_serializer_class = serializers.WritableInterfaceSerializer
     filter_class = filters.InterfaceFilter
     filter_class = filters.InterfaceFilter
 
 
     @detail_route()
     @detail_route()
@@ -363,14 +344,12 @@ class InterfaceViewSet(ModelViewSet):
 class DeviceBayViewSet(ModelViewSet):
 class DeviceBayViewSet(ModelViewSet):
     queryset = DeviceBay.objects.select_related('installed_device')
     queryset = DeviceBay.objects.select_related('installed_device')
     serializer_class = serializers.DeviceBaySerializer
     serializer_class = serializers.DeviceBaySerializer
-    write_serializer_class = serializers.WritableDeviceBaySerializer
     filter_class = filters.DeviceBayFilter
     filter_class = filters.DeviceBayFilter
 
 
 
 
 class InventoryItemViewSet(ModelViewSet):
 class InventoryItemViewSet(ModelViewSet):
     queryset = InventoryItem.objects.select_related('device', 'manufacturer')
     queryset = InventoryItem.objects.select_related('device', 'manufacturer')
     serializer_class = serializers.InventoryItemSerializer
     serializer_class = serializers.InventoryItemSerializer
-    write_serializer_class = serializers.WritableInventoryItemSerializer
     filter_class = filters.InventoryItemFilter
     filter_class = filters.InventoryItemFilter
 
 
 
 
@@ -393,7 +372,6 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
 class InterfaceConnectionViewSet(ModelViewSet):
 class InterfaceConnectionViewSet(ModelViewSet):
     queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')
     queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')
     serializer_class = serializers.InterfaceConnectionSerializer
     serializer_class = serializers.InterfaceConnectionSerializer
-    write_serializer_class = serializers.WritableInterfaceConnectionSerializer
     filter_class = filters.InterfaceConnectionFilter
     filter_class = filters.InterfaceConnectionFilter
 
 
 
 
@@ -404,7 +382,6 @@ class InterfaceConnectionViewSet(ModelViewSet):
 class VirtualChassisViewSet(ModelViewSet):
 class VirtualChassisViewSet(ModelViewSet):
     queryset = VirtualChassis.objects.all()
     queryset = VirtualChassis.objects.all()
     serializer_class = serializers.VirtualChassisSerializer
     serializer_class = serializers.VirtualChassisSerializer
-    write_serializer_class = serializers.WritableVirtualChassisSerializer
 
 
 
 
 #
 #

+ 9 - 9
netbox/dcim/tests/test_api.py

@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
+from django.test.utils import override_settings
 from django.urls import reverse
 from django.urls import reverse
 from rest_framework import status
 from rest_framework import status
 from rest_framework.test import APITestCase
 from rest_framework.test import APITestCase
@@ -2321,6 +2322,7 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
         self.assertEqual(interface4.device_id, data['device'])
         self.assertEqual(interface4.device_id, data['device'])
         self.assertEqual(interface4.name, data['name'])
         self.assertEqual(interface4.name, data['name'])
 
 
+    @override_settings(DEBUG=True)
     def test_create_interface_with_802_1q(self):
     def test_create_interface_with_802_1q(self):
 
 
         data = {
         data = {
@@ -2368,6 +2370,7 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
         self.assertEqual(response.data[1]['name'], data[1]['name'])
         self.assertEqual(response.data[1]['name'], data[1]['name'])
         self.assertEqual(response.data[2]['name'], data[2]['name'])
         self.assertEqual(response.data[2]['name'], data[2]['name'])
 
 
+    @override_settings(DEBUG=True)
     def test_create_interface_802_1q_bulk(self):
     def test_create_interface_802_1q_bulk(self):
 
 
         data = [
         data = [
@@ -2852,9 +2855,9 @@ class InterfaceConnectionTest(HttpStatusMixin, APITestCase):
 
 
         self.assertHttpStatus(response, status.HTTP_201_CREATED)
         self.assertHttpStatus(response, status.HTTP_201_CREATED)
         self.assertEqual(InterfaceConnection.objects.count(), 6)
         self.assertEqual(InterfaceConnection.objects.count(), 6)
-        self.assertEqual(response.data[0]['interface_a'], data[0]['interface_a'])
-        self.assertEqual(response.data[1]['interface_a'], data[1]['interface_a'])
-        self.assertEqual(response.data[2]['interface_a'], data[2]['interface_a'])
+        for i in range(0, 3):
+            self.assertEqual(response.data[i]['interface_a']['id'], data[i]['interface_a'])
+            self.assertEqual(response.data[i]['interface_b']['id'], data[i]['interface_b'])
 
 
     def test_update_interfaceconnection(self):
     def test_update_interfaceconnection(self):
 
 
@@ -3052,12 +3055,9 @@ class VirtualChassisTest(HttpStatusMixin, APITestCase):
         response = self.client.post(url, data, format='json', **self.header)
         response = self.client.post(url, data, format='json', **self.header)
         self.assertHttpStatus(response, status.HTTP_201_CREATED)
         self.assertHttpStatus(response, status.HTTP_201_CREATED)
         self.assertEqual(VirtualChassis.objects.count(), 5)
         self.assertEqual(VirtualChassis.objects.count(), 5)
-        self.assertEqual(response.data[0]['master'], data[0]['master'])
-        self.assertEqual(response.data[0]['domain'], data[0]['domain'])
-        self.assertEqual(response.data[1]['master'], data[1]['master'])
-        self.assertEqual(response.data[1]['domain'], data[1]['domain'])
-        self.assertEqual(response.data[2]['master'], data[2]['master'])
-        self.assertEqual(response.data[2]['domain'], data[2]['domain'])
+        for i in range(0, 3):
+            self.assertEqual(response.data[i]['master']['id'], data[i]['master'])
+            self.assertEqual(response.data[i]['domain'], data[i]['domain'])
 
 
     def test_update_virtualchassis(self):
     def test_update_virtualchassis(self):
 
 

+ 23 - 42
netbox/extras/api/serializers.py

@@ -15,7 +15,7 @@ from utilities.api import ChoiceFieldSerializer, ContentTypeFieldSerializer, Val
 # Graphs
 # Graphs
 #
 #
 
 
-class GraphSerializer(serializers.ModelSerializer):
+class GraphSerializer(ValidatedModelSerializer):
     type = ChoiceFieldSerializer(choices=GRAPH_TYPE_CHOICES)
     type = ChoiceFieldSerializer(choices=GRAPH_TYPE_CHOICES)
 
 
     class Meta:
     class Meta:
@@ -23,13 +23,6 @@ class GraphSerializer(serializers.ModelSerializer):
         fields = ['id', 'type', 'weight', 'name', 'source', 'link']
         fields = ['id', 'type', 'weight', 'name', 'source', 'link']
 
 
 
 
-class WritableGraphSerializer(serializers.ModelSerializer):
-
-    class Meta:
-        model = Graph
-        fields = ['id', 'type', 'weight', 'name', 'source', 'link']
-
-
 class RenderedGraphSerializer(serializers.ModelSerializer):
 class RenderedGraphSerializer(serializers.ModelSerializer):
     embed_url = serializers.SerializerMethodField()
     embed_url = serializers.SerializerMethodField()
     embed_link = serializers.SerializerMethodField()
     embed_link = serializers.SerializerMethodField()
@@ -50,7 +43,7 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
 # Export templates
 # Export templates
 #
 #
 
 
-class ExportTemplateSerializer(serializers.ModelSerializer):
+class ExportTemplateSerializer(ValidatedModelSerializer):
 
 
     class Meta:
     class Meta:
         model = ExportTemplate
         model = ExportTemplate
@@ -61,7 +54,7 @@ class ExportTemplateSerializer(serializers.ModelSerializer):
 # Topology maps
 # Topology maps
 #
 #
 
 
-class TopologyMapSerializer(serializers.ModelSerializer):
+class TopologyMapSerializer(ValidatedModelSerializer):
     site = NestedSiteSerializer()
     site = NestedSiteSerializer()
 
 
     class Meta:
     class Meta:
@@ -69,45 +62,19 @@ class TopologyMapSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'slug', 'site', 'device_patterns', 'description']
         fields = ['id', 'name', 'slug', 'site', 'device_patterns', 'description']
 
 
 
 
-class WritableTopologyMapSerializer(serializers.ModelSerializer):
-
-    class Meta:
-        model = TopologyMap
-        fields = ['id', 'name', 'slug', 'site', 'device_patterns', 'description']
-
-
 #
 #
 # Image attachments
 # Image attachments
 #
 #
 
 
-class ImageAttachmentSerializer(serializers.ModelSerializer):
-    parent = serializers.SerializerMethodField()
-
-    class Meta:
-        model = ImageAttachment
-        fields = ['id', 'parent', 'name', 'image', 'image_height', 'image_width', 'created']
-
-    def get_parent(self, obj):
-
-        # Static mapping of models to their nested serializers
-        if isinstance(obj.parent, Device):
-            serializer = NestedDeviceSerializer
-        elif isinstance(obj.parent, Rack):
-            serializer = NestedRackSerializer
-        elif isinstance(obj.parent, Site):
-            serializer = NestedSiteSerializer
-        else:
-            raise Exception("Unexpected type of parent object for ImageAttachment")
-
-        return serializer(obj.parent, context={'request': self.context['request']}).data
-
-
-class WritableImageAttachmentSerializer(ValidatedModelSerializer):
+class ImageAttachmentSerializer(ValidatedModelSerializer):
     content_type = ContentTypeFieldSerializer()
     content_type = ContentTypeFieldSerializer()
+    parent = serializers.SerializerMethodField(read_only=True)
 
 
     class Meta:
     class Meta:
         model = ImageAttachment
         model = ImageAttachment
-        fields = ['id', 'content_type', 'object_id', 'name', 'image']
+        fields = [
+            'id', 'content_type', 'object_id', 'parent', 'name', 'image', 'image_height', 'image_width', 'created',
+        ]
 
 
     def validate(self, data):
     def validate(self, data):
 
 
@@ -120,10 +87,24 @@ class WritableImageAttachmentSerializer(ValidatedModelSerializer):
             )
             )
 
 
         # Enforce model validation
         # Enforce model validation
-        super(WritableImageAttachmentSerializer, self).validate(data)
+        super(ImageAttachmentSerializer, self).validate(data)
 
 
         return data
         return data
 
 
+    def get_parent(self, obj):
+
+        # Static mapping of models to their nested serializers
+        if isinstance(obj.parent, Device):
+            serializer = NestedDeviceSerializer
+        elif isinstance(obj.parent, Rack):
+            serializer = NestedRackSerializer
+        elif isinstance(obj.parent, Site):
+            serializer = NestedSiteSerializer
+        else:
+            raise Exception("Unexpected type of parent object for ImageAttachment")
+
+        return serializer(obj.parent, context={'request': self.context['request']}).data
+
 
 
 #
 #
 # Reports
 # Reports

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

@@ -67,7 +67,6 @@ class CustomFieldModelViewSet(ModelViewSet):
 class GraphViewSet(ModelViewSet):
 class GraphViewSet(ModelViewSet):
     queryset = Graph.objects.all()
     queryset = Graph.objects.all()
     serializer_class = serializers.GraphSerializer
     serializer_class = serializers.GraphSerializer
-    write_serializer_class = serializers.WritableGraphSerializer
     filter_class = filters.GraphFilter
     filter_class = filters.GraphFilter
 
 
 
 
@@ -88,7 +87,6 @@ class ExportTemplateViewSet(ModelViewSet):
 class TopologyMapViewSet(ModelViewSet):
 class TopologyMapViewSet(ModelViewSet):
     queryset = TopologyMap.objects.select_related('site')
     queryset = TopologyMap.objects.select_related('site')
     serializer_class = serializers.TopologyMapSerializer
     serializer_class = serializers.TopologyMapSerializer
-    write_serializer_class = serializers.WritableTopologyMapSerializer
     filter_class = filters.TopologyMapFilter
     filter_class = filters.TopologyMapFilter
 
 
     @detail_route()
     @detail_route()
@@ -118,7 +116,6 @@ class TopologyMapViewSet(ModelViewSet):
 class ImageAttachmentViewSet(ModelViewSet):
 class ImageAttachmentViewSet(ModelViewSet):
     queryset = ImageAttachment.objects.all()
     queryset = ImageAttachment.objects.all()
     serializer_class = serializers.ImageAttachmentSerializer
     serializer_class = serializers.ImageAttachmentSerializer
-    write_serializer_class = serializers.WritableImageAttachmentSerializer
 
 
 
 
 #
 #

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

@@ -4,7 +4,7 @@ from rest_framework import serializers
 
 
 from extras.api.customfields import CustomFieldModelSerializer
 from extras.api.customfields import CustomFieldModelSerializer
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
-from utilities.api import ValidatedModelSerializer
+from utilities.api import ValidatedModelSerializer, WritableNestedSerializer
 
 
 
 
 #
 #
@@ -18,7 +18,7 @@ class TenantGroupSerializer(ValidatedModelSerializer):
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class NestedTenantGroupSerializer(serializers.ModelSerializer):
+class NestedTenantGroupSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail')
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail')
 
 
     class Meta:
     class Meta:
@@ -31,23 +31,16 @@ class NestedTenantGroupSerializer(serializers.ModelSerializer):
 #
 #
 
 
 class TenantSerializer(CustomFieldModelSerializer):
 class TenantSerializer(CustomFieldModelSerializer):
-    group = NestedTenantGroupSerializer()
+    group = NestedTenantGroupSerializer(required=False)
 
 
     class Meta:
     class Meta:
         model = Tenant
         model = Tenant
         fields = ['id', 'name', 'slug', 'group', 'description', 'comments', 'custom_fields', 'created', 'last_updated']
         fields = ['id', 'name', 'slug', 'group', 'description', 'comments', 'custom_fields', 'created', 'last_updated']
 
 
 
 
-class NestedTenantSerializer(serializers.ModelSerializer):
+class NestedTenantSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail')
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail')
 
 
     class Meta:
     class Meta:
         model = Tenant
         model = Tenant
         fields = ['id', 'url', 'name', 'slug']
         fields = ['id', 'url', 'name', 'slug']
-
-
-class WritableTenantSerializer(CustomFieldModelSerializer):
-
-    class Meta:
-        model = Tenant
-        fields = ['id', 'name', 'slug', 'group', 'description', 'comments', 'custom_fields', 'created', 'last_updated']

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

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

+ 3 - 2
netbox/users/api/serializers.py

@@ -1,10 +1,11 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
-from rest_framework import serializers
 
 
+from utilities.api import WritableNestedSerializer
 
 
-class NestedUserSerializer(serializers.ModelSerializer):
+
+class NestedUserSerializer(WritableNestedSerializer):
 
 
     class Meta:
     class Meta:
         model = User
         model = User