diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 731602975..ccfe57a4c 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -9,6 +9,7 @@ from circuits.models import Provider, CircuitTermination, CircuitType, Circuit from circuits.filters import CircuitFilter from extras.api.views import CustomFieldModelViewSet +from utilities.api import WritableSerializerMixin from . import serializers @@ -34,26 +35,23 @@ class CircuitTypeViewSet(ModelViewSet): # Circuits # -class CircuitViewSet(CustomFieldModelViewSet): +class CircuitViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Circuit.objects.select_related('type', 'tenant', 'provider') + serializer_class = serializers.CircuitSerializer filter_class = CircuitFilter - def get_serializer_class(self): - if self.action == 'retrieve': - return serializers.CircuitDetailSerializer - return serializers.CircuitSerializer - # # Circuit Terminations # -class CircuitTerminationViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): +class CircuitTerminationViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, WritableSerializerMixin, + GenericViewSet): queryset = CircuitTermination.objects.select_related('site', 'interface__device') serializer_class = serializers.CircuitTerminationSerializer -class NestedCircuitTerminationViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): +class NestedCircuitTerminationViewSet(CreateModelMixin, ListModelMixin ,WritableSerializerMixin, GenericViewSet): serializer_class = serializers.CircuitTerminationSerializer def get_queryset(self): diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 31103b786..44ebd4677 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -18,7 +18,7 @@ from dcim.models import ( from dcim import filters from extras.api.views import CustomFieldModelViewSet from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer -from utilities.api import ServiceUnavailable +from utilities.api import ServiceUnavailable, WritableSerializerMixin from .exceptions import MissingFilterException from . import serializers @@ -27,7 +27,7 @@ from . import serializers # Sites # -class SiteViewSet(CustomFieldModelViewSet): +class SiteViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Site.objects.select_related('tenant') serializer_class = serializers.SiteSerializer @@ -36,7 +36,7 @@ class SiteViewSet(CustomFieldModelViewSet): # Rack groups # -class RackGroupViewSet(ModelViewSet): +class RackGroupViewSet(WritableSerializerMixin, ModelViewSet): queryset = RackGroup.objects.select_related('site') serializer_class = serializers.RackGroupSerializer filter_class = filters.RackGroupFilter @@ -55,7 +55,7 @@ class RackRoleViewSet(ModelViewSet): # Racks # -class RackViewSet(CustomFieldModelViewSet): +class RackViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Rack.objects.select_related('site', 'group__site', 'tenant') serializer_class = serializers.RackSerializer filter_class = filters.RackFilter @@ -102,7 +102,7 @@ class ManufacturerViewSet(ModelViewSet): # Device Types # -class DeviceTypeViewSet(CustomFieldModelViewSet): +class DeviceTypeViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = DeviceType.objects.select_related('manufacturer') serializer_class = serializers.DeviceTypeSerializer @@ -129,7 +129,7 @@ class PlatformViewSet(ModelViewSet): # Devices # -class DeviceViewSet(CustomFieldModelViewSet): +class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Device.objects.select_related( 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'rack__site', 'parent_bay', ).prefetch_related( @@ -144,12 +144,13 @@ class DeviceViewSet(CustomFieldModelViewSet): # Console Ports # -class ConsolePortViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): +class ConsolePortViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, WritableSerializerMixin, + GenericViewSet): queryset = ConsolePort.objects.select_related('cs_port') serializer_class = serializers.ConsolePortSerializer -class ChildConsolePortViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): +class ChildConsolePortViewSet(CreateModelMixin, ListModelMixin, WritableSerializerMixin, GenericViewSet): serializer_class = serializers.ChildConsoleServerPortSerializer def get_queryset(self): @@ -161,12 +162,13 @@ class ChildConsolePortViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): # Console Server Ports # -class ConsoleServerPortViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): +class ConsoleServerPortViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, WritableSerializerMixin, + GenericViewSet): queryset = ConsoleServerPort.objects.select_related('connected_console') serializer_class = serializers.ConsoleServerPortSerializer -class ChildConsoleServerPortViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): +class ChildConsoleServerPortViewSet(CreateModelMixin, ListModelMixin, WritableSerializerMixin, GenericViewSet): serializer_class = serializers.ChildConsoleServerPortSerializer def get_queryset(self): @@ -178,12 +180,13 @@ class ChildConsoleServerPortViewSet(CreateModelMixin, ListModelMixin, GenericVie # Power Ports # -class PowerPortViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): +class PowerPortViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, WritableSerializerMixin, + GenericViewSet): queryset = PowerPort.objects.select_related('power_outlet') serializer_class = serializers.PowerPortSerializer -class NestedPowerPortViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): +class NestedPowerPortViewSet(CreateModelMixin, ListModelMixin, WritableSerializerMixin, GenericViewSet): serializer_class = serializers.ChildPowerPortSerializer def get_queryset(self): @@ -195,12 +198,13 @@ class NestedPowerPortViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): # Power Outlets # -class PowerOutletViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): +class PowerOutletViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, WritableSerializerMixin, + GenericViewSet): queryset = PowerOutlet.objects.select_related('connected_port') serializer_class = serializers.PowerOutletSerializer -class NestedPowerOutletViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): +class NestedPowerOutletViewSet(CreateModelMixin, ListModelMixin, WritableSerializerMixin, GenericViewSet): serializer_class = serializers.ChildPowerOutletSerializer def get_queryset(self): @@ -212,12 +216,13 @@ class NestedPowerOutletViewSet(CreateModelMixin, ListModelMixin, GenericViewSet) # Interfaces # -class InterfaceViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): +class InterfaceViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, WritableSerializerMixin, + GenericViewSet): queryset = Interface.objects.select_related('device') serializer_class = serializers.InterfaceDetailSerializer -class NestedInterfaceViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): +class NestedInterfaceViewSet(CreateModelMixin, ListModelMixin, WritableSerializerMixin, GenericViewSet): serializer_class = serializers.ChildInterfaceSerializer filter_class = filters.InterfaceFilter @@ -231,12 +236,13 @@ class NestedInterfaceViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): # Device bays # -class DeviceBayViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): +class DeviceBayViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, WritableSerializerMixin, + GenericViewSet): queryset = DeviceBay.objects.select_related('installed_device') serializer_class = serializers.DeviceBaySerializer -class NestedDeviceBayViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): +class NestedDeviceBayViewSet(CreateModelMixin, ListModelMixin, WritableSerializerMixin, GenericViewSet): serializer_class = serializers.ChildDeviceBaySerializer def get_queryset(self): @@ -248,12 +254,12 @@ class NestedDeviceBayViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): # Modules # -class ModuleViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): +class ModuleViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, WritableSerializerMixin, GenericViewSet): queryset = Module.objects.select_related('device', 'manufacturer') serializer_class = serializers.ModuleSerializer -class NestedModuleViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): +class NestedModuleViewSet(CreateModelMixin, ListModelMixin, WritableSerializerMixin, GenericViewSet): serializer_class = serializers.ChildModuleSerializer def get_queryset(self): diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index bab257cc5..4394cc1a8 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -4,6 +4,7 @@ from dcim.api.serializers import NestedDeviceSerializer, ChildInterfaceSerialize from extras.api.serializers import CustomFieldSerializer from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from tenancy.api.serializers import NestedTenantSerializer +from utilities.api import WritableSerializerMixin # @@ -84,7 +85,7 @@ class NestedAggregateSerializer(serializers.HyperlinkedModelSerializer): # VLAN groups # -class VLANGroupSerializer(serializers.ModelSerializer): +class VLANGroupSerializer(WritableSerializerMixin, serializers.ModelSerializer): site = NestedSiteSerializer() class Meta: diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 80ff10c6f..94d1e6814 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -4,6 +4,7 @@ from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, from ipam import filters from extras.api.views import CustomFieldModelViewSet +from utilities.api import WritableSerializerMixin from . import serializers @@ -11,7 +12,7 @@ from . import serializers # VRFs # -class VRFViewSet(CustomFieldModelViewSet): +class VRFViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = VRF.objects.select_related('tenant') serializer_class = serializers.VRFSerializer filter_class = filters.VRFFilter @@ -39,7 +40,7 @@ class RIRViewSet(ModelViewSet): # Aggregates # -class AggregateViewSet(CustomFieldModelViewSet): +class AggregateViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Aggregate.objects.select_related('rir') serializer_class = serializers.AggregateSerializer filter_class = filters.AggregateFilter @@ -49,7 +50,7 @@ class AggregateViewSet(CustomFieldModelViewSet): # Prefixes # -class PrefixViewSet(CustomFieldModelViewSet): +class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') serializer_class = serializers.PrefixSerializer filter_class = filters.PrefixFilter @@ -59,7 +60,7 @@ class PrefixViewSet(CustomFieldModelViewSet): # IP addresses # -class IPAddressViewSet(CustomFieldModelViewSet): +class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside') serializer_class = serializers.IPAddressSerializer filter_class = filters.IPAddressFilter @@ -69,7 +70,7 @@ class IPAddressViewSet(CustomFieldModelViewSet): # VLAN groups # -class VLANGroupViewSet(ModelViewSet): +class VLANGroupViewSet(WritableSerializerMixin, ModelViewSet): queryset = VLANGroup.objects.select_related('site') serializer_class = serializers.VLANGroupSerializer filter_class = filters.VLANGroupFilter @@ -79,7 +80,7 @@ class VLANGroupViewSet(ModelViewSet): # VLANs # -class VLANViewSet(CustomFieldModelViewSet): +class VLANViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role') serializer_class = serializers.VLANSerializer filter_class = filters.VLANFilter @@ -89,7 +90,7 @@ class VLANViewSet(CustomFieldModelViewSet): # Services # -class ServiceViewSet(ModelViewSet): +class ServiceViewSet(WritableSerializerMixin, ModelViewSet): queryset = Service.objects.select_related('device').prefetch_related('ipaddresses') serializer_class = serializers.ServiceSerializer filter_class = filters.ServiceFilter diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 17d0e79ef..d288dd9f2 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -4,6 +4,7 @@ from tenancy.models import Tenant, TenantGroup from tenancy.filters import TenantFilter from extras.api.views import CustomFieldModelViewSet +from utilities.api import WritableSerializerMixin from . import serializers @@ -20,7 +21,7 @@ class TenantGroupViewSet(ModelViewSet): # Tenants # -class TenantViewSet(CustomFieldModelViewSet): +class TenantViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = Tenant.objects.select_related('group') serializer_class = serializers.TenantSerializer filter_class = TenantFilter diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index ff35fd293..c25f7e842 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -1,6 +1,30 @@ from rest_framework.exceptions import APIException +from rest_framework.serializers import ModelSerializer + + +WRITE_OPERATIONS = ['create', 'update', 'partial_update', 'delete'] class ServiceUnavailable(APIException): status_code = 503 default_detail = "Service temporarily unavailable, please try again later." + + +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. + """ + + def get_serializer_class(self): + + class WritableSerializer(ModelSerializer): + + class Meta: + model = self.queryset.model + fields = '__all__' + + if self.action in WRITE_OPERATIONS: + return WritableSerializer + + return self.serializer_class