Merge pull request #2024 from digitalocean/1794-writable-nested-serializers

1794 writable nested serializers
This commit is contained in:
Jeremy Stretch 2018-04-16 10:46:23 -04:00 committed by GitHub
commit 7805848e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 355 additions and 694 deletions

View File

@ -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, allow_null=True)
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() interface = InterfaceSerializer(required=False, allow_null=True)
class Meta:
model = CircuitTermination
fields = [
'id', 'circuit', 'term_side', 'site', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
]
class WritableCircuitTerminationSerializer(ValidatedModelSerializer):
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination

View File

@ -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

View File

@ -20,7 +20,9 @@ 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, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer, WritableNestedSerializer,
)
from virtualization.models import Cluster from virtualization.models import Cluster
@ -28,7 +30,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 +39,7 @@ class NestedRegionSerializer(serializers.ModelSerializer):
class RegionSerializer(serializers.ModelSerializer): class RegionSerializer(serializers.ModelSerializer):
parent = NestedRegionSerializer() parent = NestedRegionSerializer(required=False, allow_null=True)
class Meta:
model = Region
fields = ['id', 'name', 'slug', 'parent']
class WritableRegionSerializer(ValidatedModelSerializer):
class Meta: class Meta:
model = Region model = Region
@ -56,9 +51,9 @@ class WritableRegionSerializer(ValidatedModelSerializer):
# #
class SiteSerializer(CustomFieldModelSerializer): class SiteSerializer(CustomFieldModelSerializer):
status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES) status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES, required=False)
region = NestedRegionSerializer() region = NestedRegionSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True)
time_zone = TimeZoneField(required=False) time_zone = TimeZoneField(required=False)
class Meta: class Meta:
@ -71,7 +66,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 +74,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 +86,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 +94,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 +105,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 +119,11 @@ class NestedRackRoleSerializer(serializers.ModelSerializer):
class RackSerializer(CustomFieldModelSerializer): class RackSerializer(CustomFieldModelSerializer):
site = NestedSiteSerializer() site = NestedSiteSerializer()
group = NestedRackGroupSerializer() group = NestedRackGroupSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True)
role = NestedRackRoleSerializer() role = NestedRackRoleSerializer(required=False, allow_null=True)
type = ChoiceFieldSerializer(choices=RACK_TYPE_CHOICES) type = ChoiceFieldSerializer(choices=RACK_TYPE_CHOICES, required=False)
width = ChoiceFieldSerializer(choices=RACK_WIDTH_CHOICES) width = ChoiceFieldSerializer(choices=RACK_WIDTH_CHOICES, required=False)
class Meta: class Meta:
model = Rack model = Rack
@ -155,24 +131,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 +146,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 +185,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, allow_null=True)
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 +206,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 +220,8 @@ class NestedManufacturerSerializer(serializers.ModelSerializer):
class DeviceTypeSerializer(CustomFieldModelSerializer): class DeviceTypeSerializer(CustomFieldModelSerializer):
manufacturer = NestedManufacturerSerializer() manufacturer = NestedManufacturerSerializer()
interface_ordering = ChoiceFieldSerializer(choices=IFACE_ORDERING_CHOICES) interface_ordering = ChoiceFieldSerializer(choices=IFACE_ORDERING_CHOICES, required=False)
subdevice_role = ChoiceFieldSerializer(choices=SUBDEVICE_ROLE_CHOICES) 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 +233,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 +254,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 +266,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 +278,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 +290,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) form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES, required=False)
class Meta:
model = InterfaceTemplate
fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only']
class WritableInterfaceTemplateSerializer(ValidatedModelSerializer):
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate
@ -393,7 +307,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 +315,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 +326,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 +338,15 @@ class NestedDeviceRoleSerializer(serializers.ModelSerializer):
# Platforms # Platforms
# #
class PlatformSerializer(serializers.ModelSerializer): class PlatformSerializer(ValidatedModelSerializer):
manufacturer = NestedManufacturerSerializer() manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
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 +354,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 +389,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() tenant = NestedTenantSerializer(required=False, allow_null=True)
platform = NestedPlatformSerializer() platform = NestedPlatformSerializer(required=False, allow_null=True)
site = NestedSiteSerializer() site = NestedSiteSerializer()
rack = NestedRackSerializer() rack = NestedRackSerializer(required=False, allow_null=True)
face = ChoiceFieldSerializer(choices=RACK_FACE_CHOICES) face = ChoiceFieldSerializer(choices=RACK_FACE_CHOICES, required=False)
status = ChoiceFieldSerializer(choices=DEVICE_STATUS_CHOICES) status = ChoiceFieldSerializer(choices=DEVICE_STATUS_CHOICES, required=False)
primary_ip = DeviceIPAddressSerializer() primary_ip = DeviceIPAddressSerializer(read_only=True)
primary_ip4 = DeviceIPAddressSerializer() primary_ip4 = DeviceIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = DeviceIPAddressSerializer() primary_ip6 = DeviceIPAddressSerializer(required=False, allow_null=True)
parent_device = serializers.SerializerMethodField() parent_device = serializers.SerializerMethodField()
cluster = NestedClusterSerializer() cluster = NestedClusterSerializer(required=False, allow_null=True)
virtual_chassis = DeviceVirtualChassisSerializer() virtual_chassis = DeviceVirtualChassisSerializer(required=False, allow_null=True)
class Meta: class Meta:
model = Device model = Device
@ -510,27 +410,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,16 +421,26 @@ 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 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
# #
# Console server ports # Console server ports
# #
class ConsoleServerPortSerializer(serializers.ModelSerializer): class ConsoleServerPortSerializer(ValidatedModelSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
class Meta: class Meta:
@ -560,27 +449,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() cs_port = NestedConsoleServerPortSerializer(required=False, allow_null=True)
class Meta:
model = ConsolePort
fields = ['id', 'device', 'name', 'cs_port', 'connection_status']
class WritableConsolePortSerializer(ValidatedModelSerializer):
class Meta: class Meta:
model = ConsolePort model = ConsolePort
@ -591,7 +475,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 +484,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() power_outlet = NestedPowerOutletSerializer(required=False, allow_null=True)
class Meta:
model = PowerPort
fields = ['id', 'device', 'name', 'power_outlet', 'connection_status']
class WritablePowerPortSerializer(ValidatedModelSerializer):
class Meta: class Meta:
model = PowerPort model = PowerPort
@ -631,12 +510,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 +527,8 @@ class InterfaceNestedCircuitSerializer(serializers.ModelSerializer):
fields = ['id', 'url', 'cid'] fields = ['id', 'url', 'cid']
class InterfaceCircuitTerminationSerializer(serializers.ModelSerializer): class InterfaceCircuitTerminationSerializer(WritableNestedSerializer):
circuit = InterfaceNestedCircuitSerializer() circuit = InterfaceNestedCircuitSerializer(read_only=True)
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination
@ -658,7 +538,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 +546,21 @@ 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) form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES, required=False)
lag = NestedInterfaceSerializer() 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() circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True)
untagged_vlan = InterfaceVLANSerializer() mode = ChoiceFieldSerializer(choices=IFACE_MODE_CHOICES, required=False)
mode = ChoiceFieldSerializer(choices=IFACE_MODE_CHOICES) untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
tagged_vlans = InterfaceVLANSerializer(many=True) tagged_vlans = SerializedPKRelatedField(
queryset=VLAN.objects.all(),
serializer=InterfaceVLANSerializer,
required=False,
many=True
)
class Meta: class Meta:
model = Interface model = Interface
@ -684,51 +569,6 @@ 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 get_is_connected(self, obj):
"""
Return True if the interface has a connected interface or circuit termination.
"""
if obj.connection:
return True
try:
circuit_termination = obj.circuit_termination
return True
except CircuitTermination.DoesNotExist:
pass
return False
def get_interface_connection(self, obj):
if obj.connection:
return OrderedDict((
('interface', PeerInterfaceSerializer(obj.connected_interface, context=self.context).data),
('status', obj.connection.connection_status),
))
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): def validate(self, data):
# All associated VLANs be global or assigned to the parent device's site. # All associated VLANs be global or assigned to the parent device's site.
@ -746,23 +586,44 @@ class WritableInterfaceSerializer(ValidatedModelSerializer):
"be global.".format(vlan) "be global.".format(vlan)
}) })
return super(WritableInterfaceSerializer, self).validate(data) return super(InterfaceSerializer, self).validate(data)
def get_is_connected(self, obj):
"""
Return True if the interface has a connected interface or circuit termination.
"""
if obj.connection:
return True
try:
circuit_termination = obj.circuit_termination
return True
except CircuitTermination.DoesNotExist:
pass
return False
def get_interface_connection(self, obj):
if obj.connection:
return OrderedDict((
('interface', NestedInterfaceSerializer(obj.connected_interface, context=self.context).data),
('status', obj.connection.connection_status),
))
return None
# #
# Device bays # Device bays
# #
class DeviceBaySerializer(serializers.ModelSerializer): class DeviceBaySerializer(ValidatedModelSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
installed_device = NestedDeviceSerializer() installed_device = NestedDeviceSerializer(required=False, allow_null=True)
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 +631,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 +653,17 @@ class WritableInventoryItemSerializer(ValidatedModelSerializer):
# Interface connections # Interface connections
# #
class InterfaceConnectionSerializer(serializers.ModelSerializer): class InterfaceConnectionSerializer(ValidatedModelSerializer):
interface_a = PeerInterfaceSerializer() interface_a = NestedInterfaceSerializer()
interface_b = PeerInterfaceSerializer() interface_b = NestedInterfaceSerializer()
connection_status = ChoiceFieldSerializer(choices=CONNECTION_STATUS_CHOICES) 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 +671,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 +683,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']

View File

@ -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
# #

View File

@ -2327,8 +2327,8 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
'device': self.device.pk, 'device': self.device.pk,
'name': 'Test Interface 4', 'name': 'Test Interface 4',
'mode': IFACE_MODE_TAGGED, 'mode': IFACE_MODE_TAGGED,
'untagged_vlan': self.vlan3.id,
'tagged_vlans': [self.vlan1.id, self.vlan2.id], 'tagged_vlans': [self.vlan1.id, self.vlan2.id],
'untagged_vlan': self.vlan3.id
} }
url = reverse('dcim-api:interface-list') url = reverse('dcim-api:interface-list')
@ -2336,11 +2336,10 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
self.assertHttpStatus(response, status.HTTP_201_CREATED) self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(Interface.objects.count(), 4) self.assertEqual(Interface.objects.count(), 4)
interface5 = Interface.objects.get(pk=response.data['id']) self.assertEqual(response.data['device']['id'], data['device'])
self.assertEqual(interface5.device_id, data['device']) self.assertEqual(response.data['name'], data['name'])
self.assertEqual(interface5.name, data['name']) self.assertEqual(response.data['untagged_vlan']['id'], data['untagged_vlan'])
self.assertEqual(interface5.tagged_vlans.count(), 2) self.assertEqual([v['id'] for v in response.data['tagged_vlans']], data['tagged_vlans'])
self.assertEqual(interface5.untagged_vlan.id, data['untagged_vlan'])
def test_create_interface_bulk(self): def test_create_interface_bulk(self):
@ -2375,22 +2374,22 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
'device': self.device.pk, 'device': self.device.pk,
'name': 'Test Interface 4', 'name': 'Test Interface 4',
'mode': IFACE_MODE_TAGGED, 'mode': IFACE_MODE_TAGGED,
'tagged_vlans': [self.vlan1.id],
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id],
}, },
{ {
'device': self.device.pk, 'device': self.device.pk,
'name': 'Test Interface 5', 'name': 'Test Interface 5',
'mode': IFACE_MODE_TAGGED, 'mode': IFACE_MODE_TAGGED,
'tagged_vlans': [self.vlan1.id],
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id],
}, },
{ {
'device': self.device.pk, 'device': self.device.pk,
'name': 'Test Interface 6', 'name': 'Test Interface 6',
'mode': IFACE_MODE_TAGGED, 'mode': IFACE_MODE_TAGGED,
'tagged_vlans': [self.vlan1.id],
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id],
}, },
] ]
@ -2399,15 +2398,10 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
self.assertHttpStatus(response, status.HTTP_201_CREATED) self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(Interface.objects.count(), 6) self.assertEqual(Interface.objects.count(), 6)
self.assertEqual(response.data[0]['name'], data[0]['name']) for i in range(0, 3):
self.assertEqual(response.data[1]['name'], data[1]['name']) self.assertEqual(response.data[i]['name'], data[i]['name'])
self.assertEqual(response.data[2]['name'], data[2]['name']) self.assertEqual([v['id'] for v in response.data[i]['tagged_vlans']], data[i]['tagged_vlans'])
self.assertEqual(len(response.data[0]['tagged_vlans']), 1) self.assertEqual(response.data[i]['untagged_vlan']['id'], data[i]['untagged_vlan'])
self.assertEqual(len(response.data[1]['tagged_vlans']), 1)
self.assertEqual(len(response.data[2]['tagged_vlans']), 1)
self.assertEqual(response.data[0]['untagged_vlan'], self.vlan2.id)
self.assertEqual(response.data[1]['untagged_vlan'], self.vlan2.id)
self.assertEqual(response.data[2]['untagged_vlan'], self.vlan2.id)
def test_update_interface(self): def test_update_interface(self):
@ -2852,9 +2846,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']) for i in range(0, 3):
self.assertEqual(response.data[1]['interface_a'], data[1]['interface_a']) self.assertEqual(response.data[i]['interface_a']['id'], data[i]['interface_a'])
self.assertEqual(response.data[2]['interface_a'], data[2]['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 +3046,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']) for i in range(0, 3):
self.assertEqual(response.data[0]['domain'], data[0]['domain']) self.assertEqual(response.data[i]['master']['id'], data[i]['master'])
self.assertEqual(response.data[1]['master'], data[1]['master']) self.assertEqual(response.data[i]['domain'], data[i]['domain'])
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'])
def test_update_virtualchassis(self): def test_update_virtualchassis(self):

