Jelajahi Sumber

Closes #1553: Introduced support for bulk object creation via the API

Jeremy Stretch 8 tahun lalu
induk
melakukan
198170ca48

+ 4 - 5
netbox/circuits/api/views.py

@@ -3,14 +3,13 @@ from __future__ import unicode_literals
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
 from rest_framework.decorators import detail_route
 from rest_framework.decorators import detail_route
 from rest_framework.response import Response
 from rest_framework.response import Response
-from rest_framework.viewsets import ModelViewSet
 
 
 from circuits import filters
 from circuits import filters
 from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
 from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
 from extras.api.serializers import RenderedGraphSerializer
 from extras.api.serializers import RenderedGraphSerializer
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
 from extras.models import Graph, GRAPH_TYPE_PROVIDER
 from extras.models import Graph, GRAPH_TYPE_PROVIDER
-from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
+from utilities.api import FieldChoicesViewSet, ModelViewSet
 from . import serializers
 from . import serializers
 
 
 
 
@@ -28,7 +27,7 @@ class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
 # Providers
 # Providers
 #
 #
 
 
-class ProviderViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableProviderSerializer
@@ -59,7 +58,7 @@ class CircuitTypeViewSet(ModelViewSet):
 # Circuits
 # Circuits
 #
 #
 
 
-class CircuitViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableCircuitSerializer
@@ -70,7 +69,7 @@ class CircuitViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # Circuit Terminations
 # Circuit Terminations
 #
 #
 
 
-class CircuitTerminationViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableCircuitTerminationSerializer

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

@@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404
 from rest_framework.decorators import detail_route
 from rest_framework.decorators import detail_route
 from rest_framework.mixins import ListModelMixin
 from rest_framework.mixins import ListModelMixin
 from rest_framework.response import Response
 from rest_framework.response import Response
