Browse Source

Initial work on API v2.0

Jeremy Stretch 8 years ago
parent
commit
062a5bfe8d

+ 6 - 6
netbox/circuits/api/urls.py

@@ -9,17 +9,17 @@ from .views import *
 urlpatterns = [
 urlpatterns = [
 
 
     # Providers
     # Providers
-    url(r'^providers/$', ProviderListView.as_view(), name='provider_list'),
-    url(r'^providers/(?P<pk>\d+)/$', ProviderDetailView.as_view(), name='provider_detail'),
+    url(r'^providers/$', ProviderViewSet.as_view({'get': 'list'}), name='provider_list'),
+    url(r'^providers/(?P<pk>\d+)/$', ProviderViewSet.as_view({'get': 'retrieve'}), name='provider_detail'),
     url(r'^providers/(?P<pk>\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_PROVIDER},
     url(r'^providers/(?P<pk>\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_PROVIDER},
         name='provider_graphs'),
         name='provider_graphs'),
 
 
     # Circuit types
     # Circuit types
-    url(r'^circuit-types/$', CircuitTypeListView.as_view(), name='circuittype_list'),
-    url(r'^circuit-types/(?P<pk>\d+)/$', CircuitTypeDetailView.as_view(), name='circuittype_detail'),
+    url(r'^circuit-types/$', CircuitTypeViewSet.as_view({'get': 'list'}), name='circuittype_list'),
+    url(r'^circuit-types/(?P<pk>\d+)/$', CircuitTypeViewSet.as_view({'get': 'retrieve'}), name='circuittype_detail'),
 
 
     # Circuits
     # Circuits
-    url(r'^circuits/$', CircuitListView.as_view(), name='circuit_list'),
-    url(r'^circuits/(?P<pk>\d+)/$', CircuitDetailView.as_view(), name='circuit_detail'),
+    url(r'^circuits/$', CircuitViewSet.as_view({'get': 'list'}), name='circuit_list'),
+    url(r'^circuits/(?P<pk>\d+)/$', CircuitViewSet.as_view({'get': 'retrieve'}), name='circuit_detail'),
 
 
 ]
 ]

+ 20 - 34
netbox/circuits/api/views.py

@@ -1,58 +1,44 @@
-from rest_framework import generics
+from rest_framework.viewsets import ModelViewSet
 
 
 from circuits.models import Provider, CircuitType, Circuit
 from circuits.models import Provider, CircuitType, Circuit
 from circuits.filters import CircuitFilter
 from circuits.filters import CircuitFilter
 
 
-from extras.api.views import CustomFieldModelAPIView
+from extras.api.views import CustomFieldModelViewSet
 from . import serializers
 from . import serializers
 
 
 
 
-class ProviderListView(CustomFieldModelAPIView, generics.ListAPIView):
-    """
-    List all providers
-    """
-    queryset = Provider.objects.prefetch_related('custom_field_values__field')
-    serializer_class = serializers.ProviderSerializer
-
+#
+# Providers
+#
 
 
-class ProviderDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
+class ProviderViewSet(CustomFieldModelViewSet):
     """
     """
-    Retrieve a single provider
+    List and retrieve circuit providers
     """
     """
-    queryset = Provider.objects.prefetch_related('custom_field_values__field')
+    queryset = Provider.objects.all()
     serializer_class = serializers.ProviderSerializer
     serializer_class = serializers.ProviderSerializer
 
 
 
 
-class CircuitTypeListView(generics.ListAPIView):
-    """
-    List all circuit types
-    """
-    queryset = CircuitType.objects.all()
-    serializer_class = serializers.CircuitTypeSerializer
+#
+#  Circuit Types
+#
 
 
-
-class CircuitTypeDetailView(generics.RetrieveAPIView):
+class CircuitTypeViewSet(ModelViewSet):
     """
     """
-    Retrieve a single circuit type
+    List and retrieve circuit types
     """
     """
     queryset = CircuitType.objects.all()
     queryset = CircuitType.objects.all()
     serializer_class = serializers.CircuitTypeSerializer
     serializer_class = serializers.CircuitTypeSerializer
 
 
 
 
-class CircuitListView(CustomFieldModelAPIView, generics.ListAPIView):
-    """
-    List circuits (filterable)
-    """
-    queryset = Circuit.objects.select_related('type', 'tenant', 'provider')\
-        .prefetch_related('custom_field_values__field')
-    serializer_class = serializers.CircuitSerializer
-    filter_class = CircuitFilter
-
+#
+# Circuits
+#
 
 
-class CircuitDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
+class CircuitViewSet(CustomFieldModelViewSet):
     """
     """
-    Retrieve a single circuit
+    List and retrieve circuits
     """
     """
-    queryset = Circuit.objects.select_related('type', 'tenant', 'provider')\
-        .prefetch_related('custom_field_values__field')
+    queryset = Circuit.objects.select_related('type', 'tenant', 'provider')
     serializer_class = serializers.CircuitSerializer
     serializer_class = serializers.CircuitSerializer
+    filter_class = CircuitFilter

+ 25 - 27
netbox/dcim/api/urls.py