View File

@ -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,23 +62,34 @@ 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): class ImageAttachmentSerializer(ValidatedModelSerializer):
parent = serializers.SerializerMethodField() content_type = ContentTypeFieldSerializer()
parent = serializers.SerializerMethodField(read_only=True)
class Meta: class Meta:
model = ImageAttachment model = ImageAttachment
fields = ['id', 'parent', 'name', 'image', 'image_height', 'image_width', 'created'] fields = [
'id', 'content_type', 'object_id', 'parent', 'name', 'image', 'image_height', 'image_width', 'created',
]
def validate(self, data):
# Validate that the parent object exists
try:
data['content_type'].get_object_for_this_type(id=data['object_id'])
except ObjectDoesNotExist:
raise serializers.ValidationError(
"Invalid parent object: {} ID {}".format(data['content_type'], data['object_id'])
)
# Enforce model validation
super(ImageAttachmentSerializer, self).validate(data)
return data
def get_parent(self, obj): def get_parent(self, obj):
@ -102,29 +106,6 @@ class ImageAttachmentSerializer(serializers.ModelSerializer):
return serializer(obj.parent, context={'request': self.context['request']}).data return serializer(obj.parent, context={'request': self.context['request']}).data
class WritableImageAttachmentSerializer(ValidatedModelSerializer):
content_type = ContentTypeFieldSerializer()
class Meta:
model = ImageAttachment
fields = ['id', 'content_type', 'object_id', 'name', 'image']
def validate(self, data):
# Validate that the parent object exists
try:
data['content_type'].get_object_for_this_type(id=data['object_id'])
except ObjectDoesNotExist:
raise serializers.ValidationError(
"Invalid parent object: {} ID {}".format(data['content_type'], data['object_id'])
)
# Enforce model validation
super(WritableImageAttachmentSerializer, self).validate(data)
return data
# #
# Reports # Reports
# #