-from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
+from rest_framework.viewsets import GenericViewSet, ViewSet
 
 
 from dcim import filters
 from dcim import filters
 from dcim.models import (
 from dcim.models import (
@@ -20,9 +20,7 @@ from dcim.models import (
 from extras.api.serializers import RenderedGraphSerializer
 from extras.api.serializers import RenderedGraphSerializer
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
 from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
 from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
-from utilities.api import (
-    IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ServiceUnavailable, WritableSerializerMixin,
-)
+from utilities.api import IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable
 from . import serializers
 from . import serializers
 from .exceptions import MissingFilterException
 from .exceptions import MissingFilterException
 
 
@@ -47,7 +45,7 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
 # Regions
 # Regions
 #
 #
 
 
-class RegionViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableRegionSerializer
@@ -58,7 +56,7 @@ class RegionViewSet(WritableSerializerMixin, ModelViewSet):
 # Sites
 # Sites
 #
 #
 
 
-class SiteViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableSiteSerializer
@@ -79,7 +77,7 @@ class SiteViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # Rack groups
 # Rack groups
 #
 #
 
 
-class RackGroupViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableRackGroupSerializer
@@ -100,7 +98,7 @@ class RackRoleViewSet(ModelViewSet):
 # Racks
 # Racks
 #
 #
 
 
-class RackViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableRackSerializer
@@ -131,7 +129,7 @@ class RackViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # Rack reservations
 # Rack reservations
 #
 #
 
 
-class RackReservationViewSet(WritableSerializerMixin, ModelViewSet):
+class RackReservationViewSet(ModelViewSet):
     queryset = RackReservation.objects.select_related('rack')
     queryset = RackReservation.objects.select_related('rack')
     serializer_class = serializers.RackReservationSerializer
     serializer_class = serializers.RackReservationSerializer
     write_serializer_class = serializers.WritableRackReservationSerializer
     write_serializer_class = serializers.WritableRackReservationSerializer
@@ -156,7 +154,7 @@ class ManufacturerViewSet(ModelViewSet):
 # Device types
 # Device types
 #
 #
 
 
-class DeviceTypeViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableDeviceTypeSerializer
@@ -167,42 +165,42 @@ class DeviceTypeViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # Device type components
 # Device type components
 #
 #
 
 
-class ConsolePortTemplateViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableConsolePortTemplateSerializer
     filter_class = filters.ConsolePortTemplateFilter
     filter_class = filters.ConsolePortTemplateFilter
 
 
 
 
-class ConsoleServerPortTemplateViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableConsoleServerPortTemplateSerializer
     filter_class = filters.ConsoleServerPortTemplateFilter
     filter_class = filters.ConsoleServerPortTemplateFilter
 
 
 
 
-class PowerPortTemplateViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritablePowerPortTemplateSerializer
     filter_class = filters.PowerPortTemplateFilter
     filter_class = filters.PowerPortTemplateFilter
 
 
 
 
-class PowerOutletTemplateViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritablePowerOutletTemplateSerializer
     filter_class = filters.PowerOutletTemplateFilter
     filter_class = filters.PowerOutletTemplateFilter
 
 
 
 
-class InterfaceTemplateViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableInterfaceTemplateSerializer
     filter_class = filters.InterfaceTemplateFilter
     filter_class = filters.InterfaceTemplateFilter
 
 
 
 
-class DeviceBayTemplateViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableDeviceBayTemplateSerializer
@@ -233,7 +231,7 @@ class PlatformViewSet(ModelViewSet):
 # Devices
 # Devices
 #
 #
 
 
-class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
+class DeviceViewSet(CustomFieldModelViewSet):
     queryset = Device.objects.select_related(
     queryset = Device.objects.select_related(
         'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
         'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
     ).prefetch_related(
     ).prefetch_related(
@@ -309,35 +307,35 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # Device components
 # Device components
 #
 #
 
 
-class ConsolePortViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableConsolePortSerializer
     filter_class = filters.ConsolePortFilter
     filter_class = filters.ConsolePortFilter
 
 
 
 
-class ConsoleServerPortViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableConsoleServerPortSerializer
     filter_class = filters.ConsoleServerPortFilter
     filter_class = filters.ConsoleServerPortFilter
 
 
 
 
-class PowerPortViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritablePowerPortSerializer
     filter_class = filters.PowerPortFilter
     filter_class = filters.PowerPortFilter
 
 
 
 
-class PowerOutletViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritablePowerOutletSerializer
     filter_class = filters.PowerOutletFilter
     filter_class = filters.PowerOutletFilter
 
 
 
 
-class InterfaceViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableInterfaceSerializer
@@ -354,14 +352,14 @@ class InterfaceViewSet(WritableSerializerMixin, ModelViewSet):
         return Response(serializer.data)
         return Response(serializer.data)
 
 
 
 
-class DeviceBayViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableDeviceBaySerializer
     filter_class = filters.DeviceBayFilter
     filter_class = filters.DeviceBayFilter
 
 
 
 
-class InventoryItemViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableInventoryItemSerializer
@@ -384,7 +382,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
     filter_class = filters.PowerConnectionFilter
     filter_class = filters.PowerConnectionFilter
 
 
 
 
-class InterfaceConnectionViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableInterfaceConnectionSerializer

+ 6 - 6
netbox/extras/api/views.py

@@ -6,12 +6,12 @@ from django.shortcuts import get_object_or_404
 from rest_framework.decorators import detail_route
 from rest_framework.decorators import detail_route
 from rest_framework.exceptions import PermissionDenied
 from rest_framework.exceptions import PermissionDenied
 from rest_framework.response import Response
 from rest_framework.response import Response
-from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet
+from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
 
 
 from extras import filters
 from extras import filters
 from extras.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction
 from extras.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction
 from extras.reports import get_report, get_reports
 from extras.reports import get_report, get_reports
-from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, WritableSerializerMixin
+from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
 from . import serializers
 from . import serializers
 
 
 
 
@@ -64,7 +64,7 @@ class CustomFieldModelViewSet(ModelViewSet):
 # Graphs
 # Graphs
 #
 #
 
 
-class GraphViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableGraphSerializer
@@ -75,7 +75,7 @@ class GraphViewSet(WritableSerializerMixin, ModelViewSet):
 # Export templates
 # Export templates
 #
 #
 
 
-class ExportTemplateViewSet(WritableSerializerMixin, ModelViewSet):
+class ExportTemplateViewSet(ModelViewSet):
     queryset = ExportTemplate.objects.all()
     queryset = ExportTemplate.objects.all()
     serializer_class = serializers.ExportTemplateSerializer
     serializer_class = serializers.ExportTemplateSerializer
     filter_class = filters.ExportTemplateFilter
     filter_class = filters.ExportTemplateFilter
@@ -85,7 +85,7 @@ class ExportTemplateViewSet(WritableSerializerMixin, ModelViewSet):
 # Topology maps
 # Topology maps
 #
 #
 
 
-class TopologyMapViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableTopologyMapSerializer
@@ -115,7 +115,7 @@ class TopologyMapViewSet(WritableSerializerMixin, ModelViewSet):
 # Image attachments
 # Image attachments
 #
 #
 
 
-class ImageAttachmentViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableImageAttachmentSerializer

+ 8 - 9
netbox/ipam/api/views.py

@@ -6,12 +6,11 @@ from rest_framework import status
 from rest_framework.decorators import detail_route
 from rest_framework.decorators import detail_route
 from rest_framework.exceptions import PermissionDenied
 from rest_framework.exceptions import PermissionDenied
 from rest_framework.response import Response
 from rest_framework.response import Response
-from rest_framework.viewsets import ModelViewSet
 
 
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
 from ipam import filters
 from ipam import filters
 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 utilities.api import FieldChoicesViewSet, WritableSerializerMixin
+from utilities.api import FieldChoicesViewSet, ModelViewSet
 from . import serializers
 from . import serializers
 
 
 
 
@@ -33,7 +32,7 @@ class IPAMFieldChoicesViewSet(FieldChoicesViewSet):
 # VRFs
 # VRFs
 #
 #
 
 
-class VRFViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableVRFSerializer
@@ -54,7 +53,7 @@ class RIRViewSet(ModelViewSet):
 # Aggregates
 # Aggregates
 #
 #
 
 
-class AggregateViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableAggregateSerializer
@@ -75,7 +74,7 @@ class RoleViewSet(ModelViewSet):
 # Prefixes
 # Prefixes
 #
 #
 
 
-class PrefixViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritablePrefixSerializer
@@ -146,7 +145,7 @@ class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # IP addresses
 # IP addresses
 #
 #
 
 
-class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
+class IPAddressViewSet(CustomFieldModelViewSet):
     queryset = IPAddress.objects.select_related(
     queryset = IPAddress.objects.select_related(
         'vrf__tenant', 'tenant', 'nat_inside'
         'vrf__tenant', 'tenant', 'nat_inside'
     ).prefetch_related(
     ).prefetch_related(
@@ -161,7 +160,7 @@ class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # VLAN groups
 # VLAN groups
 #
 #
 
 
-class VLANGroupViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableVLANGroupSerializer
@@ -172,7 +171,7 @@ class VLANGroupViewSet(WritableSerializerMixin, ModelViewSet):
 # VLANs
 # VLANs
 #
 #
 
 
-class VLANViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableVLANSerializer
@@ -183,7 +182,7 @@ class VLANViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # Services
 # Services
 #
 #
 
 
-class ServiceViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableServiceSerializer

+ 3 - 3
netbox/secrets/api/views.py

@@ -7,12 +7,12 @@ from django.http import HttpResponseBadRequest
 from rest_framework.exceptions import ValidationError
 from rest_framework.exceptions import ValidationError
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.response import Response
-from rest_framework.viewsets import ModelViewSet, ViewSet
+from rest_framework.viewsets import ViewSet
 
 
 from secrets import filters
 from secrets import filters
 from secrets.exceptions import InvalidKey
 from secrets.exceptions import InvalidKey
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
-from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
+from utilities.api import FieldChoicesViewSet, ModelViewSet
 from . import serializers
 from . import serializers
 
 
 ERR_USERKEY_MISSING = "No UserKey found for the current user."
 ERR_USERKEY_MISSING = "No UserKey found for the current user."
@@ -44,7 +44,7 @@ class SecretRoleViewSet(ModelViewSet):
 # Secrets
 # Secrets
 #
 #
 
 
-class SecretViewSet(WritableSerializerMixin, ModelViewSet):
+class SecretViewSet(ModelViewSet):
     queryset = Secret.objects.select_related(
     queryset = Secret.objects.select_related(
         'device__primary_ip4', 'device__primary_ip6', 'role',
         'device__primary_ip4', 'device__primary_ip6', 'role',
     ).prefetch_related(
     ).prefetch_related(

+ 2 - 4
netbox/tenancy/api/views.py

@@ -1,11 +1,9 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from rest_framework.viewsets import ModelViewSet
-
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
 from tenancy import filters
 from tenancy import filters
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
-from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
+from utilities.api import FieldChoicesViewSet, ModelViewSet
 from . import serializers
 from . import serializers
 
 
 
 
@@ -31,7 +29,7 @@ class TenantGroupViewSet(ModelViewSet):
 # Tenants
 # Tenants
 #
 #
 
 
-class TenantViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableTenantSerializer

+ 27 - 18
netbox/utilities/api.py

@@ -5,11 +5,12 @@ from collections import OrderedDict
 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.http import Http404
 from django.http import Http404
+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.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 ViewSet
+from rest_framework.viewsets import GenericViewSet, ViewSet
 
 
 WRITE_OPERATIONS = ['create', 'update', 'partial_update', 'delete']
 WRITE_OPERATIONS = ['create', 'update', 'partial_update', 'delete']
 
 
@@ -97,9 +98,33 @@ class ContentTypeFieldSerializer(Field):
 
 
 
 
 #
 #
-# Views
+# Viewsets
 #
 #
 
 
+class ModelViewSet(mixins.CreateModelMixin,
+                   mixins.RetrieveModelMixin,
+                   mixins.UpdateModelMixin,
+                   mixins.DestroyModelMixin,
+                   mixins.ListModelMixin,
+                   GenericViewSet):
+    """
+    Substitute DRF's built-in ModelViewSet for our own, which introduces a bit of additional functionality:
+    1. Use an alternate serializer (if provided) for write operations
+    2. Accept either a single object or a list of objects to create
+    """
+    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):
+        # If a list of objects has been provided, initialize the serializer with many=True
+        if isinstance(kwargs.get('data', {}), list):
+            kwargs['many'] = True
+        return super(ModelViewSet, self).get_serializer(*args, **kwargs)
+
+
 class FieldChoicesViewSet(ViewSet):
 class FieldChoicesViewSet(ViewSet):
     """
     """
     Expose the built-in numeric values which represent static choices for a model's field.
     Expose the built-in numeric values which represent static choices for a model's field.
@@ -135,25 +160,9 @@ class FieldChoicesViewSet(ViewSet):
         return Response(self._fields)
         return Response(self._fields)
 
 
     def retrieve(self, request, pk):
     def retrieve(self, request, pk):
-
         if pk not in self._fields:
         if pk not in self._fields:
             raise Http404
             raise Http404
-
         return Response(self._fields[pk])
         return Response(self._fields[pk])
 
 
     def get_view_name(self):
     def get_view_name(self):
         return "Field Choices"
         return "Field Choices"
-
-
-#
-# Mixins
-#
-
-class WritableSerializerMixin(object):
-    """
-    Allow for the use of an alternate, writable serializer class for write operations (e.g. POST, PUT).
-    """
-    def get_serializer_class(self):
-        if self.action in WRITE_OPERATIONS and hasattr(self, 'write_serializer_class'):
-            return self.write_serializer_class
-        return self.serializer_class

+ 4 - 6
netbox/virtualization/api/views.py

@@ -1,10 +1,8 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from rest_framework.viewsets import ModelViewSet
-
 from dcim.models import Interface
 from dcim.models import Interface
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
-from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
+from utilities.api import FieldChoicesViewSet, ModelViewSet
 from virtualization import filters
 from virtualization import filters
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from . import serializers
 from . import serializers
@@ -34,7 +32,7 @@ class ClusterGroupViewSet(ModelViewSet):
     serializer_class = serializers.ClusterGroupSerializer
     serializer_class = serializers.ClusterGroupSerializer
 
 
 
 
-class ClusterViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableClusterSerializer
@@ -45,14 +43,14 @@ class ClusterViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 # Virtual machines
 # Virtual machines
 #
 #
 
 
-class VirtualMachineViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableVirtualMachineSerializer
     filter_class = filters.VirtualMachineFilter
     filter_class = filters.VirtualMachineFilter
 
 
 
 
-class InterfaceViewSet(WritableSerializerMixin, 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
     write_serializer_class = serializers.WritableInterfaceSerializer