@@ -9,52 +9,50 @@ from .views import *
 urlpatterns = [
 urlpatterns = [
 
 
     # Sites
     # Sites
-    url(r'^sites/$', SiteListView.as_view(), name='site_list'),
-    url(r'^sites/(?P<pk>\d+)/$', SiteDetailView.as_view(), name='site_detail'),
+    url(r'^sites/$', SiteViewSet.as_view({'get': 'list'}), name='site_list'),
+    url(r'^sites/(?P<pk>\d+)/$', SiteViewSet.as_view({'get': 'retrieve'}), name='site_detail'),
     url(r'^sites/(?P<pk>\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_SITE}, name='site_graphs'),
     url(r'^sites/(?P<pk>\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_SITE}, name='site_graphs'),
-    url(r'^sites/(?P<site>\d+)/racks/$', RackListView.as_view(), name='site_racks'),
 
 
     # Rack groups
     # Rack groups
-    url(r'^rack-groups/$', RackGroupListView.as_view(), name='rackgroup_list'),
-    url(r'^rack-groups/(?P<pk>\d+)/$', RackGroupDetailView.as_view(), name='rackgroup_detail'),
+    url(r'^rack-groups/$', RackGroupViewSet.as_view({'get': 'list'}), name='rackgroup_list'),
+    url(r'^rack-groups/(?P<pk>\d+)/$', RackGroupViewSet.as_view({'get': 'retrieve'}), name='rackgroup_detail'),
 
 
     # Rack roles
     # Rack roles
-    url(r'^rack-roles/$', RackRoleListView.as_view(), name='rackrole_list'),
-    url(r'^rack-roles/(?P<pk>\d+)/$', RackRoleDetailView.as_view(), name='rackrole_detail'),
+    url(r'^rack-roles/$', RackRoleViewSet.as_view({'get': 'list'}), name='rackrole_list'),
+    url(r'^rack-roles/(?P<pk>\d+)/$', RackRoleViewSet.as_view({'get': 'retrieve'}), name='rackrole_detail'),
 
 
     # Racks
     # Racks
-    url(r'^racks/$', RackListView.as_view(), name='rack_list'),
-    url(r'^racks/(?P<pk>\d+)/$', RackDetailView.as_view(), name='rack_detail'),
+    url(r'^racks/$', RackViewSet.as_view({'get': 'list'}), name='rack_list'),
+    url(r'^racks/(?P<pk>\d+)/$', RackViewSet.as_view({'get': 'retrieve'}), name='rack_detail'),
     url(r'^racks/(?P<pk>\d+)/rack-units/$', RackUnitListView.as_view(), name='rack_units'),
     url(r'^racks/(?P<pk>\d+)/rack-units/$', RackUnitListView.as_view(), name='rack_units'),
 
 
     # Manufacturers
     # Manufacturers
-    url(r'^manufacturers/$', ManufacturerListView.as_view(), name='manufacturer_list'),
-    url(r'^manufacturers/(?P<pk>\d+)/$', ManufacturerDetailView.as_view(), name='manufacturer_detail'),
+    url(r'^manufacturers/$', ManufacturerViewSet.as_view({'get': 'list'}), name='manufacturer_list'),
+    url(r'^manufacturers/(?P<pk>\d+)/$', ManufacturerViewSet.as_view({'get': 'retrieve'}), name='manufacturer_detail'),
 
 
     # Device types
     # Device types
-    url(r'^device-types/$', DeviceTypeListView.as_view(), name='devicetype_list'),
-    url(r'^device-types/(?P<pk>\d+)/$', DeviceTypeDetailView.as_view(), name='devicetype_detail'),
+    url(r'^device-types/$', DeviceTypeViewSet.as_view({'get': 'list'}), name='devicetype_list'),
+    url(r'^device-types/(?P<pk>\d+)/$', DeviceTypeViewSet.as_view({'get': 'retrieve'}), name='devicetype_detail'),
 
 
     # Device roles
     # Device roles
-    url(r'^device-roles/$', DeviceRoleListView.as_view(), name='devicerole_list'),
-    url(r'^device-roles/(?P<pk>\d+)/$', DeviceRoleDetailView.as_view(), name='devicerole_detail'),
+    url(r'^device-roles/$', DeviceRoleViewSet.as_view({'get': 'list'}), name='devicerole_list'),
+    url(r'^device-roles/(?P<pk>\d+)/$', DeviceRoleViewSet.as_view({'get': 'retrieve'}), name='devicerole_detail'),
 
 
     # Platforms
     # Platforms
-    url(r'^platforms/$', PlatformListView.as_view(), name='platform_list'),
-    url(r'^platforms/(?P<pk>\d+)/$', PlatformDetailView.as_view(), name='platform_detail'),
+    url(r'^platforms/$', PlatformViewSet.as_view({'get': 'list'}), name='platform_list'),
+    url(r'^platforms/(?P<pk>\d+)/$', PlatformViewSet.as_view({'get': 'retrieve'}), name='platform_detail'),
 
 
     # Devices
     # Devices
-    url(r'^devices/$', DeviceListView.as_view(), name='device_list'),
-    url(r'^devices/(?P<pk>\d+)/$', DeviceDetailView.as_view(), name='device_detail'),
+    url(r'^devices/$', DeviceViewSet.as_view({'get': 'list'}), name='device_list'),
+    url(r'^devices/(?P<pk>\d+)/$', DeviceViewSet.as_view({'get': 'retrieve'}), name='device_detail'),
     url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', LLDPNeighborsView.as_view(), name='device_lldp-neighbors'),
     url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', LLDPNeighborsView.as_view(), name='device_lldp-neighbors'),
-    url(r'^devices/(?P<pk>\d+)/console-ports/$', ConsolePortListView.as_view(), name='device_consoleports'),
-    url(r'^devices/(?P<pk>\d+)/console-server-ports/$', ConsoleServerPortListView.as_view(),
-        name='device_consoleserverports'),
-    url(r'^devices/(?P<pk>\d+)/power-ports/$', PowerPortListView.as_view(), name='device_powerports'),
-    url(r'^devices/(?P<pk>\d+)/power-outlets/$', PowerOutletListView.as_view(), name='device_poweroutlets'),
-    url(r'^devices/(?P<pk>\d+)/interfaces/$', InterfaceListView.as_view(), name='device_interfaces'),
-    url(r'^devices/(?P<pk>\d+)/device-bays/$', DeviceBayListView.as_view(), name='device_devicebays'),
-    url(r'^devices/(?P<pk>\d+)/modules/$', ModuleListView.as_view(), name='device_modules'),
+    url(r'^devices/(?P<pk>\d+)/console-ports/$', ConsolePortViewSet.as_view({'get': 'list'}), name='device_consoleports'),
+    url(r'^devices/(?P<pk>\d+)/console-server-ports/$', ConsoleServerPortViewSet.as_view({'get': 'list'}), name='device_consoleserverports'),
+    url(r'^devices/(?P<pk>\d+)/power-ports/$', PowerPortViewSet.as_view({'get': 'list'}), name='device_powerports'),
+    url(r'^devices/(?P<pk>\d+)/power-outlets/$', PowerOutletViewSet.as_view({'get': 'list'}), name='device_poweroutlets'),
+    url(r'^devices/(?P<pk>\d+)/interfaces/$', InterfaceViewSet.as_view({'get': 'list'}), name='device_interfaces'),
+    url(r'^devices/(?P<pk>\d+)/device-bays/$', DeviceBayViewSet.as_view({'get': 'list'}), name='device_devicebays'),
+    url(r'^devices/(?P<pk>\d+)/modules/$', ModuleViewSet.as_view({'get': 'list'}), name='device_modules'),
 
 
     # Console ports
     # Console ports
     url(r'^console-ports/(?P<pk>\d+)/$', ConsolePortView.as_view(), name='consoleport'),
     url(r'^console-ports/(?P<pk>\d+)/$', ConsolePortView.as_view(), name='consoleport'),

+ 64 - 129
netbox/dcim/api/views.py

@@ -3,10 +3,10 @@ from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly
 from rest_framework.response import Response
 from rest_framework.response import Response
 from rest_framework.settings import api_settings
 from rest_framework.settings import api_settings
 from rest_framework.views import APIView
 from rest_framework.views import APIView
+from rest_framework.viewsets import ModelViewSet
 
 
 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.db.models import Count
 from django.http import Http404
 from django.http import Http404
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
 
 
@@ -15,7 +15,7 @@ from dcim.models import (
     InterfaceConnection, Manufacturer, Module, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, Site,
     InterfaceConnection, Manufacturer, Module, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, Site,
 )
 )
 from dcim import filters
 from dcim import filters
-from extras.api.views import CustomFieldModelAPIView
+from extras.api.views import CustomFieldModelViewSet
 from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer
 from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer
 from utilities.api import ServiceUnavailable
 from utilities.api import ServiceUnavailable
 from .exceptions import MissingFilterException
 from .exceptions import MissingFilterException
@@ -26,19 +26,11 @@ from . import serializers
 # Sites
 # Sites
 #
 #
 
 
-class SiteListView(CustomFieldModelAPIView, generics.ListAPIView):
+class SiteViewSet(CustomFieldModelViewSet):
     """
     """
-    List all sites
+    List and retrieve sites
     """
     """
-    queryset = Site.objects.select_related('tenant').prefetch_related('custom_field_values__field')
-    serializer_class = serializers.SiteSerializer
-
-
-class SiteDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single site
-    """
-    queryset = Site.objects.select_related('tenant').prefetch_related('custom_field_values__field')
+    queryset = Site.objects.select_related('tenant')
     serializer_class = serializers.SiteSerializer
     serializer_class = serializers.SiteSerializer
 
 
 
 
@@ -46,38 +38,22 @@ class SiteDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
 # Rack groups
 # Rack groups
 #
 #
 
 
-class RackGroupListView(generics.ListAPIView):
+class RackGroupViewSet(ModelViewSet):
     """
     """
-    List all rack groups
+    List and retrieve rack groups
     """
     """
     queryset = RackGroup.objects.select_related('site')
     queryset = RackGroup.objects.select_related('site')
     serializer_class = serializers.RackGroupSerializer
     serializer_class = serializers.RackGroupSerializer
     filter_class = filters.RackGroupFilter
     filter_class = filters.RackGroupFilter
 
 
 
 
-class RackGroupDetailView(generics.RetrieveAPIView):
-    """
-    Retrieve a single rack group
-    """
-    queryset = RackGroup.objects.select_related('site')
-    serializer_class = serializers.RackGroupSerializer
-
-
 #
 #
 # Rack roles
 # Rack roles
 #
 #
 
 
-class RackRoleListView(generics.ListAPIView):
-    """
-    List all rack roles
-    """
-    queryset = RackRole.objects.all()
-    serializer_class = serializers.RackRoleSerializer
-
-
-class RackRoleDetailView(generics.RetrieveAPIView):
+class RackRoleViewSet(ModelViewSet):
     """
     """
-    Retrieve a single rack role
+    List and retrieve rack roles
     """
     """
     queryset = RackRole.objects.all()
     queryset = RackRole.objects.all()
     serializer_class = serializers.RackRoleSerializer
     serializer_class = serializers.RackRoleSerializer
@@ -87,28 +63,18 @@ class RackRoleDetailView(generics.RetrieveAPIView):
 # Racks
 # Racks
 #
 #
 
 
-class RackListView(CustomFieldModelAPIView, generics.ListAPIView):
+class RackViewSet(CustomFieldModelViewSet):
     """
     """
-    List racks (filterable)
+    List and retrieve racks
     """
     """
-    queryset = Rack.objects.select_related('site', 'group__site', 'tenant')\
-        .prefetch_related('custom_field_values__field')
-    serializer_class = serializers.RackSerializer
+    queryset = Rack.objects.select_related('site', 'group__site', 'tenant')
     filter_class = filters.RackFilter
     filter_class = filters.RackFilter
 
 
+    def get_serializer_class(self):
+        if self.action == 'retrieve':
+            return serializers.RackDetailSerializer
+        return serializers.RackSerializer
 
 
-class RackDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single rack
-    """
-    queryset = Rack.objects.select_related('site', 'group__site', 'tenant')\
-        .prefetch_related('custom_field_values__field')
-    serializer_class = serializers.RackDetailSerializer
-
-
-#
-# Rack units
-#
 
 
 class RackUnitListView(APIView):
 class RackUnitListView(APIView):
     """
     """
@@ -139,17 +105,9 @@ class RackUnitListView(APIView):
 # Manufacturers
 # Manufacturers
 #
 #
 
 
-class ManufacturerListView(generics.ListAPIView):
-    """
-    List all hardware manufacturers
-    """
-    queryset = Manufacturer.objects.all()
-    serializer_class = serializers.ManufacturerSerializer
-
-
-class ManufacturerDetailView(generics.RetrieveAPIView):
+class ManufacturerViewSet(ModelViewSet):
     """
     """
-    Retrieve a single hardware manufacturers
+    List and retrieve manufacturers
     """
     """
     queryset = Manufacturer.objects.all()
     queryset = Manufacturer.objects.all()
     serializer_class = serializers.ManufacturerSerializer
     serializer_class = serializers.ManufacturerSerializer
@@ -159,38 +117,26 @@ class ManufacturerDetailView(generics.RetrieveAPIView):
 # Device Types
 # Device Types
 #
 #
 
 
-class DeviceTypeListView(CustomFieldModelAPIView, generics.ListAPIView):
+class DeviceTypeViewSet(CustomFieldModelViewSet):
     """
     """
-    List device types (filterable)
+    List and retrieve device types
     """
     """
-    queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('custom_field_values__field')
-    serializer_class = serializers.DeviceTypeSerializer
+    queryset = DeviceType.objects.select_related('manufacturer')
     filter_class = filters.DeviceTypeFilter
     filter_class = filters.DeviceTypeFilter
 
 
-
-class DeviceTypeDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single device type
-    """
-    queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('custom_field_values__field')
-    serializer_class = serializers.DeviceTypeDetailSerializer
+    def get_serializer_class(self):
+        if self.action == 'retrieve':
+            return serializers.DeviceTypeDetailSerializer
+        return serializers.DeviceTypeSerializer
 
 
 
 
 #
 #
 # Device roles
 # Device roles
 #
 #
 
 
-class DeviceRoleListView(generics.ListAPIView):
-    """
-    List all device roles
-    """
-    queryset = DeviceRole.objects.all()
-    serializer_class = serializers.DeviceRoleSerializer
-
-
-class DeviceRoleDetailView(generics.RetrieveAPIView):
+class DeviceRoleViewSet(ModelViewSet):
     """
     """
-    Retrieve a single device role
+    List and retrieve device roles
     """
     """
     queryset = DeviceRole.objects.all()
     queryset = DeviceRole.objects.all()
     serializer_class = serializers.DeviceRoleSerializer
     serializer_class = serializers.DeviceRoleSerializer
@@ -200,17 +146,9 @@ class DeviceRoleDetailView(generics.RetrieveAPIView):
 # Platforms
 # Platforms
 #
 #
 
 
-class PlatformListView(generics.ListAPIView):
-    """
-    List all platforms
-    """
-    queryset = Platform.objects.all()
-    serializer_class = serializers.PlatformSerializer
-
-
-class PlatformDetailView(generics.RetrieveAPIView):
+class PlatformViewSet(ModelViewSet):
     """
     """
-    Retrieve a single platform
+    List and retrieve platforms
     """
     """
     queryset = Platform.objects.all()
     queryset = Platform.objects.all()
     serializer_class = serializers.PlatformSerializer
     serializer_class = serializers.PlatformSerializer
@@ -220,40 +158,31 @@ class PlatformDetailView(generics.RetrieveAPIView):
 # Devices
 # Devices
 #
 #
 
 
-class DeviceListView(CustomFieldModelAPIView, generics.ListAPIView):
+class DeviceViewSet(CustomFieldModelViewSet):
     """
     """
-    List devices (filterable)
+    List and retrieve devices
     """
     """
-    queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'platform',
-                                             'rack__site', 'parent_bay').prefetch_related('primary_ip4__nat_outside',
-                                                                                          'primary_ip6__nat_outside',
-                                                                                          'custom_field_values__field')
+    queryset = Device.objects.select_related(
+        'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'rack__site', 'parent_bay',
+    ).prefetch_related(
+        'primary_ip4__nat_outside', 'primary_ip6__nat_outside',
+    )
     serializer_class = serializers.DeviceSerializer
     serializer_class = serializers.DeviceSerializer
     filter_class = filters.DeviceFilter
     filter_class = filters.DeviceFilter
     renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer]
     renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer]
 
 
 
 
-class DeviceDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single device
-    """
-    queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'platform',
-                                             'rack__site', 'parent_bay').prefetch_related('custom_field_values__field')
-    serializer_class = serializers.DeviceSerializer
-
-
 #
 #
 # Console ports
 # Console ports
 #
 #
 
 
-class ConsolePortListView(generics.ListAPIView):
+class ConsolePortViewSet(ModelViewSet):
     """
     """
-    List console ports (by device)
+    List and retrieve console ports (by device)
     """
     """
     serializer_class = serializers.ConsolePortSerializer
     serializer_class = serializers.ConsolePortSerializer
 
 
     def get_queryset(self):
     def get_queryset(self):
-
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         return ConsolePort.objects.filter(device=device).select_related('cs_port')
         return ConsolePort.objects.filter(device=device).select_related('cs_port')
 
 
@@ -268,14 +197,13 @@ class ConsolePortView(generics.RetrieveUpdateDestroyAPIView):
 # Console server ports
 # Console server ports
 #
 #
 
 
-class ConsoleServerPortListView(generics.ListAPIView):
+class ConsoleServerPortViewSet(ModelViewSet):
     """
     """
-    List console server ports (by device)
+    List and retrieve console server ports (by device)
     """
     """
     serializer_class = serializers.ConsoleServerPortSerializer
     serializer_class = serializers.ConsoleServerPortSerializer
 
 
     def get_queryset(self):
     def get_queryset(self):
-
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         return ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
         return ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
 
 
@@ -284,14 +212,13 @@ class ConsoleServerPortListView(generics.ListAPIView):
 # Power ports
 # Power ports
 #
 #
 
 
-class PowerPortListView(generics.ListAPIView):
+class PowerPortViewSet(ModelViewSet):
     """
     """
-    List power ports (by device)
+    List and retrieve power ports (by device)
     """
     """
     serializer_class = serializers.PowerPortSerializer
     serializer_class = serializers.PowerPortSerializer
 
 
     def get_queryset(self):
     def get_queryset(self):
-
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         return PowerPort.objects.filter(device=device).select_related('power_outlet')
         return PowerPort.objects.filter(device=device).select_related('power_outlet')
 
 
@@ -306,14 +233,13 @@ class PowerPortView(generics.RetrieveUpdateDestroyAPIView):
 # Power outlets
 # Power outlets
 #
 #
 
 
-class PowerOutletListView(generics.ListAPIView):
+class PowerOutletViewSet(ModelViewSet):
     """
     """
-    List power outlets (by device)
+    List and retrieve power outlets (by device)
     """
     """
     serializer_class = serializers.PowerOutletSerializer
     serializer_class = serializers.PowerOutletSerializer
 
 
     def get_queryset(self):
     def get_queryset(self):
-
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         return PowerOutlet.objects.filter(device=device).select_related('connected_port')
         return PowerOutlet.objects.filter(device=device).select_related('connected_port')
 
 
@@ -322,9 +248,9 @@ class PowerOutletListView(generics.ListAPIView):
 # Interfaces
 # Interfaces
 #
 #
 
 
-class InterfaceListView(generics.ListAPIView):
+class InterfaceViewSet(ModelViewSet):
     """
     """
-    List interfaces (by device)
+    List and retrieve interfaces (by device)
     """
     """
     serializer_class = serializers.InterfaceSerializer
     serializer_class = serializers.InterfaceSerializer
     filter_class = filters.InterfaceFilter
     filter_class = filters.InterfaceFilter
@@ -372,14 +298,13 @@ class InterfaceConnectionListView(generics.ListAPIView):
 # Device bays
 # Device bays
 #
 #
 
 
-class DeviceBayListView(generics.ListAPIView):
+class DeviceBayViewSet(ModelViewSet):
     """
     """
-    List device bays (by device)
+    List and retrieve device bays (by device)
     """
     """
     serializer_class = serializers.DeviceBayNestedSerializer
     serializer_class = serializers.DeviceBayNestedSerializer
 
 
     def get_queryset(self):
     def get_queryset(self):
-
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         return DeviceBay.objects.filter(device=device).select_related('installed_device')
         return DeviceBay.objects.filter(device=device).select_related('installed_device')
 
 
@@ -388,14 +313,13 @@ class DeviceBayListView(generics.ListAPIView):
 # Modules
 # Modules
 #
 #
 
 
-class ModuleListView(generics.ListAPIView):
+class ModuleViewSet(ModelViewSet):
     """
     """
-    List device modules (by device)
+    List and retrieve modules (by device)
     """
     """
     serializer_class = serializers.ModuleSerializer
     serializer_class = serializers.ModuleSerializer
 
 
     def get_queryset(self):
     def get_queryset(self):
-
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         device = get_object_or_404(Device, pk=self.kwargs['pk'])
         return Module.objects.filter(device=device).select_related('device', 'manufacturer')
         return Module.objects.filter(device=device).select_related('device', 'manufacturer')
 
 
@@ -442,8 +366,19 @@ class RelatedConnectionsView(APIView):
         super(RelatedConnectionsView, self).__init__()
         super(RelatedConnectionsView, self).__init__()
 
 
         # Custom fields
         # Custom fields
-        self.content_type = ContentType.objects.get_for_model(Device)
-        self.custom_fields = self.content_type.custom_fields.prefetch_related('choices')
+        content_type = ContentType.objects.get_for_model(Device)
+        custom_fields = content_type.custom_fields.prefetch_related('choices')
+
+        # Cache all relevant CustomFieldChoices. This saves us from having to do a lookup per select field per object.
+        custom_field_choices = {}
+        for field in custom_fields:
+            for cfc in field.choices.all():
+                custom_field_choices[cfc.id] = cfc.value
+
+        self.context = {
+            'custom_fields': custom_fields,
+            'custom_field_choices': custom_field_choices,
+        }
 
 
     def get(self, request):
     def get(self, request):
 
 
@@ -469,7 +404,7 @@ class RelatedConnectionsView(APIView):
 
 
         # Initialize response skeleton
         # Initialize response skeleton
         response = {
         response = {
-            'device': serializers.DeviceSerializer(device, context={'view': self}).data,
+            'device': serializers.DeviceSerializer(device, context=self.context).data,
             'console-ports': [],
             'console-ports': [],
             'power-ports': [],
             'power-ports': [],
             'interfaces': [],
             'interfaces': [],

+ 1 - 15
netbox/dcim/tests/test_apis.py

@@ -82,21 +82,6 @@ class SiteTest(APITestCase):
             sorted(self.standard_fields),
             sorted(self.standard_fields),
         )
         )
 
 
-    def test_get_site_list_rack(self, endpoint='/{}api/dcim/sites/1/racks/'.format(settings.BASE_PATH)):
-        response = self.client.get(endpoint)
-        content = json.loads(response.content.decode('utf-8'))
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        for i in json.loads(response.content.decode('utf-8')):
-            self.assertEqual(
-                sorted(i.keys()),
-                sorted(self.rack_fields),
-            )
-            # Check Nested Serializer.
-            self.assertEqual(
-                sorted(i.get('site').keys()),
-                sorted(self.nested_fields),
-            )
-
     def test_get_site_list_graphs(self, endpoint='/{}api/dcim/sites/1/graphs/'.format(settings.BASE_PATH)):
     def test_get_site_list_graphs(self, endpoint='/{}api/dcim/sites/1/graphs/'.format(settings.BASE_PATH)):
         response = self.client.get(endpoint)
         response = self.client.get(endpoint)
         content = json.loads(response.content.decode('utf-8'))
         content = json.loads(response.content.decode('utf-8'))
@@ -239,6 +224,7 @@ class DeviceTypeTest(APITestCase):
         'subdevice_role',
         'subdevice_role',
         'comments',
         'comments',
         'custom_fields',
         'custom_fields',
+        'instance_count',
     ]
     ]
 
 
     nested_fields = [
     nested_fields = [

+ 2 - 0
netbox/extras/api/renderers.py

@@ -27,6 +27,8 @@ class BINDZoneRenderer(renderers.BaseRenderer):
 
 
     def render(self, data, media_type=None, renderer_context=None):
     def render(self, data, media_type=None, renderer_context=None):
         records = []
         records = []
+        if not isinstance(data, (list, tuple)):
+            data = (data,)
         for record in data:
         for record in data:
             if record.get('name') and record.get('primary_ip'):
             if record.get('name') and record.get('primary_ip'):
                 try:
                 try:

+ 4 - 6
netbox/extras/api/serializers.py

@@ -12,22 +12,20 @@ class CustomFieldSerializer(serializers.Serializer):
     def get_custom_fields(self, obj):
     def get_custom_fields(self, obj):
 
 
         # Gather all CustomFields applicable to this object
         # Gather all CustomFields applicable to this object
-        fields = {cf.name: None for cf in self.context['view'].custom_fields}
+        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
         # Attach any defined CustomFieldValues to their respective CustomFields
         for cfv in obj.custom_field_values.all():
         for cfv in obj.custom_field_values.all():
 
 
             # Attempt to suppress database lookups for CustomFieldChoices by using the cached choice set from the view
             # Attempt to suppress database lookups for CustomFieldChoices by using the cached choice set from the view
             # context.
             # context.
-            if cfv.field.type == CF_TYPE_SELECT and hasattr(self, 'custom_field_choices'):
+            if cfv.field.type == CF_TYPE_SELECT:
                 cfc = {
                 cfc = {
                     'id': int(cfv.serialized_value),
                     'id': int(cfv.serialized_value),
-                    'value': self.context['view'].custom_field_choices[int(cfv.serialized_value)]
+                    'value': custom_field_choices[int(cfv.serialized_value)]
                 }
                 }
                 fields[cfv.field.name] = CustomFieldChoiceSerializer(instance=cfc).data
                 fields[cfv.field.name] = CustomFieldChoiceSerializer(instance=cfc).data
-            # Fall back to hitting the database in case we're in a view that doesn't inherit CustomFieldModelAPIView.
-            elif cfv.field.type == CF_TYPE_SELECT:
-                fields[cfv.field.name] = CustomFieldChoiceSerializer(instance=cfv.value).data
             else:
             else:
                 fields[cfv.field.name] = cfv.value
                 fields[cfv.field.name] = cfv.value
 
 

+ 19 - 8
netbox/extras/api/views.py

@@ -1,6 +1,7 @@
 import graphviz
 import graphviz
 from rest_framework import generics
 from rest_framework import generics
 from rest_framework.views import APIView
 from rest_framework.views import APIView
+from rest_framework.viewsets import ModelViewSet
 
 
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
 from django.db.models import Q
@@ -14,22 +15,32 @@ from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_P
 from .serializers import GraphSerializer
 from .serializers import GraphSerializer
 
 
 
 
-class CustomFieldModelAPIView(object):
+class CustomFieldModelViewSet(ModelViewSet):
     """
     """
-    Include the applicable set of CustomField in the view context.
+    Include the applicable set of CustomField in the ModelViewSet context.
     """
     """
 
 
-    def __init__(self):
-        super(CustomFieldModelAPIView, self).__init__()
-        self.content_type = ContentType.objects.get_for_model(self.queryset.model)
-        self.custom_fields = self.content_type.custom_fields.prefetch_related('choices')
+    def get_serializer_context(self):
+
+        # Gather all custom fields for the model
+        content_type = ContentType.objects.get_for_model(self.queryset.model)
+        custom_fields = content_type.custom_fields.prefetch_related('choices')
 
 
         # Cache all relevant CustomFieldChoices. This saves us from having to do a lookup per select field per object.
         # Cache all relevant CustomFieldChoices. This saves us from having to do a lookup per select field per object.
         custom_field_choices = {}
         custom_field_choices = {}
-        for field in self.custom_fields:
+        for field in custom_fields:
             for cfc in field.choices.all():
             for cfc in field.choices.all():
                 custom_field_choices[cfc.id] = cfc.value
                 custom_field_choices[cfc.id] = cfc.value
-        self.custom_field_choices = custom_field_choices
+        custom_field_choices = custom_field_choices
+
+        return {
+            'custom_fields': custom_fields,
+            'custom_field_choices': custom_field_choices,
+        }
+
+    def get_queryset(self):
+        # Prefetch custom field values
+        return super(CustomFieldModelViewSet, self).get_queryset().prefetch_related('custom_field_values__field')
 
 
 
 
 class GraphListView(generics.ListAPIView):
 class GraphListView(generics.ListAPIView):

+ 18 - 18
netbox/ipam/api/urls.py

@@ -6,39 +6,39 @@ from .views import *
 urlpatterns = [
 urlpatterns = [
 
 
     # VRFs
     # VRFs
-    url(r'^vrfs/$', VRFListView.as_view(), name='vrf_list'),
-    url(r'^vrfs/(?P<pk>\d+)/$', VRFDetailView.as_view(), name='vrf_detail'),
+    url(r'^vrfs/$', VRFViewSet.as_view({'get': 'list'}), name='vrf_list'),
+    url(r'^vrfs/(?P<pk>\d+)/$', VRFViewSet.as_view({'get': 'retrieve'}), name='vrf_detail'),
 
 
     # Roles
     # Roles
-    url(r'^roles/$', RoleListView.as_view(), name='role_list'),
-    url(r'^roles/(?P<pk>\d+)/$', RoleDetailView.as_view(), name='role_detail'),
+    url(r'^roles/$', RoleViewSet.as_view({'get': 'list'}), name='role_list'),
+    url(r'^roles/(?P<pk>\d+)/$', RoleViewSet.as_view({'get': 'retrieve'}), name='role_detail'),
 
 
     # RIRs
     # RIRs
-    url(r'^rirs/$', RIRListView.as_view(), name='rir_list'),
-    url(r'^rirs/(?P<pk>\d+)/$', RIRDetailView.as_view(), name='rir_detail'),
+    url(r'^rirs/$', RIRViewSet.as_view({'get': 'list'}), name='rir_list'),
+    url(r'^rirs/(?P<pk>\d+)/$', RIRViewSet.as_view({'get': 'retrieve'}), name='rir_detail'),
 
 
     # Aggregates
     # Aggregates
-    url(r'^aggregates/$', AggregateListView.as_view(), name='aggregate_list'),
-    url(r'^aggregates/(?P<pk>\d+)/$', AggregateDetailView.as_view(), name='aggregate_detail'),
+    url(r'^aggregates/$', AggregateViewSet.as_view({'get': 'list'}), name='aggregate_list'),
+    url(r'^aggregates/(?P<pk>\d+)/$', AggregateViewSet.as_view({'get': 'retrieve'}), name='aggregate_detail'),
 
 
     # Prefixes
     # Prefixes
-    url(r'^prefixes/$', PrefixListView.as_view(), name='prefix_list'),
-    url(r'^prefixes/(?P<pk>\d+)/$', PrefixDetailView.as_view(), name='prefix_detail'),
+    url(r'^prefixes/$', PrefixViewSet.as_view({'get': 'list'}), name='prefix_list'),
+    url(r'^prefixes/(?P<pk>\d+)/$', PrefixViewSet.as_view({'get': 'retrieve'}), name='prefix_detail'),
 
 
     # IP addresses
     # IP addresses
-    url(r'^ip-addresses/$', IPAddressListView.as_view(), name='ipaddress_list'),
-    url(r'^ip-addresses/(?P<pk>\d+)/$', IPAddressDetailView.as_view(), name='ipaddress_detail'),
+    url(r'^ip-addresses/$', IPAddressViewSet.as_view({'get': 'list'}), name='ipaddress_list'),
+    url(r'^ip-addresses/(?P<pk>\d+)/$', IPAddressViewSet.as_view({'get': 'retrieve'}), name='ipaddress_detail'),
 
 
     # VLAN groups
     # VLAN groups
-    url(r'^vlan-groups/$', VLANGroupListView.as_view(), name='vlangroup_list'),
-    url(r'^vlan-groups/(?P<pk>\d+)/$', VLANGroupDetailView.as_view(), name='vlangroup_detail'),
+    url(r'^vlan-groups/$', VLANGroupViewSet.as_view({'get': 'list'}), name='vlangroup_list'),
+    url(r'^vlan-groups/(?P<pk>\d+)/$', VLANGroupViewSet.as_view({'get': 'retrieve'}), name='vlangroup_detail'),
 
 
     # VLANs
     # VLANs
-    url(r'^vlans/$', VLANListView.as_view(), name='vlan_list'),
-    url(r'^vlans/(?P<pk>\d+)/$', VLANDetailView.as_view(), name='vlan_detail'),
+    url(r'^vlans/$', VLANViewSet.as_view({'get': 'list'}), name='vlan_list'),
+    url(r'^vlans/(?P<pk>\d+)/$', VLANViewSet.as_view({'get': 'retrieve'}), name='vlan_detail'),
 
 
     # Services
     # Services
-    url(r'^services/$', ServiceListView.as_view(), name='service_list'),
-    url(r'^services/(?P<pk>\d+)/$', ServiceDetailView.as_view(), name='service_detail'),
+    url(r'^services/$', ServiceViewSet.as_view({'get': 'list'}), name='service_list'),
+    url(r'^services/(?P<pk>\d+)/$', ServiceViewSet.as_view({'get': 'retrieve'}), name='service_detail'),
 
 
 ]
 ]

+ 25 - 103
netbox/ipam/api/views.py

@@ -1,9 +1,9 @@
-from rest_framework import generics
+from rest_framework.viewsets import ModelViewSet
 
 
 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 ipam import filters
 from ipam import filters
 
 
-from extras.api.views import CustomFieldModelAPIView
+from extras.api.views import CustomFieldModelViewSet
 from . import serializers
 from . import serializers
 
 
 
 
@@ -11,38 +11,22 @@ from . import serializers
 # VRFs
 # VRFs
 #
 #
 
 
-class VRFListView(CustomFieldModelAPIView, generics.ListAPIView):
+class VRFViewSet(CustomFieldModelViewSet):
     """
     """
-    List all VRFs
+    List and retrieve VRFs
     """
     """
-    queryset = VRF.objects.select_related('tenant').prefetch_related('custom_field_values__field')
+    queryset = VRF.objects.select_related('tenant')
     serializer_class = serializers.VRFSerializer
     serializer_class = serializers.VRFSerializer
     filter_class = filters.VRFFilter
     filter_class = filters.VRFFilter
 
 
 
 
-class VRFDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single VRF
-    """
-    queryset = VRF.objects.select_related('tenant').prefetch_related('custom_field_values__field')
-    serializer_class = serializers.VRFSerializer
-
-
 #
 #
 # Roles
 # Roles
 #
 #
 
 
-class RoleListView(generics.ListAPIView):
+class RoleViewSet(ModelViewSet):
     """
     """
-    List all roles
-    """
-    queryset = Role.objects.all()
-    serializer_class = serializers.RoleSerializer
-
-
-class RoleDetailView(generics.RetrieveAPIView):
-    """
-    Retrieve a single role
+    List and retrieve prefix/VLAN roles
     """
     """
     queryset = Role.objects.all()
     queryset = Role.objects.all()
     serializer_class = serializers.RoleSerializer
     serializer_class = serializers.RoleSerializer
@@ -52,17 +36,9 @@ class RoleDetailView(generics.RetrieveAPIView):
 # RIRs
 # RIRs
 #
 #
 
 
-class RIRListView(generics.ListAPIView):
+class RIRViewSet(ModelViewSet):
     """
     """
-    List all RIRs
-    """
-    queryset = RIR.objects.all()
-    serializer_class = serializers.RIRSerializer
-
-
-class RIRDetailView(generics.RetrieveAPIView):
-    """
-    Retrieve a single RIR
+    List and retrieve RIRs
     """
     """
     queryset = RIR.objects.all()
     queryset = RIR.objects.all()
     serializer_class = serializers.RIRSerializer
     serializer_class = serializers.RIRSerializer
@@ -72,129 +48,75 @@ class RIRDetailView(generics.RetrieveAPIView):
 # Aggregates
 # Aggregates
 #
 #
 
 
-class AggregateListView(CustomFieldModelAPIView, generics.ListAPIView):
+class AggregateViewSet(CustomFieldModelViewSet):
     """
     """
-    List aggregates (filterable)
+    List and retrieve aggregates
     """
     """
-    queryset = Aggregate.objects.select_related('rir').prefetch_related('custom_field_values__field')
+    queryset = Aggregate.objects.select_related('rir')
     serializer_class = serializers.AggregateSerializer
     serializer_class = serializers.AggregateSerializer
     filter_class = filters.AggregateFilter
     filter_class = filters.AggregateFilter
 
 
 
 
-class AggregateDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single aggregate
-    """
-    queryset = Aggregate.objects.select_related('rir').prefetch_related('custom_field_values__field')
-    serializer_class = serializers.AggregateSerializer
-
-
 #
 #
 # Prefixes
 # Prefixes
 #
 #
 
 
-class PrefixListView(CustomFieldModelAPIView, generics.ListAPIView):
+class PrefixViewSet(CustomFieldModelViewSet):
     """
     """
-    List prefixes (filterable)
+    List and retrieve prefixes
     """
     """
-    queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')\
-        .prefetch_related('custom_field_values__field')
+    queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
     serializer_class = serializers.PrefixSerializer
     serializer_class = serializers.PrefixSerializer
     filter_class = filters.PrefixFilter
     filter_class = filters.PrefixFilter
 
 
 
 
-class PrefixDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single prefix
-    """
-    queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')\
-        .prefetch_related('custom_field_values__field')
-    serializer_class = serializers.PrefixSerializer
-
-
 #
 #
 # IP addresses
 # IP addresses
 #
 #
 
 
-class IPAddressListView(CustomFieldModelAPIView, generics.ListAPIView):
+class IPAddressViewSet(CustomFieldModelViewSet):
     """
     """
-    List IP addresses (filterable)
+    List and retrieve IP addresses
     """
     """
-    queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside')\
-        .prefetch_related('nat_outside', 'custom_field_values__field')
+    queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside')
     serializer_class = serializers.IPAddressSerializer
     serializer_class = serializers.IPAddressSerializer
     filter_class = filters.IPAddressFilter
     filter_class = filters.IPAddressFilter
 
 
 
 
-class IPAddressDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single IP address
-    """
-    queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside')\
-        .prefetch_related('nat_outside', 'custom_field_values__field')
-    serializer_class = serializers.IPAddressSerializer
-
-
 #
 #
 # VLAN groups
 # VLAN groups
 #
 #
 
 
-class VLANGroupListView(generics.ListAPIView):
+class VLANGroupViewSet(ModelViewSet):
     """
     """
-    List all VLAN groups
+    List and retrieve VLAN groups
     """
     """
     queryset = VLANGroup.objects.select_related('site')
     queryset = VLANGroup.objects.select_related('site')
     serializer_class = serializers.VLANGroupSerializer
     serializer_class = serializers.VLANGroupSerializer
     filter_class = filters.VLANGroupFilter
     filter_class = filters.VLANGroupFilter
 
 
 
 
-class VLANGroupDetailView(generics.RetrieveAPIView):
-    """
-    Retrieve a single VLAN group
-    """
-    queryset = VLANGroup.objects.select_related('site')
-    serializer_class = serializers.VLANGroupSerializer
-
-
 #
 #
 # VLANs
 # VLANs
 #
 #
 
 
-class VLANListView(CustomFieldModelAPIView, generics.ListAPIView):
+class VLANViewSet(CustomFieldModelViewSet):
     """
     """
-    List VLANs (filterable)
+    List and retrieve VLANs
     """
     """
-    queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')\
-        .prefetch_related('custom_field_values__field')
+    queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
     serializer_class = serializers.VLANSerializer
     serializer_class = serializers.VLANSerializer
     filter_class = filters.VLANFilter
     filter_class = filters.VLANFilter
 
 
 
 
-class VLANDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
-    """
-    Retrieve a single VLAN
-    """
-    queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')\
-        .prefetch_related('custom_field_values__field')
-    serializer_class = serializers.VLANSerializer
-
-
 #
 #
 # Services
 # Services
 #
 #
 
 
-class ServiceListView(generics.ListAPIView):
+class ServiceViewSet(ModelViewSet):
     """
     """
-    List services (filterable)
+    List and retrieve services
     """
     """
     queryset = Service.objects.select_related('device').prefetch_related('ipaddresses')
     queryset = Service.objects.select_related('device').prefetch_related('ipaddresses')
     serializer_class = serializers.ServiceSerializer
     serializer_class = serializers.ServiceSerializer
     filter_class = filters.ServiceFilter
     filter_class = filters.ServiceFilter
-
-
-class ServiceDetailView(generics.RetrieveAPIView):
-    """
-    Retrieve a single service
-    """
-    queryset = Service.objects.select_related('device').prefetch_related('ipaddresses')
-    serializer_class = serializers.ServiceSerializer

+ 4 - 4
netbox/secrets/api/urls.py

@@ -5,14 +5,14 @@ from .views import *
 
 
 urlpatterns = [
 urlpatterns = [
 
 
+    # Secret roles
+    url(r'^secret-roles/$', SecretRoleViewSet.as_view({'get': 'list'}), name='secretrole_list'),
+    url(r'^secret-roles/(?P<pk>\d+)/$', SecretRoleViewSet.as_view({'get': 'retrieve'}), name='secretrole_detail'),
+
     # Secrets
     # Secrets
     url(r'^secrets/$', SecretListView.as_view(), name='secret_list'),
     url(r'^secrets/$', SecretListView.as_view(), name='secret_list'),
     url(r'^secrets/(?P<pk>\d+)/$', SecretDetailView.as_view(), name='secret_detail'),
     url(r'^secrets/(?P<pk>\d+)/$', SecretDetailView.as_view(), name='secret_detail'),
 
 
-    # Secret roles
-    url(r'^secret-roles/$', SecretRoleListView.as_view(), name='secretrole_list'),
-    url(r'^secret-roles/(?P<pk>\d+)/$', SecretRoleDetailView.as_view(), name='secretrole_detail'),
-
     # Miscellaneous
     # Miscellaneous
     url(r'^generate-keys/$', RSAKeyGeneratorView.as_view(), name='generate_keys'),
     url(r'^generate-keys/$', RSAKeyGeneratorView.as_view(), name='generate_keys'),
 
 

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

@@ -9,6 +9,7 @@ from rest_framework.permissions import IsAuthenticated
 from rest_framework.renderers import JSONRenderer
 from rest_framework.renderers import JSONRenderer
 from rest_framework.response import Response
 from rest_framework.response import Response
 from rest_framework.views import APIView
 from rest_framework.views import APIView
+from rest_framework.viewsets import ModelViewSet
 
 
 from extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer
 from extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer
 from secrets.filters import SecretFilter
 from secrets.filters import SecretFilter
@@ -22,24 +23,23 @@ ERR_USERKEY_INACTIVE = "UserKey has not been activated for decryption."
 ERR_PRIVKEY_INVALID = "Invalid private key."
 ERR_PRIVKEY_INVALID = "Invalid private key."
 
 
 
 
-class SecretRoleListView(generics.ListAPIView):
-    """
-    List all secret roles
-    """
-    queryset = SecretRole.objects.all()
-    serializer_class = serializers.SecretRoleSerializer
-    permission_classes = [IsAuthenticated]
-
+#
+# Secret Roles
+#
 
 
-class SecretRoleDetailView(generics.RetrieveAPIView):
+class SecretRoleViewSet(ModelViewSet):
     """
     """
-    Retrieve a single secret role
+    List and retrieve secret roles
     """
     """
     queryset = SecretRole.objects.all()
     queryset = SecretRole.objects.all()
     serializer_class = serializers.SecretRoleSerializer
     serializer_class = serializers.SecretRoleSerializer
     permission_classes = [IsAuthenticated]
     permission_classes = [IsAuthenticated]
 
 
 
 
+#
+# Secrets
+#
+
 class SecretListView(generics.GenericAPIView):
 class SecretListView(generics.GenericAPIView):
     """
     """
     List secrets (filterable). If a private key is POSTed, attempt to decrypt each Secret.
     List secrets (filterable). If a private key is POSTed, attempt to decrypt each Secret.

+ 4 - 4
netbox/tenancy/api/urls.py

@@ -6,11 +6,11 @@ from .views import *
 urlpatterns = [
 urlpatterns = [
 
 
     # Tenant groups
     # Tenant groups
-    url(r'^tenant-groups/$', TenantGroupListView.as_view(), name='tenantgroup_list'),
-    url(r'^tenant-groups/(?P<pk>\d+)/$', TenantGroupDetailView.as_view(), name='tenantgroup_detail'),
+    url(r'^tenant-groups/$', TenantGroupViewSet.as_view({'get': 'list'}), name='tenantgroup_list'),
+    url(r'^tenant-groups/(?P<pk>\d+)/$', TenantGroupViewSet.as_view({'get': 'retrieve'}), name='tenantgroup_detail'),
 
 
     # Tenants
     # Tenants
-    url(r'^tenants/$', TenantListView.as_view(), name='tenant_list'),
-    url(r'^tenants/(?P<pk>\d+)/$', TenantDetailView.as_view(), name='tenant_detail'),
+    url(r'^tenants/$', TenantViewSet.as_view({'get': 'list'}), name='tenant_list'),
+    url(r'^tenants/(?P<pk>\d+)/$', TenantViewSet.as_view({'get': 'retrieve'}), name='tenant_detail'),
 
 
 ]
 ]

+ 14 - 22
netbox/tenancy/api/views.py

@@ -1,40 +1,32 @@
-from rest_framework import generics
+from rest_framework.viewsets import ModelViewSet
 
 
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from tenancy.filters import TenantFilter
 from tenancy.filters import TenantFilter
 
 
-from extras.api.views import CustomFieldModelAPIView
+from extras.api.views import CustomFieldModelViewSet
 from . import serializers
 from . import serializers
 
 
 
 
-class TenantGroupListView(generics.ListAPIView):
-    """
-    List all tenant groups
-    """
-    queryset = TenantGroup.objects.all()
-    serializer_class = serializers.TenantGroupSerializer
-
+#
+# Tenant Groups
+#
 
 
-class TenantGroupDetailView(generics.RetrieveAPIView):
+class TenantGroupViewSet(ModelViewSet):
     """
     """
-    Retrieve a single circuit type
+    List and retrieve tenant groups
     """
     """
     queryset = TenantGroup.objects.all()
     queryset = TenantGroup.objects.all()
     serializer_class = serializers.TenantGroupSerializer
     serializer_class = serializers.TenantGroupSerializer
 
 
 
 
-class TenantListView(CustomFieldModelAPIView, generics.ListAPIView):
-    """
-    List tenants (filterable)
-    """
-    queryset = Tenant.objects.select_related('group').prefetch_related('custom_field_values__field')
-    serializer_class = serializers.TenantSerializer
-    filter_class = TenantFilter
+#
+# Tenants
+#
 
 
-
-class TenantDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView):
+class TenantViewSet(CustomFieldModelViewSet):
     """
     """
-    Retrieve a single tenant
+    List and retrieve tenants
     """
     """
-    queryset = Tenant.objects.select_related('group').prefetch_related('custom_field_values__field')
+    queryset = Tenant.objects.select_related('group')
     serializer_class = serializers.TenantSerializer
     serializer_class = serializers.TenantSerializer
+    filter_class = TenantFilter