diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 666f67502..9b75bc184 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -3,14 +3,13 @@ from __future__ import unicode_literals from django.shortcuts import get_object_or_404 from rest_framework.decorators import detail_route from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet from circuits import filters from circuits.models import Provider, CircuitTermination, CircuitType, Circuit from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet from extras.models import Graph, GRAPH_TYPE_PROVIDER -from utilities.api import FieldChoicesViewSet, WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, ModelViewSet from . import serializers @@ -28,7 +27,7 @@ class CircuitsFieldChoicesViewSet(FieldChoicesViewSet): # Providers # -class ProviderViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class ProviderViewSet(CustomFieldModelViewSet): queryset = Provider.objects.all() serializer_class = serializers.ProviderSerializer write_serializer_class = serializers.WritableProviderSerializer @@ -59,7 +58,7 @@ class CircuitTypeViewSet(ModelViewSet): # Circuits # -class CircuitViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class CircuitViewSet(CustomFieldModelViewSet): queryset = Circuit.objects.select_related('type', 'tenant', 'provider') serializer_class = serializers.CircuitSerializer write_serializer_class = serializers.WritableCircuitSerializer @@ -70,7 +69,7 @@ class CircuitViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # Circuit Terminations # -class CircuitTerminationViewSet(WritableSerializerMixin, ModelViewSet): +class CircuitTerminationViewSet(ModelViewSet): queryset = CircuitTermination.objects.select_related('circuit', 'site', 'interface__device') serializer_class = serializers.CircuitTerminationSerializer write_serializer_class = serializers.WritableCircuitTerminationSerializer diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 1df0f9cbc..01711709c 100644 --- a/netbox/dcim/api/views.py +++ b/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.mixins import ListModelMixin 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.models import ( @@ -20,9 +20,7 @@ from dcim.models import ( from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet 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 .exceptions import MissingFilterException @@ -47,7 +45,7 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet): # Regions # -class RegionViewSet(WritableSerializerMixin, ModelViewSet): +class RegionViewSet(ModelViewSet): queryset = Region.objects.all() serializer_class = serializers.RegionSerializer write_serializer_class = serializers.WritableRegionSerializer @@ -58,7 +56,7 @@ class RegionViewSet(WritableSerializerMixin, ModelViewSet): # Sites # -class SiteViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class SiteViewSet(CustomFieldModelViewSet): queryset = Site.objects.select_related('region', 'tenant') serializer_class = serializers.SiteSerializer write_serializer_class = serializers.WritableSiteSerializer @@ -79,7 +77,7 @@ class SiteViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # Rack groups # -class RackGroupViewSet(WritableSerializerMixin, ModelViewSet): +class RackGroupViewSet(ModelViewSet): queryset = RackGroup.objects.select_related('site') serializer_class = serializers.RackGroupSerializer write_serializer_class = serializers.WritableRackGroupSerializer @@ -100,7 +98,7 @@ class RackRoleViewSet(ModelViewSet): # Racks # -class RackViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class RackViewSet(CustomFieldModelViewSet): queryset = Rack.objects.select_related('site', 'group__site', 'tenant') serializer_class = serializers.RackSerializer write_serializer_class = serializers.WritableRackSerializer @@ -131,7 +129,7 @@ class RackViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # Rack reservations # -class RackReservationViewSet(WritableSerializerMixin, ModelViewSet): +class RackReservationViewSet(ModelViewSet): queryset = RackReservation.objects.select_related('rack') serializer_class = serializers.RackReservationSerializer write_serializer_class = serializers.WritableRackReservationSerializer @@ -156,7 +154,7 @@ class ManufacturerViewSet(ModelViewSet): # Device types # -class DeviceTypeViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class DeviceTypeViewSet(CustomFieldModelViewSet): queryset = DeviceType.objects.select_related('manufacturer') serializer_class = serializers.DeviceTypeSerializer write_serializer_class = serializers.WritableDeviceTypeSerializer @@ -167,42 +165,42 @@ class DeviceTypeViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # Device type components # -class ConsolePortTemplateViewSet(WritableSerializerMixin, ModelViewSet): +class ConsolePortTemplateViewSet(ModelViewSet): queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer') serializer_class = serializers.ConsolePortTemplateSerializer write_serializer_class = serializers.WritableConsolePortTemplateSerializer filter_class = filters.ConsolePortTemplateFilter -class ConsoleServerPortTemplateViewSet(WritableSerializerMixin, ModelViewSet): +class ConsoleServerPortTemplateViewSet(ModelViewSet): queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer') serializer_class = serializers.ConsoleServerPortTemplateSerializer write_serializer_class = serializers.WritableConsoleServerPortTemplateSerializer filter_class = filters.ConsoleServerPortTemplateFilter -class PowerPortTemplateViewSet(WritableSerializerMixin, ModelViewSet): +class PowerPortTemplateViewSet(ModelViewSet): queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer') serializer_class = serializers.PowerPortTemplateSerializer write_serializer_class = serializers.WritablePowerPortTemplateSerializer filter_class = filters.PowerPortTemplateFilter -class PowerOutletTemplateViewSet(WritableSerializerMixin, ModelViewSet): +class PowerOutletTemplateViewSet(ModelViewSet): queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer') serializer_class = serializers.PowerOutletTemplateSerializer write_serializer_class = serializers.WritablePowerOutletTemplateSerializer filter_class = filters.PowerOutletTemplateFilter -class InterfaceTemplateViewSet(WritableSerializerMixin, ModelViewSet): +class InterfaceTemplateViewSet(ModelViewSet): queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer') serializer_class = serializers.InterfaceTemplateSerializer write_serializer_class = serializers.WritableInterfaceTemplateSerializer filter_class = filters.InterfaceTemplateFilter -class DeviceBayTemplateViewSet(WritableSerializerMixin, ModelViewSet): +class DeviceBayTemplateViewSet(ModelViewSet): queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer') serializer_class = serializers.DeviceBayTemplateSerializer write_serializer_class = serializers.WritableDeviceBayTemplateSerializer @@ -233,7 +231,7 @@ class PlatformViewSet(ModelViewSet): # Devices # -class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class DeviceViewSet(CustomFieldModelViewSet): queryset = Device.objects.select_related( 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay', ).prefetch_related( @@ -309,35 +307,35 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # Device components # -class ConsolePortViewSet(WritableSerializerMixin, ModelViewSet): +class ConsolePortViewSet(ModelViewSet): queryset = ConsolePort.objects.select_related('device', 'cs_port__device') serializer_class = serializers.ConsolePortSerializer write_serializer_class = serializers.WritableConsolePortSerializer filter_class = filters.ConsolePortFilter -class ConsoleServerPortViewSet(WritableSerializerMixin, ModelViewSet): +class ConsoleServerPortViewSet(ModelViewSet): queryset = ConsoleServerPort.objects.select_related('device', 'connected_console__device') serializer_class = serializers.ConsoleServerPortSerializer write_serializer_class = serializers.WritableConsoleServerPortSerializer filter_class = filters.ConsoleServerPortFilter -class PowerPortViewSet(WritableSerializerMixin, ModelViewSet): +class PowerPortViewSet(ModelViewSet): queryset = PowerPort.objects.select_related('device', 'power_outlet__device') serializer_class = serializers.PowerPortSerializer write_serializer_class = serializers.WritablePowerPortSerializer filter_class = filters.PowerPortFilter -class PowerOutletViewSet(WritableSerializerMixin, ModelViewSet): +class PowerOutletViewSet(ModelViewSet): queryset = PowerOutlet.objects.select_related('device', 'connected_port__device') serializer_class = serializers.PowerOutletSerializer write_serializer_class = serializers.WritablePowerOutletSerializer filter_class = filters.PowerOutletFilter -class InterfaceViewSet(WritableSerializerMixin, ModelViewSet): +class InterfaceViewSet(ModelViewSet): queryset = Interface.objects.select_related('device') serializer_class = serializers.InterfaceSerializer write_serializer_class = serializers.WritableInterfaceSerializer @@ -354,14 +352,14 @@ class InterfaceViewSet(WritableSerializerMixin, ModelViewSet): return Response(serializer.data) -class DeviceBayViewSet(WritableSerializerMixin, ModelViewSet): +class DeviceBayViewSet(ModelViewSet): queryset = DeviceBay.objects.select_related('installed_device') serializer_class = serializers.DeviceBaySerializer write_serializer_class = serializers.WritableDeviceBaySerializer filter_class = filters.DeviceBayFilter -class InventoryItemViewSet(WritableSerializerMixin, ModelViewSet): +class InventoryItemViewSet(ModelViewSet): queryset = InventoryItem.objects.select_related('device', 'manufacturer') serializer_class = serializers.InventoryItemSerializer write_serializer_class = serializers.WritableInventoryItemSerializer @@ -384,7 +382,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet): filter_class = filters.PowerConnectionFilter -class InterfaceConnectionViewSet(WritableSerializerMixin, ModelViewSet): +class InterfaceConnectionViewSet(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/views.py b/netbox/extras/api/views.py index c8d1e58c4..252c2d12c 100644 --- a/netbox/extras/api/views.py +++ b/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.exceptions import PermissionDenied 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.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction 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 @@ -64,7 +64,7 @@ class CustomFieldModelViewSet(ModelViewSet): # Graphs # -class GraphViewSet(WritableSerializerMixin, ModelViewSet): +class GraphViewSet(ModelViewSet): queryset = Graph.objects.all() serializer_class = serializers.GraphSerializer write_serializer_class = serializers.WritableGraphSerializer @@ -75,7 +75,7 @@ class GraphViewSet(WritableSerializerMixin, ModelViewSet): # Export templates # -class ExportTemplateViewSet(WritableSerializerMixin, ModelViewSet): +class ExportTemplateViewSet(ModelViewSet): queryset = ExportTemplate.objects.all() serializer_class = serializers.ExportTemplateSerializer filter_class = filters.ExportTemplateFilter @@ -85,7 +85,7 @@ class ExportTemplateViewSet(WritableSerializerMixin, ModelViewSet): # Topology maps # -class TopologyMapViewSet(WritableSerializerMixin, ModelViewSet): +class TopologyMapViewSet(ModelViewSet): queryset = TopologyMap.objects.select_related('site') serializer_class = serializers.TopologyMapSerializer write_serializer_class = serializers.WritableTopologyMapSerializer @@ -115,7 +115,7 @@ class TopologyMapViewSet(WritableSerializerMixin, ModelViewSet): # Image attachments # -class ImageAttachmentViewSet(WritableSerializerMixin, ModelViewSet): +class ImageAttachmentViewSet(ModelViewSet): queryset = ImageAttachment.objects.all() serializer_class = serializers.ImageAttachmentSerializer write_serializer_class = serializers.WritableImageAttachmentSerializer diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 649e74069..3c8f3c43d 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -6,12 +6,11 @@ from rest_framework import status from rest_framework.decorators import detail_route from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet from extras.api.views import CustomFieldModelViewSet from ipam import filters 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 @@ -33,7 +32,7 @@ class IPAMFieldChoicesViewSet(FieldChoicesViewSet): # VRFs # -class VRFViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class VRFViewSet(CustomFieldModelViewSet): queryset = VRF.objects.select_related('tenant') serializer_class = serializers.VRFSerializer write_serializer_class = serializers.WritableVRFSerializer @@ -54,7 +53,7 @@ class RIRViewSet(ModelViewSet): # Aggregates # -class AggregateViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class AggregateViewSet(CustomFieldModelViewSet): queryset = Aggregate.objects.select_related('rir') serializer_class = serializers.AggregateSerializer write_serializer_class = serializers.WritableAggregateSerializer @@ -75,7 +74,7 @@ class RoleViewSet(ModelViewSet): # Prefixes # -class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class PrefixViewSet(CustomFieldModelViewSet): queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') serializer_class = serializers.PrefixSerializer write_serializer_class = serializers.WritablePrefixSerializer @@ -146,7 +145,7 @@ class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # IP addresses # -class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class IPAddressViewSet(CustomFieldModelViewSet): queryset = IPAddress.objects.select_related( 'vrf__tenant', 'tenant', 'nat_inside' ).prefetch_related( @@ -161,7 +160,7 @@ class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # VLAN groups # -class VLANGroupViewSet(WritableSerializerMixin, ModelViewSet): +class VLANGroupViewSet(ModelViewSet): queryset = VLANGroup.objects.select_related('site') serializer_class = serializers.VLANGroupSerializer write_serializer_class = serializers.WritableVLANGroupSerializer @@ -172,7 +171,7 @@ class VLANGroupViewSet(WritableSerializerMixin, ModelViewSet): # VLANs # -class VLANViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class VLANViewSet(CustomFieldModelViewSet): queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role') serializer_class = serializers.VLANSerializer write_serializer_class = serializers.WritableVLANSerializer @@ -183,7 +182,7 @@ class VLANViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # Services # -class ServiceViewSet(WritableSerializerMixin, ModelViewSet): +class ServiceViewSet(ModelViewSet): queryset = Service.objects.select_related('device') serializer_class = serializers.ServiceSerializer write_serializer_class = serializers.WritableServiceSerializer diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index e2ffa3b28..a105e0505 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -7,12 +7,12 @@ from django.http import HttpResponseBadRequest from rest_framework.exceptions import ValidationError from rest_framework.permissions import IsAuthenticated 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.exceptions import InvalidKey from secrets.models import Secret, SecretRole, SessionKey, UserKey -from utilities.api import FieldChoicesViewSet, WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, ModelViewSet from . import serializers ERR_USERKEY_MISSING = "No UserKey found for the current user." @@ -44,7 +44,7 @@ class SecretRoleViewSet(ModelViewSet): # Secrets # -class SecretViewSet(WritableSerializerMixin, ModelViewSet): +class SecretViewSet(ModelViewSet): queryset = Secret.objects.select_related( 'device__primary_ip4', 'device__primary_ip6', 'role', ).prefetch_related( diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index c1f7d990d..26f9bc71e 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -1,11 +1,9 @@ from __future__ import unicode_literals -from rest_framework.viewsets import ModelViewSet - from extras.api.views import CustomFieldModelViewSet from tenancy import filters from tenancy.models import Tenant, TenantGroup -from utilities.api import FieldChoicesViewSet, WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, ModelViewSet from . import serializers @@ -31,7 +29,7 @@ class TenantGroupViewSet(ModelViewSet): # Tenants # -class TenantViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class TenantViewSet(CustomFieldModelViewSet): queryset = Tenant.objects.select_related('group') serializer_class = serializers.TenantSerializer write_serializer_class = serializers.WritableTenantSerializer diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 91e0fb8af..4f5ce4471 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -5,11 +5,12 @@ from collections import OrderedDict from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.http import Http404 +from rest_framework import mixins from rest_framework.exceptions import APIException from rest_framework.permissions import BasePermission from rest_framework.response import Response 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'] @@ -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): """ 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) def retrieve(self, request, pk): - if pk not in self._fields: raise Http404 - return Response(self._fields[pk]) def get_view_name(self): 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 diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index 2b7ce4b60..eadc93d58 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -1,10 +1,8 @@ from __future__ import unicode_literals -from rest_framework.viewsets import ModelViewSet - from dcim.models import Interface from extras.api.views import CustomFieldModelViewSet -from utilities.api import FieldChoicesViewSet, WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, ModelViewSet from virtualization import filters from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine from . import serializers @@ -34,7 +32,7 @@ class ClusterGroupViewSet(ModelViewSet): serializer_class = serializers.ClusterGroupSerializer -class ClusterViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class ClusterViewSet(CustomFieldModelViewSet): queryset = Cluster.objects.select_related('type', 'group') serializer_class = serializers.ClusterSerializer write_serializer_class = serializers.WritableClusterSerializer @@ -45,14 +43,14 @@ class ClusterViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # Virtual machines # -class VirtualMachineViewSet(WritableSerializerMixin, CustomFieldModelViewSet): +class VirtualMachineViewSet(CustomFieldModelViewSet): queryset = VirtualMachine.objects.all() serializer_class = serializers.VirtualMachineSerializer write_serializer_class = serializers.WritableVirtualMachineSerializer filter_class = filters.VirtualMachineFilter -class InterfaceViewSet(WritableSerializerMixin, ModelViewSet): +class InterfaceViewSet(ModelViewSet): queryset = Interface.objects.filter(virtual_machine__isnull=False).select_related('virtual_machine') serializer_class = serializers.InterfaceSerializer write_serializer_class = serializers.WritableInterfaceSerializer