From bb1f97abc285a12496459953db3a82435e8185e1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 31 Jan 2017 15:35:09 -0500 Subject: [PATCH] Implemented static writable ModelSerializers for all models --- netbox/circuits/api/serializers.py | 35 ++++--- netbox/circuits/api/urls.py | 3 - netbox/circuits/api/views.py | 4 +- netbox/dcim/api/serializers.py | 145 ++++++++++++++++++----------- netbox/dcim/api/urls.py | 4 +- netbox/dcim/api/views.py | 8 +- netbox/extras/api/serializers.py | 57 +++++++----- netbox/ipam/api/serializers.py | 94 ++++++++++++++----- netbox/ipam/api/views.py | 6 ++ netbox/secrets/api/serializers.py | 2 +- netbox/tenancy/api/serializers.py | 18 +++- netbox/tenancy/api/views.py | 1 + netbox/utilities/api.py | 14 +-- 13 files changed, 251 insertions(+), 140 deletions(-) diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index a670b2e01..0cc2a277b 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -2,7 +2,7 @@ from rest_framework import serializers from circuits.models import Provider, Circuit, CircuitTermination, CircuitType from dcim.api.serializers import NestedSiteSerializer, DeviceInterfaceSerializer -from extras.api.serializers import CustomFieldSerializer +from extras.api.serializers import CustomFieldValueSerializer from tenancy.api.serializers import NestedTenantSerializer @@ -10,17 +10,18 @@ from tenancy.api.serializers import NestedTenantSerializer # Providers # -class ProviderSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class ProviderSerializer(serializers.ModelSerializer): + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = Provider fields = [ 'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', - 'custom_fields', + 'custom_field_values', ] -class NestedProviderSerializer(serializers.HyperlinkedModelSerializer): +class NestedProviderSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail') class Meta: @@ -28,6 +29,15 @@ class NestedProviderSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'name', 'slug'] +class WritableProviderSerializer(serializers.ModelSerializer): + + class Meta: + model = Provider + fields = [ + 'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', + ] + + # # Circuit types # @@ -39,7 +49,7 @@ class CircuitTypeSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug'] -class NestedCircuitTypeSerializer(serializers.HyperlinkedModelSerializer): +class NestedCircuitTypeSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail') class Meta: @@ -64,20 +74,21 @@ class CircuitTerminationSerializer(serializers.ModelSerializer): # Circuits # -class CircuitSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class CircuitSerializer(serializers.ModelSerializer): provider = NestedProviderSerializer() type = NestedCircuitTypeSerializer() tenant = NestedTenantSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = Circuit fields = [ 'id', 'cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', - 'custom_fields', + 'custom_field_values', ] -class NestedCircuitSerializer(serializers.HyperlinkedModelSerializer): +class NestedCircuitSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail') class Meta: @@ -85,12 +96,10 @@ class NestedCircuitSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'cid'] -# TODO: Delete this -class CircuitDetailSerializer(CircuitSerializer): - terminations = CircuitTerminationSerializer(many=True) +class WritableCircuitSerializer(serializers.ModelSerializer): - class Meta(CircuitSerializer.Meta): + class Meta: + model = Circuit fields = [ 'id', 'cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', - 'terminations', 'custom_fields', ] diff --git a/netbox/circuits/api/urls.py b/netbox/circuits/api/urls.py index f06b14165..59739e510 100644 --- a/netbox/circuits/api/urls.py +++ b/netbox/circuits/api/urls.py @@ -2,9 +2,6 @@ from django.conf.urls import include, url from rest_framework import routers -from extras.models import GRAPH_TYPE_PROVIDER -from extras.api.views import GraphListView - from . import views diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 1fdafa97b..97d619fd2 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -21,9 +21,10 @@ from . import serializers # Providers # -class ProviderViewSet(CustomFieldModelViewSet): +class ProviderViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Provider.objects.all() serializer_class = serializers.ProviderSerializer + write_serializer_class = serializers.WritableProviderSerializer @detail_route() def graphs(self, request, pk=None): @@ -49,6 +50,7 @@ class CircuitTypeViewSet(ModelViewSet): class CircuitViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Circuit.objects.select_related('type', 'tenant', 'provider') serializer_class = serializers.CircuitSerializer + write_serializer_class = serializers.WritableCircuitSerializer filter_class = CircuitFilter diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 244846672..1585109f1 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -4,10 +4,10 @@ from ipam.models import IPAddress from dcim.models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceType, DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet, - PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, RACK_FACE_FRONT, RACK_FACE_REAR, Site, - SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, + PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, Site, SUBDEVICE_ROLE_CHILD, + SUBDEVICE_ROLE_PARENT, ) -from extras.api.serializers import CustomFieldSerializer +from extras.api.serializers import CustomFieldValueSerializer from tenancy.api.serializers import NestedTenantSerializer @@ -15,19 +15,20 @@ from tenancy.api.serializers import NestedTenantSerializer # Sites # -class SiteSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class SiteSerializer(serializers.ModelSerializer): tenant = NestedTenantSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = Site fields = [ 'id', 'name', 'slug', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'contact_name', - 'contact_phone', 'contact_email', 'comments', 'custom_fields', 'count_prefixes', 'count_vlans', + 'contact_phone', 'contact_email', 'comments', 'custom_field_values', 'count_prefixes', 'count_vlans', 'count_racks', 'count_devices', 'count_circuits', ] -class NestedSiteSerializer(serializers.HyperlinkedModelSerializer): +class NestedSiteSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail') class Meta: @@ -35,6 +36,16 @@ class NestedSiteSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'name', 'slug'] +class WritableSiteSerializer(serializers.ModelSerializer): + + class Meta: + model = Site + fields = [ + 'id', 'name', 'slug', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'contact_name', + 'contact_phone', 'contact_email', 'comments', + ] + + # # Rack groups # @@ -47,7 +58,7 @@ class RackGroupSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'site'] -class NestedRackGroupSerializer(serializers.HyperlinkedModelSerializer): +class NestedRackGroupSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail') class Meta: @@ -55,6 +66,13 @@ class NestedRackGroupSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'name', 'slug'] +class WritableRackGroupSerializer(serializers.ModelSerializer): + + class Meta: + model = RackGroup + fields = ['id', 'name', 'slug', 'site'] + + # # Rack roles # @@ -66,7 +84,7 @@ class RackRoleSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'color'] -class NestedRackRoleSerializer(serializers.HyperlinkedModelSerializer): +class NestedRackRoleSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail') class Meta: @@ -79,21 +97,22 @@ class NestedRackRoleSerializer(serializers.HyperlinkedModelSerializer): # -class RackSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class RackSerializer(serializers.ModelSerializer): site = NestedSiteSerializer() group = NestedRackGroupSerializer() tenant = NestedTenantSerializer() role = NestedRackRoleSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = Rack fields = [ 'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'type', 'width', 'u_height', - 'desc_units', 'comments', 'custom_fields', + 'desc_units', 'comments', 'custom_field_values', ] -class NestedRackSerializer(serializers.HyperlinkedModelSerializer): +class NestedRackSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail') class Meta: @@ -101,28 +120,15 @@ class NestedRackSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'name', 'display_name'] -class RackDetailSerializer(RackSerializer): - front_units = serializers.SerializerMethodField() - rear_units = serializers.SerializerMethodField() +class WritableRackSerializer(serializers.ModelSerializer): - class Meta(RackSerializer.Meta): + class Meta: + model = Rack fields = [ - 'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'type', 'width', 'u_height', - 'desc_units', 'comments', 'custom_fields', 'front_units', 'rear_units', + 'id', 'name', 'facility_id', 'site', 'group', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units', + 'comments', ] - def get_front_units(self, obj): - units = obj.get_rack_units(face=RACK_FACE_FRONT) - for u in units: - u['device'] = NestedDeviceSerializer(u['device']).data if u['device'] else None - return units - - def get_rear_units(self, obj): - units = obj.get_rack_units(face=RACK_FACE_REAR) - for u in units: - u['device'] = NestedDeviceSerializer(u['device']).data if u['device'] else None - return units - # # Manufacturers @@ -135,7 +141,7 @@ class ManufacturerSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug'] -class NestedManufacturerSerializer(serializers.HyperlinkedModelSerializer): +class NestedManufacturerSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail') class Meta: @@ -147,16 +153,17 @@ class NestedManufacturerSerializer(serializers.HyperlinkedModelSerializer): # Device types # -class DeviceTypeSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class DeviceTypeSerializer(serializers.ModelSerializer): manufacturer = NestedManufacturerSerializer() subdevice_role = serializers.SerializerMethodField() instance_count = serializers.IntegerField(source='instances.count', read_only=True) + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = DeviceType fields = [ 'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering', - 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'custom_fields', + 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'custom_field_values', 'instance_count', ] @@ -168,7 +175,7 @@ class DeviceTypeSerializer(CustomFieldSerializer, serializers.ModelSerializer): }[obj.subdevice_role] -class NestedDeviceTypeSerializer(serializers.HyperlinkedModelSerializer): +class NestedDeviceTypeSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail') manufacturer = NestedManufacturerSerializer() @@ -177,6 +184,16 @@ class NestedDeviceTypeSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'manufacturer', 'model', 'slug'] +class WritableDeviceTypeSerializer(serializers.ModelSerializer): + + class Meta: + model = DeviceType + fields = [ + 'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering', + 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', + ] + + class ConsolePortTemplateSerializer(serializers.ModelSerializer): class Meta: @@ -230,7 +247,7 @@ class DeviceRoleSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'color'] -class NestedDeviceRoleSerializer(serializers.HyperlinkedModelSerializer): +class NestedDeviceRoleSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail') class Meta: @@ -249,7 +266,7 @@ class PlatformSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'rpc_client'] -class NestedPlatformSerializer(serializers.HyperlinkedModelSerializer): +class NestedPlatformSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail') class Meta: @@ -262,7 +279,7 @@ class NestedPlatformSerializer(serializers.HyperlinkedModelSerializer): # # Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency -class DeviceIPAddressSerializer(serializers.HyperlinkedModelSerializer): +class DeviceIPAddressSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') class Meta: @@ -270,7 +287,7 @@ class DeviceIPAddressSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'family', 'address'] -class DeviceSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class DeviceSerializer(serializers.ModelSerializer): device_type = NestedDeviceTypeSerializer() device_role = NestedDeviceRoleSerializer() tenant = NestedTenantSerializer() @@ -280,13 +297,14 @@ class DeviceSerializer(CustomFieldSerializer, serializers.ModelSerializer): primary_ip4 = DeviceIPAddressSerializer() primary_ip6 = DeviceIPAddressSerializer() parent_device = serializers.SerializerMethodField() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = Device fields = [ 'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', - 'comments', 'custom_fields', + 'comments', 'custom_field_values', ] def get_parent_device(self, obj): @@ -304,7 +322,7 @@ class DeviceSerializer(CustomFieldSerializer, serializers.ModelSerializer): } -class NestedDeviceSerializer(serializers.HyperlinkedModelSerializer): +class NestedDeviceSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail') class Meta: @@ -312,19 +330,29 @@ class NestedDeviceSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'name', 'display_name'] +class WritableDeviceSerializer(serializers.ModelSerializer): + + class Meta: + model = Device + fields = [ + 'id', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'rack', + 'position', 'face', 'status', 'primary_ip4', 'primary_ip6', 'comments', + ] + + # # Console server ports # class ConsoleServerPortSerializer(serializers.ModelSerializer): - device = NestedDeviceSerializer() + device = NestedDeviceSerializer(read_only=True) class Meta: model = ConsoleServerPort fields = ['id', 'device', 'name', 'connected_console'] -class DeviceConsoleServerPortSerializer(serializers.HyperlinkedModelSerializer): +class DeviceConsoleServerPortSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail') class Meta: @@ -338,7 +366,7 @@ class DeviceConsoleServerPortSerializer(serializers.HyperlinkedModelSerializer): # class ConsolePortSerializer(serializers.ModelSerializer): - device = NestedDeviceSerializer() + device = NestedDeviceSerializer(read_only=True) cs_port = ConsoleServerPortSerializer() class Meta: @@ -346,7 +374,7 @@ class ConsolePortSerializer(serializers.ModelSerializer): fields = ['id', 'device', 'name', 'cs_port', 'connection_status'] -class DeviceConsolePortSerializer(serializers.HyperlinkedModelSerializer): +class DeviceConsolePortSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail') class Meta: @@ -360,14 +388,14 @@ class DeviceConsolePortSerializer(serializers.HyperlinkedModelSerializer): # class PowerOutletSerializer(serializers.ModelSerializer): - device = NestedDeviceSerializer() + device = NestedDeviceSerializer(read_only=True) class Meta: model = PowerOutlet fields = ['id', 'device', 'name', 'connected_port'] -class DevicePowerOutletSerializer(serializers.HyperlinkedModelSerializer): +class DevicePowerOutletSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail') class Meta: @@ -381,7 +409,7 @@ class DevicePowerOutletSerializer(serializers.HyperlinkedModelSerializer): # class PowerPortSerializer(serializers.ModelSerializer): - device = NestedDeviceSerializer() + device = NestedDeviceSerializer(read_only=True) power_outlet = PowerOutletSerializer() class Meta: @@ -389,7 +417,7 @@ class PowerPortSerializer(serializers.ModelSerializer): fields = ['id', 'device', 'name', 'power_outlet', 'connection_status'] -class DevicePowerPortSerializer(serializers.HyperlinkedModelSerializer): +class DevicePowerPortSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail') class Meta: @@ -404,7 +432,7 @@ class DevicePowerPortSerializer(serializers.HyperlinkedModelSerializer): class InterfaceSerializer(serializers.ModelSerializer): - device = NestedDeviceSerializer() + device = NestedDeviceSerializer(read_only=True) connection = serializers.SerializerMethodField(read_only=True) connected_interface = serializers.SerializerMethodField(read_only=True) @@ -426,7 +454,7 @@ class InterfaceSerializer(serializers.ModelSerializer): return None -class PeerInterfaceSerializer(serializers.HyperlinkedModelSerializer): +class PeerInterfaceSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail') device = NestedDeviceSerializer() @@ -435,7 +463,7 @@ class PeerInterfaceSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'device', 'name', 'form_factor', 'mac_address', 'mgmt_only', 'description'] -class DeviceInterfaceSerializer(serializers.HyperlinkedModelSerializer): +class DeviceInterfaceSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail') connection = serializers.SerializerMethodField() @@ -462,7 +490,7 @@ class InterfaceConnectionSerializer(serializers.ModelSerializer): fields = ['id', 'interface_a', 'interface_b', 'connection_status'] -class NestedInterfaceConnectionSerializer(serializers.HyperlinkedModelSerializer): +class NestedInterfaceConnectionSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail') class Meta: @@ -470,12 +498,19 @@ class NestedInterfaceConnectionSerializer(serializers.HyperlinkedModelSerializer fields = ['id', 'url', 'connection_status'] +class WritableInterfaceConnectionSerializer(serializers.ModelSerializer): + + class Meta: + model = InterfaceConnection + fields = ['id', 'interface_a', 'interface_b', 'connection_status'] + + # # Device bays # class DeviceBaySerializer(serializers.ModelSerializer): - device = NestedDeviceSerializer() + device = NestedDeviceSerializer(read_only=True) installed_device = NestedDeviceSerializer() class Meta: @@ -483,7 +518,7 @@ class DeviceBaySerializer(serializers.ModelSerializer): fields = ['id', 'device', 'name', 'installed_device'] -class DeviceDeviceBaySerializer(serializers.HyperlinkedModelSerializer): +class DeviceDeviceBaySerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail') class Meta: @@ -497,7 +532,7 @@ class DeviceDeviceBaySerializer(serializers.HyperlinkedModelSerializer): # class ModuleSerializer(serializers.ModelSerializer): - device = NestedDeviceSerializer() + device = NestedDeviceSerializer(read_only=True) manufacturer = NestedManufacturerSerializer() class Meta: @@ -505,7 +540,7 @@ class ModuleSerializer(serializers.ModelSerializer): fields = ['id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'discovered'] -class DeviceModuleSerializer(serializers.HyperlinkedModelSerializer): +class DeviceModuleSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail') manufacturer = NestedManufacturerSerializer() diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index b10e857e8..0bf692dbb 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -2,10 +2,8 @@ from django.conf.urls import include, url from rest_framework import routers -from extras.models import GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE -from extras.api.views import GraphListView, TopologyMapView +from extras.api.views import TopologyMapView from ipam.api.views import ServiceViewSet, DeviceServiceViewSet - from . import views diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 0f8f5d742..27724e289 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -33,6 +33,7 @@ from . import serializers class SiteViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Site.objects.select_related('tenant') serializer_class = serializers.SiteSerializer + write_serializer_class = serializers.WritableSiteSerializer @detail_route() def graphs(self, request, pk=None): @@ -50,6 +51,7 @@ class RackGroupViewSet(WritableSerializerMixin, ModelViewSet): queryset = RackGroup.objects.select_related('site') serializer_class = serializers.RackGroupSerializer filter_class = filters.RackGroupFilter + write_serializer_class = serializers.WritableRackGroupSerializer # @@ -68,6 +70,7 @@ class RackRoleViewSet(ModelViewSet): class RackViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Rack.objects.select_related('site', 'group__site', 'tenant') serializer_class = serializers.RackSerializer + write_serializer_class = serializers.WritableRackSerializer filter_class = filters.RackFilter @detail_route(url_path='rack-units') @@ -112,6 +115,7 @@ class ManufacturerViewSet(ModelViewSet): class DeviceTypeViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = DeviceType.objects.select_related('manufacturer') serializer_class = serializers.DeviceTypeSerializer + write_serializer_class = serializers.WritableDeviceTypeSerializer # @@ -143,6 +147,7 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet): 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', ) serializer_class = serializers.DeviceSerializer + write_serializer_class = serializers.WritableDeviceSerializer filter_class = filters.DeviceFilter renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer] @@ -335,9 +340,10 @@ class DeviceModuleViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): # Interface connections # -class InterfaceConnectionViewSet(ModelViewSet): +class InterfaceConnectionViewSet(WritableSerializerMixin, ModelViewSet): queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device') serializer_class = serializers.InterfaceConnectionSerializer + write_serializer_class = serializers.WritableInterfaceConnectionSerializer # diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 01d348b0a..2dec2a360 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -1,35 +1,42 @@ from rest_framework import serializers -from extras.models import CF_TYPE_SELECT, CustomFieldChoice, Graph +from extras.models import CF_TYPE_SELECT, CustomFieldChoice, CustomFieldValue, Graph -class CustomFieldSerializer(serializers.Serializer): - """ - Extends a ModelSerializer to render any CustomFields and their values associated with an object. - """ - custom_fields = serializers.SerializerMethodField() +# class CustomFieldSerializer(serializers.ModelSerializer): +# """ +# Extends ModelSerializer to render any CustomFields and their values associated with an object. +# """ +# custom_fields = serializers.SerializerMethodField() +# +# def get_custom_fields(self, obj): +# +# # Gather all CustomFields applicable to this object +# fields = {cf.name: None for cf in self.context['custom_fields']} +# custom_field_choices = self.context['custom_field_choices'] +# +# # Attach any defined CustomFieldValues to their respective CustomFields +# for cfv in obj.custom_field_values.all(): +# +# # Attempt to suppress database lookups for CustomFieldChoices by using the cached choice set from the view +# # context. +# if cfv.field.type == CF_TYPE_SELECT: +# cfc = { +# 'id': int(cfv.serialized_value), +# 'value': custom_field_choices[int(cfv.serialized_value)] +# } +# fields[cfv.field.name] = CustomFieldChoiceSerializer(instance=cfc).data +# else: +# fields[cfv.field.name] = cfv.value +# +# return fields - def get_custom_fields(self, obj): - # Gather all CustomFields applicable to this object - fields = {cf.name: None for cf in self.context['custom_fields']} - custom_field_choices = self.context['custom_field_choices'] +class CustomFieldValueSerializer(serializers.ModelSerializer): - # Attach any defined CustomFieldValues to their respective CustomFields - for cfv in obj.custom_field_values.all(): - - # Attempt to suppress database lookups for CustomFieldChoices by using the cached choice set from the view - # context. - if cfv.field.type == CF_TYPE_SELECT: - cfc = { - 'id': int(cfv.serialized_value), - 'value': custom_field_choices[int(cfv.serialized_value)] - } - fields[cfv.field.name] = CustomFieldChoiceSerializer(instance=cfc).data - else: - fields[cfv.field.name] = cfv.value - - return fields + class Meta: + model = CustomFieldValue + fields = ['field', 'serialized_value'] class CustomFieldChoiceSerializer(serializers.ModelSerializer): diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index d4f60340b..9104f55b9 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -1,25 +1,25 @@ from rest_framework import serializers from dcim.api.serializers import NestedDeviceSerializer, DeviceInterfaceSerializer, NestedSiteSerializer -from extras.api.serializers import CustomFieldSerializer +from extras.api.serializers import CustomFieldValueSerializer from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from tenancy.api.serializers import NestedTenantSerializer -from utilities.api import WritableSerializerMixin # # VRFs # -class VRFSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class VRFSerializer(serializers.ModelSerializer): tenant = NestedTenantSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = VRF - fields = ['id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'custom_fields'] + fields = ['id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'custom_field_values'] -class NestedVRFSerializer(serializers.HyperlinkedModelSerializer): +class NestedVRFSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vrf-detail') class Meta: @@ -27,6 +27,13 @@ class NestedVRFSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'name', 'rd'] +class WritableVRFSerializer(serializers.ModelSerializer): + + class Meta: + model = VRF + fields = ['id', 'name', 'rd', 'tenant', 'enforce_unique', 'description'] + + # # Roles # @@ -38,7 +45,7 @@ class RoleSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'weight'] -class NestedRoleSerializer(serializers.HyperlinkedModelSerializer): +class NestedRoleSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:role-detail') class Meta: @@ -57,7 +64,7 @@ class RIRSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'is_private'] -class NestedRIRSerializer(serializers.HyperlinkedModelSerializer): +class NestedRIRSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail') class Meta: @@ -69,15 +76,16 @@ class NestedRIRSerializer(serializers.HyperlinkedModelSerializer): # Aggregates # -class AggregateSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class AggregateSerializer(serializers.ModelSerializer): rir = NestedRIRSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = Aggregate - fields = ['id', 'family', 'prefix', 'rir', 'date_added', 'description', 'custom_fields'] + fields = ['id', 'family', 'prefix', 'rir', 'date_added', 'description', 'custom_field_values'] -class NestedAggregateSerializer(serializers.HyperlinkedModelSerializer): +class NestedAggregateSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:aggregate-detail') class Meta(AggregateSerializer.Meta): @@ -85,11 +93,18 @@ class NestedAggregateSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'family', 'prefix'] +class WritableAggregateSerializer(serializers.ModelSerializer): + + class Meta: + model = Aggregate + fields = ['id', 'family', 'prefix', 'rir', 'date_added', 'description'] + + # # VLAN groups # -class VLANGroupSerializer(WritableSerializerMixin, serializers.ModelSerializer): +class VLANGroupSerializer(serializers.ModelSerializer): site = NestedSiteSerializer() class Meta: @@ -97,7 +112,7 @@ class VLANGroupSerializer(WritableSerializerMixin, serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'site'] -class NestedVLANGroupSerializer(serializers.HyperlinkedModelSerializer): +class NestedVLANGroupSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail') class Meta: @@ -105,25 +120,33 @@ class NestedVLANGroupSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'name', 'slug'] +class WritableVLANGroupSerializer(serializers.ModelSerializer): + + class Meta: + model = VLANGroup + fields = ['id', 'name', 'slug', 'site'] + + # # VLANs # -class VLANSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class VLANSerializer(serializers.ModelSerializer): site = NestedSiteSerializer() group = NestedVLANGroupSerializer() tenant = NestedTenantSerializer() role = NestedRoleSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = VLAN fields = [ 'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'display_name', - 'custom_fields', + 'custom_field_values', ] -class NestedVLANSerializer(serializers.HyperlinkedModelSerializer): +class NestedVLANSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail') class Meta: @@ -131,26 +154,36 @@ class NestedVLANSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'vid', 'name', 'display_name'] +class WritableVLANSerializer(serializers.ModelSerializer): + + class Meta: + model = VLAN + fields = [ + 'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', + ] + + # # Prefixes # -class PrefixSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class PrefixSerializer(serializers.ModelSerializer): site = NestedSiteSerializer() vrf = NestedVRFSerializer() tenant = NestedTenantSerializer() vlan = NestedVLANSerializer() role = NestedRoleSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = Prefix fields = [ 'id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description', - 'custom_fields', + 'custom_field_values', ] -class NestedPrefixSerializer(serializers.HyperlinkedModelSerializer): +class NestedPrefixSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:prefix-detail') class Meta: @@ -158,24 +191,34 @@ class NestedPrefixSerializer(serializers.HyperlinkedModelSerializer): fields = ['id', 'url', 'family', 'prefix'] +class WritablePrefixSerializer(serializers.ModelSerializer): + + class Meta: + model = Prefix + fields = [ + 'id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description', + ] + + # # IP addresses # -class IPAddressSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class IPAddressSerializer(serializers.ModelSerializer): vrf = NestedVRFSerializer() tenant = NestedTenantSerializer() interface = DeviceInterfaceSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = IPAddress fields = [ 'id', 'family', 'address', 'vrf', 'tenant', 'status', 'interface', 'description', 'nat_inside', - 'nat_outside', 'custom_fields', + 'nat_outside', 'custom_field_values', ] -class NestedIPAddressSerializer(serializers.HyperlinkedModelSerializer): +class NestedIPAddressSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') class Meta: @@ -186,6 +229,13 @@ IPAddressSerializer._declared_fields['nat_inside'] = NestedIPAddressSerializer() IPAddressSerializer._declared_fields['nat_outside'] = NestedIPAddressSerializer() +class WritableIPAddressSerializer(serializers.ModelSerializer): + + class Meta: + model = IPAddress + fields = ['id', 'family', 'address', 'vrf', 'tenant', 'status', 'interface', 'description', 'nat_inside'] + + # # Services # @@ -199,7 +249,7 @@ class ServiceSerializer(serializers.ModelSerializer): fields = ['id', 'device', 'name', 'port', 'protocol', 'ipaddresses', 'description'] -class DeviceServiceSerializer(serializers.HyperlinkedModelSerializer): +class DeviceServiceSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:service-detail') ipaddresses = NestedIPAddressSerializer(many=True) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 766acd97a..a95784578 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -20,6 +20,7 @@ from . import serializers class VRFViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = VRF.objects.select_related('tenant') serializer_class = serializers.VRFSerializer + write_serializer_class = serializers.WritableVRFSerializer filter_class = filters.VRFFilter @@ -48,6 +49,7 @@ class RIRViewSet(ModelViewSet): class AggregateViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Aggregate.objects.select_related('rir') serializer_class = serializers.AggregateSerializer + write_serializer_class = serializers.WritableAggregateSerializer filter_class = filters.AggregateFilter @@ -58,6 +60,7 @@ class AggregateViewSet(WritableSerializerMixin, CustomFieldModelViewSet): class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') serializer_class = serializers.PrefixSerializer + write_serializer_class = serializers.WritablePrefixSerializer filter_class = filters.PrefixFilter @@ -68,6 +71,7 @@ class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet): class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside') serializer_class = serializers.IPAddressSerializer + write_serializer_class = serializers.WritableIPAddressSerializer filter_class = filters.IPAddressFilter @@ -78,6 +82,7 @@ class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet): class VLANGroupViewSet(WritableSerializerMixin, ModelViewSet): queryset = VLANGroup.objects.select_related('site') serializer_class = serializers.VLANGroupSerializer + write_serializer_class = serializers.WritableVLANGroupSerializer filter_class = filters.VLANGroupFilter @@ -88,6 +93,7 @@ class VLANGroupViewSet(WritableSerializerMixin, ModelViewSet): class VLANViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role') serializer_class = serializers.VLANSerializer + write_serializer_class = serializers.WritableVLANSerializer filter_class = filters.VLANFilter diff --git a/netbox/secrets/api/serializers.py b/netbox/secrets/api/serializers.py index a6ed6ed8c..0b70e7cf8 100644 --- a/netbox/secrets/api/serializers.py +++ b/netbox/secrets/api/serializers.py @@ -15,7 +15,7 @@ class SecretRoleSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug'] -class NestedSecretRoleSerializer(serializers.HyperlinkedModelSerializer): +class NestedSecretRoleSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secretrole-detail') class Meta: diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index 4091e5261..46e6edf63 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from extras.api.serializers import CustomFieldSerializer +from extras.api.serializers import CustomFieldValueSerializer from tenancy.models import Tenant, TenantGroup @@ -15,7 +15,7 @@ class TenantGroupSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug'] -class NestedTenantGroupSerializer(serializers.HyperlinkedModelSerializer): +class NestedTenantGroupSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail') class Meta: @@ -27,17 +27,25 @@ class NestedTenantGroupSerializer(serializers.HyperlinkedModelSerializer): # Tenants # -class TenantSerializer(CustomFieldSerializer, serializers.ModelSerializer): +class TenantSerializer(serializers.ModelSerializer): group = NestedTenantGroupSerializer() + custom_field_values = CustomFieldValueSerializer(many=True) class Meta: model = Tenant - fields = ['id', 'name', 'slug', 'group', 'description', 'comments', 'custom_fields'] + fields = ['id', 'name', 'slug', 'group', 'description', 'comments', 'custom_field_values'] -class NestedTenantSerializer(serializers.HyperlinkedModelSerializer): +class NestedTenantSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail') class Meta: model = Tenant fields = ['id', 'url', 'name', 'slug'] + + +class WritableTenantSerializer(serializers.ModelSerializer): + + class Meta: + model = Tenant + fields = ['id', 'name', 'slug', 'group', 'description', 'comments'] diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index d288dd9f2..ae5069271 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -24,4 +24,5 @@ class TenantGroupViewSet(ModelViewSet): class TenantViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Tenant.objects.select_related('group') serializer_class = serializers.TenantSerializer + write_serializer_class = serializers.WritableTenantSerializer filter_class = TenantFilter diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 939ca3c05..818eb8289 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -12,18 +12,10 @@ class ServiceUnavailable(APIException): class WritableSerializerMixin(object): """ - Returns a flat Serializer from the given model suitable for write operations (POST, PUT, PATCH). This is necessary - to allow write operations on objects which utilize nested serializers. + Allow for the use of an alternate, writable serializer class for write operations (e.g. POST, PUT). """ def get_serializer_class(self): - - class WritableSerializer(ModelSerializer): - - class Meta(self.serializer_class.Meta): - pass - - if self.action in WRITE_OPERATIONS: - return WritableSerializer - + if self.action in WRITE_OPERATIONS and hasattr(self, 'write_serializer_class'): + return self.write_serializer_class return self.serializer_class