View File

@ -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
# #

View File

@ -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, SerializedPKRelatedField, 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): class VLANGroupSerializer(ValidatedModelSerializer):
site = NestedSiteSerializer() site = NestedSiteSerializer(required=False, allow_null=True)
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 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() site = NestedSiteSerializer(required=False, allow_null=True)
group = NestedVLANGroupSerializer() group = NestedVLANGroupSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceFieldSerializer(choices=VLAN_STATUS_CHOICES) status = ChoiceFieldSerializer(choices=VLAN_STATUS_CHOICES, required=False)
role = NestedRoleSerializer() 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() site = NestedSiteSerializer(required=False, allow_null=True)
vrf = NestedVRFSerializer() vrf = NestedVRFSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True)
vlan = NestedVLANSerializer() vlan = NestedVLANSerializer(required=False, allow_null=True)
status = ChoiceFieldSerializer(choices=PREFIX_STATUS_CHOICES) status = ChoiceFieldSerializer(choices=PREFIX_STATUS_CHOICES, required=False)
role = NestedRoleSerializer() 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() vrf = NestedVRFSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES) status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES, required=False)
role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES) role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES, required=False)
interface = IPAddressInterfaceSerializer() 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_inside'] = NestedIPAddressSerializer(required=False, allow_null=True)
IPAddressSerializer._declared_fields['nat_outside'] = NestedIPAddressSerializer() IPAddressSerializer._declared_fields['nat_outside'] = NestedIPAddressSerializer(read_only=True)
class WritableIPAddressSerializer(CustomFieldModelSerializer):
class Meta:
model = IPAddress
fields = [
'id', 'address', 'vrf', 'tenant', 'status', 'role', 'interface', 'description', 'nat_inside',
'custom_fields', 'created', 'last_updated',
]
class AvailableIPSerializer(serializers.Serializer): class AvailableIPSerializer(serializers.Serializer):
@ -342,22 +292,16 @@ class AvailableIPSerializer(serializers.Serializer):
# Services # Services
# #
class ServiceSerializer(serializers.ModelSerializer): class ServiceSerializer(ValidatedModelSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer(required=False, allow_null=True)
virtual_machine = NestedVirtualMachineSerializer() virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True)
protocol = ChoiceFieldSerializer(choices=IP_PROTOCOL_CHOICES) protocol = ChoiceFieldSerializer(choices=IP_PROTOCOL_CHOICES)
ipaddresses = NestedIPAddressSerializer(many=True) ipaddresses = SerializedPKRelatedField(
queryset=IPAddress.objects.all(),
class Meta: serializer=NestedIPAddressSerializer,
model = Service required=False,
fields = [ many=True
'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):
class Meta: class Meta:
model = Service model = Service

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -5,11 +5,13 @@ import pytz
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import ManyToManyField from django.db.models import ManyToManyField
from django.http import Http404 from django.http import Http404
from rest_framework import mixins from rest_framework import mixins
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
from rest_framework.permissions import BasePermission from rest_framework.permissions import BasePermission
from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import Field, ModelSerializer, ValidationError from rest_framework.serializers import Field, ModelSerializer, ValidationError
from rest_framework.viewsets import GenericViewSet, ViewSet from rest_framework.viewsets import GenericViewSet, ViewSet
@ -36,6 +38,77 @@ class IsAuthenticatedOrLoginNotRequired(BasePermission):
return request.user.is_authenticated return request.user.is_authenticated
#
# Fields
#
class ChoiceFieldSerializer(Field):
"""
Represent a ChoiceField as {'value': <DB value>, 'label': <string>}.
"""
def __init__(self, choices, **kwargs):
self._choices = dict()
for k, v in choices:
# Unpack grouped choices
if type(v) in [list, tuple]:
for k2, v2 in v:
self._choices[k2] = v2
else:
self._choices[k] = v
super(ChoiceFieldSerializer, self).__init__(**kwargs)
def to_representation(self, obj):
return {'value': obj, 'label': self._choices[obj]}
def to_internal_value(self, data):
return data
class ContentTypeFieldSerializer(Field):
"""
Represent a ContentType as '<app_label>.<model>'
"""
def to_representation(self, obj):
return "{}.{}".format(obj.app_label, obj.model)
def to_internal_value(self, data):
app_label, model = data.split('.')
try:
return ContentType.objects.get_by_natural_key(app_label=app_label, model=model)
except ContentType.DoesNotExist:
raise ValidationError("Invalid content type")
class TimeZoneField(Field):
"""
Represent a pytz time zone.
"""
def to_representation(self, obj):
return obj.zone if obj else None
def to_internal_value(self, data):
if not data:
return ""
try:
return pytz.timezone(str(data))
except pytz.exceptions.UnknownTimeZoneError:
raise ValidationError('Invalid time zone "{}"'.format(data))
class SerializedPKRelatedField(PrimaryKeyRelatedField):
"""
Extends PrimaryKeyRelatedField to return a serialized object on read. This is useful for representing related
objects in a ManyToManyField while still allowing a set of primary keys to be written.
"""
def __init__(self, serializer, **kwargs):
self.serializer = serializer
self.pk_field = kwargs.pop('pk_field', None)
super(SerializedPKRelatedField, self).__init__(**kwargs)
def to_representation(self, value):
return self.serializer(value, context={'request': self.context['request']}).data
# #
# Serializers # Serializers
# #
@ -67,58 +140,17 @@ class ValidatedModelSerializer(ModelSerializer):
return data return data
class ChoiceFieldSerializer(Field): class WritableNestedSerializer(ModelSerializer):
""" """
Represent a ChoiceField as {'value': <DB value>, 'label': <string>}. Returns a nested representation of an object on read, but accepts only a primary key on write.
""" """
def __init__(self, choices, **kwargs):
self._choices = dict()
for k, v in choices:
# Unpack grouped choices
if type(v) in [list, tuple]:
for k2, v2 in v:
self._choices[k2] = v2
else:
self._choices[k] = v
super(ChoiceFieldSerializer, self).__init__(**kwargs)
def to_representation(self, obj):
return {'value': obj, 'label': self._choices[obj]}
def to_internal_value(self, data): def to_internal_value(self, data):
return self._choices.get(data) if data is None:
return None
class ContentTypeFieldSerializer(Field):
"""
Represent a ContentType as '<app_label>.<model>'
"""
def to_representation(self, obj):
return "{}.{}".format(obj.app_label, obj.model)
def to_internal_value(self, data):
app_label, model = data.split('.')
try: try:
return ContentType.objects.get_by_natural_key(app_label=app_label, model=model) return self.Meta.model.objects.get(pk=data)
except ContentType.DoesNotExist: except ObjectDoesNotExist:
raise ValidationError("Invalid content type") raise ValidationError("Invalid ID")
class TimeZoneField(Field):
"""
Represent a pytz time zone.
"""
def to_representation(self, obj):
return obj.zone if obj else None
def to_internal_value(self, data):
if not data:
return ""
try:
return pytz.timezone(str(data))
except pytz.exceptions.UnknownTimeZoneError:
raise ValidationError('Invalid time zone "{}"'.format(data))
# #
@ -132,16 +164,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: Accept either a single object or a list of objects to create.
1. Use an alternate serializer (if provided) for write operations
2. 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):

View File

@ -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() group = NestedClusterGroupSerializer(required=False, allow_null=True)
site = NestedSiteSerializer() 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) status = ChoiceFieldSerializer(choices=VM_STATUS_CHOICES, required=False)
cluster = NestedClusterSerializer() cluster = NestedClusterSerializer(required=False, allow_null=True)
role = NestedDeviceRoleSerializer() role = NestedDeviceRoleSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True)
platform = NestedPlatformSerializer() platform = NestedPlatformSerializer(required=False, allow_null=True)
primary_ip = VirtualMachineIPAddressSerializer() primary_ip = VirtualMachineIPAddressSerializer(read_only=True)
primary_ip4 = VirtualMachineIPAddressSerializer() primary_ip4 = VirtualMachineIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = VirtualMachineIPAddressSerializer() 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',
]

View File

@ -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

View File

@ -5,7 +5,7 @@ django-filter>=1.1.0
django-mptt>=0.9.0 django-mptt>=0.9.0
django-tables2>=1.19.0 django-tables2>=1.19.0
django-timezone-field>=2.0 django-timezone-field>=2.0
djangorestframework>=3.7.7 djangorestframework>=3.7.7,<3.8.2
drf-yasg[validation]>=1.4.4 drf-yasg[validation]>=1.4.4
graphviz>=0.8.2 graphviz>=0.8.2
Markdown>=2.6.11 Markdown>=2.6.11