diff --git a/netbox/circuits/api/urls.py b/netbox/circuits/api/urls.py index afc034141..956b87207 100644 --- a/netbox/circuits/api/urls.py +++ b/netbox/circuits/api/urls.py @@ -9,17 +9,17 @@ from .views import * urlpatterns = [ # Providers - url(r'^providers/$', ProviderListView.as_view(), name='provider_list'), - url(r'^providers/(?P\d+)/$', ProviderDetailView.as_view(), name='provider_detail'), + url(r'^providers/$', ProviderViewSet.as_view({'get': 'list'}), name='provider_list'), + url(r'^providers/(?P\d+)/$', ProviderViewSet.as_view({'get': 'retrieve'}), name='provider_detail'), url(r'^providers/(?P\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_PROVIDER}, name='provider_graphs'), # Circuit types - url(r'^circuit-types/$', CircuitTypeListView.as_view(), name='circuittype_list'), - url(r'^circuit-types/(?P\d+)/$', CircuitTypeDetailView.as_view(), name='circuittype_detail'), + url(r'^circuit-types/$', CircuitTypeViewSet.as_view({'get': 'list'}), name='circuittype_list'), + url(r'^circuit-types/(?P\d+)/$', CircuitTypeViewSet.as_view({'get': 'retrieve'}), name='circuittype_detail'), # Circuits - url(r'^circuits/$', CircuitListView.as_view(), name='circuit_list'), - url(r'^circuits/(?P\d+)/$', CircuitDetailView.as_view(), name='circuit_detail'), + url(r'^circuits/$', CircuitViewSet.as_view({'get': 'list'}), name='circuit_list'), + url(r'^circuits/(?P\d+)/$', CircuitViewSet.as_view({'get': 'retrieve'}), name='circuit_detail'), ] diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index d89286036..6c64da329 100644 --- a/netbox/circuits/api/views.py +++ b/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.filters import CircuitFilter -from extras.api.views import CustomFieldModelAPIView +from extras.api.views import CustomFieldModelViewSet from . import serializers -class ProviderListView(CustomFieldModelAPIView, generics.ListAPIView): +# +# Providers +# + +class ProviderViewSet(CustomFieldModelViewSet): """ - List all providers + List and retrieve circuit providers """ - queryset = Provider.objects.prefetch_related('custom_field_values__field') + queryset = Provider.objects.all() serializer_class = serializers.ProviderSerializer -class ProviderDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView): - """ - Retrieve a single provider - """ - queryset = Provider.objects.prefetch_related('custom_field_values__field') - serializer_class = serializers.ProviderSerializer +# +# Circuit Types +# - -class CircuitTypeListView(generics.ListAPIView): +class CircuitTypeViewSet(ModelViewSet): """ - List all circuit types + List and retrieve circuit types """ queryset = CircuitType.objects.all() serializer_class = serializers.CircuitTypeSerializer -class CircuitTypeDetailView(generics.RetrieveAPIView): - """ - Retrieve a single circuit type - """ - queryset = CircuitType.objects.all() - serializer_class = serializers.CircuitTypeSerializer +# +# Circuits +# - -class CircuitListView(CustomFieldModelAPIView, generics.ListAPIView): +class CircuitViewSet(CustomFieldModelViewSet): """ - List circuits (filterable) + 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 filter_class = CircuitFilter - - -class CircuitDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView): - """ - Retrieve a single circuit - """ - queryset = Circuit.objects.select_related('type', 'tenant', 'provider')\ - .prefetch_related('custom_field_values__field') - serializer_class = serializers.CircuitSerializer diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 23787f4b4..a0ea5796b 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -9,52 +9,50 @@ from .views import * urlpatterns = [ # Sites - url(r'^sites/$', SiteListView.as_view(), name='site_list'), - url(r'^sites/(?P\d+)/$', SiteDetailView.as_view(), name='site_detail'), + url(r'^sites/$', SiteViewSet.as_view({'get': 'list'}), name='site_list'), + url(r'^sites/(?P\d+)/$', SiteViewSet.as_view({'get': 'retrieve'}), name='site_detail'), url(r'^sites/(?P\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_SITE}, name='site_graphs'), - url(r'^sites/(?P\d+)/racks/$', RackListView.as_view(), name='site_racks'), # Rack groups - url(r'^rack-groups/$', RackGroupListView.as_view(), name='rackgroup_list'), - url(r'^rack-groups/(?P\d+)/$', RackGroupDetailView.as_view(), name='rackgroup_detail'), + url(r'^rack-groups/$', RackGroupViewSet.as_view({'get': 'list'}), name='rackgroup_list'), + url(r'^rack-groups/(?P\d+)/$', RackGroupViewSet.as_view({'get': 'retrieve'}), name='rackgroup_detail'), # Rack roles - url(r'^rack-roles/$', RackRoleListView.as_view(), name='rackrole_list'), - url(r'^rack-roles/(?P\d+)/$', RackRoleDetailView.as_view(), name='rackrole_detail'), + url(r'^rack-roles/$', RackRoleViewSet.as_view({'get': 'list'}), name='rackrole_list'), + url(r'^rack-roles/(?P\d+)/$', RackRoleViewSet.as_view({'get': 'retrieve'}), name='rackrole_detail'), # Racks - url(r'^racks/$', RackListView.as_view(), name='rack_list'), - url(r'^racks/(?P\d+)/$', RackDetailView.as_view(), name='rack_detail'), + url(r'^racks/$', RackViewSet.as_view({'get': 'list'}), name='rack_list'), + url(r'^racks/(?P\d+)/$', RackViewSet.as_view({'get': 'retrieve'}), name='rack_detail'), url(r'^racks/(?P\d+)/rack-units/$', RackUnitListView.as_view(), name='rack_units'), # Manufacturers - url(r'^manufacturers/$', ManufacturerListView.as_view(), name='manufacturer_list'), - url(r'^manufacturers/(?P\d+)/$', ManufacturerDetailView.as_view(), name='manufacturer_detail'), + url(r'^manufacturers/$', ManufacturerViewSet.as_view({'get': 'list'}), name='manufacturer_list'), + url(r'^manufacturers/(?P\d+)/$', ManufacturerViewSet.as_view({'get': 'retrieve'}), name='manufacturer_detail'), # Device types - url(r'^device-types/$', DeviceTypeListView.as_view(), name='devicetype_list'), - url(r'^device-types/(?P\d+)/$', DeviceTypeDetailView.as_view(), name='devicetype_detail'), + url(r'^device-types/$', DeviceTypeViewSet.as_view({'get': 'list'}), name='devicetype_list'), + url(r'^device-types/(?P\d+)/$', DeviceTypeViewSet.as_view({'get': 'retrieve'}), name='devicetype_detail'), # Device roles - url(r'^device-roles/$', DeviceRoleListView.as_view(), name='devicerole_list'), - url(r'^device-roles/(?P\d+)/$', DeviceRoleDetailView.as_view(), name='devicerole_detail'), + url(r'^device-roles/$', DeviceRoleViewSet.as_view({'get': 'list'}), name='devicerole_list'), + url(r'^device-roles/(?P\d+)/$', DeviceRoleViewSet.as_view({'get': 'retrieve'}), name='devicerole_detail'), # Platforms - url(r'^platforms/$', PlatformListView.as_view(), name='platform_list'), - url(r'^platforms/(?P\d+)/$', PlatformDetailView.as_view(), name='platform_detail'), + url(r'^platforms/$', PlatformViewSet.as_view({'get': 'list'}), name='platform_list'), + url(r'^platforms/(?P\d+)/$', PlatformViewSet.as_view({'get': 'retrieve'}), name='platform_detail'), # Devices - url(r'^devices/$', DeviceListView.as_view(), name='device_list'), - url(r'^devices/(?P\d+)/$', DeviceDetailView.as_view(), name='device_detail'), + url(r'^devices/$', DeviceViewSet.as_view({'get': 'list'}), name='device_list'), + url(r'^devices/(?P\d+)/$', DeviceViewSet.as_view({'get': 'retrieve'}), name='device_detail'), url(r'^devices/(?P\d+)/lldp-neighbors/$', LLDPNeighborsView.as_view(), name='device_lldp-neighbors'), - url(r'^devices/(?P\d+)/console-ports/$', ConsolePortListView.as_view(), name='device_consoleports'), - url(r'^devices/(?P\d+)/console-server-ports/$', ConsoleServerPortListView.as_view(), - name='device_consoleserverports'), - url(r'^devices/(?P\d+)/power-ports/$', PowerPortListView.as_view(), name='device_powerports'), - url(r'^devices/(?P\d+)/power-outlets/$', PowerOutletListView.as_view(), name='device_poweroutlets'), - url(r'^devices/(?P\d+)/interfaces/$', InterfaceListView.as_view(), name='device_interfaces'), - url(r'^devices/(?P\d+)/device-bays/$', DeviceBayListView.as_view(), name='device_devicebays'), - url(r'^devices/(?P\d+)/modules/$', ModuleListView.as_view(), name='device_modules'), + url(r'^devices/(?P\d+)/console-ports/$', ConsolePortViewSet.as_view({'get': 'list'}), name='device_consoleports'), + url(r'^devices/(?P\d+)/console-server-ports/$', ConsoleServerPortViewSet.as_view({'get': 'list'}), name='device_consoleserverports'), + url(r'^devices/(?P\d+)/power-ports/$', PowerPortViewSet.as_view({'get': 'list'}), name='device_powerports'), + url(r'^devices/(?P\d+)/power-outlets/$', PowerOutletViewSet.as_view({'get': 'list'}), name='device_poweroutlets'), + url(r'^devices/(?P\d+)/interfaces/$', InterfaceViewSet.as_view({'get': 'list'}), name='device_interfaces'), + url(r'^devices/(?P\d+)/device-bays/$', DeviceBayViewSet.as_view({'get': 'list'}), name='device_devicebays'), + url(r'^devices/(?P\d+)/modules/$', ModuleViewSet.as_view({'get': 'list'}), name='device_modules'), # Console ports url(r'^console-ports/(?P\d+)/$', ConsolePortView.as_view(), name='consoleport'), diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 70ca17bbc..565593e9c 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -3,10 +3,10 @@ from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.views import APIView +from rest_framework.viewsets import ModelViewSet from django.conf import settings from django.contrib.contenttypes.models import ContentType -from django.db.models import Count from django.http import Http404 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, ) from dcim import filters -from extras.api.views import CustomFieldModelAPIView +from extras.api.views import CustomFieldModelViewSet from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer from utilities.api import ServiceUnavailable from .exceptions import MissingFilterException @@ -26,19 +26,11 @@ from . import serializers # 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 @@ -46,38 +38,22 @@ class SiteDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView): # Rack groups # -class RackGroupListView(generics.ListAPIView): +class RackGroupViewSet(ModelViewSet): """ - List all rack groups + List and retrieve rack groups """ queryset = RackGroup.objects.select_related('site') serializer_class = serializers.RackGroupSerializer 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 # -class RackRoleListView(generics.ListAPIView): +class RackRoleViewSet(ModelViewSet): """ - List all rack roles - """ - queryset = RackRole.objects.all() - serializer_class = serializers.RackRoleSerializer - - -class RackRoleDetailView(generics.RetrieveAPIView): - """ - Retrieve a single rack role + List and retrieve rack roles """ queryset = RackRole.objects.all() serializer_class = serializers.RackRoleSerializer @@ -87,28 +63,18 @@ class RackRoleDetailView(generics.RetrieveAPIView): # 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 + 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): """ @@ -139,17 +105,9 @@ class RackUnitListView(APIView): # Manufacturers # -class ManufacturerListView(generics.ListAPIView): +class ManufacturerViewSet(ModelViewSet): """ - List all hardware manufacturers - """ - queryset = Manufacturer.objects.all() - serializer_class = serializers.ManufacturerSerializer - - -class ManufacturerDetailView(generics.RetrieveAPIView): - """ - Retrieve a single hardware manufacturers + List and retrieve manufacturers """ queryset = Manufacturer.objects.all() serializer_class = serializers.ManufacturerSerializer @@ -159,38 +117,26 @@ class ManufacturerDetailView(generics.RetrieveAPIView): # 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 - -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 # -class DeviceRoleListView(generics.ListAPIView): +class DeviceRoleViewSet(ModelViewSet): """ - List all device roles - """ - queryset = DeviceRole.objects.all() - serializer_class = serializers.DeviceRoleSerializer - - -class DeviceRoleDetailView(generics.RetrieveAPIView): - """ - Retrieve a single device role + List and retrieve device roles """ queryset = DeviceRole.objects.all() serializer_class = serializers.DeviceRoleSerializer @@ -200,17 +146,9 @@ class DeviceRoleDetailView(generics.RetrieveAPIView): # Platforms # -class PlatformListView(generics.ListAPIView): +class PlatformViewSet(ModelViewSet): """ - List all platforms - """ - queryset = Platform.objects.all() - serializer_class = serializers.PlatformSerializer - - -class PlatformDetailView(generics.RetrieveAPIView): - """ - Retrieve a single platform + List and retrieve platforms """ queryset = Platform.objects.all() serializer_class = serializers.PlatformSerializer @@ -220,40 +158,31 @@ class PlatformDetailView(generics.RetrieveAPIView): # 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 filter_class = filters.DeviceFilter 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 # -class ConsolePortListView(generics.ListAPIView): +class ConsolePortViewSet(ModelViewSet): """ - List console ports (by device) + List and retrieve console ports (by device) """ serializer_class = serializers.ConsolePortSerializer def get_queryset(self): - device = get_object_or_404(Device, pk=self.kwargs['pk']) return ConsolePort.objects.filter(device=device).select_related('cs_port') @@ -268,14 +197,13 @@ class ConsolePortView(generics.RetrieveUpdateDestroyAPIView): # 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 def get_queryset(self): - device = get_object_or_404(Device, pk=self.kwargs['pk']) return ConsoleServerPort.objects.filter(device=device).select_related('connected_console') @@ -284,14 +212,13 @@ class ConsoleServerPortListView(generics.ListAPIView): # 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 def get_queryset(self): - device = get_object_or_404(Device, pk=self.kwargs['pk']) return PowerPort.objects.filter(device=device).select_related('power_outlet') @@ -306,14 +233,13 @@ class PowerPortView(generics.RetrieveUpdateDestroyAPIView): # 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 def get_queryset(self): - device = get_object_or_404(Device, pk=self.kwargs['pk']) return PowerOutlet.objects.filter(device=device).select_related('connected_port') @@ -322,9 +248,9 @@ class PowerOutletListView(generics.ListAPIView): # Interfaces # -class InterfaceListView(generics.ListAPIView): +class InterfaceViewSet(ModelViewSet): """ - List interfaces (by device) + List and retrieve interfaces (by device) """ serializer_class = serializers.InterfaceSerializer filter_class = filters.InterfaceFilter @@ -372,14 +298,13 @@ class InterfaceConnectionListView(generics.ListAPIView): # 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 def get_queryset(self): - device = get_object_or_404(Device, pk=self.kwargs['pk']) return DeviceBay.objects.filter(device=device).select_related('installed_device') @@ -388,14 +313,13 @@ class DeviceBayListView(generics.ListAPIView): # Modules # -class ModuleListView(generics.ListAPIView): +class ModuleViewSet(ModelViewSet): """ - List device modules (by device) + List and retrieve modules (by device) """ serializer_class = serializers.ModuleSerializer def get_queryset(self): - device = get_object_or_404(Device, pk=self.kwargs['pk']) return Module.objects.filter(device=device).select_related('device', 'manufacturer') @@ -442,8 +366,19 @@ class RelatedConnectionsView(APIView): super(RelatedConnectionsView, self).__init__() # 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): @@ -469,7 +404,7 @@ class RelatedConnectionsView(APIView): # Initialize response skeleton response = { - 'device': serializers.DeviceSerializer(device, context={'view': self}).data, + 'device': serializers.DeviceSerializer(device, context=self.context).data, 'console-ports': [], 'power-ports': [], 'interfaces': [], diff --git a/netbox/dcim/tests/test_apis.py b/netbox/dcim/tests/test_apis.py index 0739b86ce..7545a80ba 100644 --- a/netbox/dcim/tests/test_apis.py +++ b/netbox/dcim/tests/test_apis.py @@ -82,21 +82,6 @@ class SiteTest(APITestCase): 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)): response = self.client.get(endpoint) content = json.loads(response.content.decode('utf-8')) @@ -239,6 +224,7 @@ class DeviceTypeTest(APITestCase): 'subdevice_role', 'comments', 'custom_fields', + 'instance_count', ] nested_fields = [ diff --git a/netbox/extras/api/renderers.py b/netbox/extras/api/renderers.py index 0fd35c762..2e85ed3a8 100644 --- a/netbox/extras/api/renderers.py +++ b/netbox/extras/api/renderers.py @@ -27,6 +27,8 @@ class BINDZoneRenderer(renderers.BaseRenderer): def render(self, data, media_type=None, renderer_context=None): records = [] + if not isinstance(data, (list, tuple)): + data = (data,) for record in data: if record.get('name') and record.get('primary_ip'): try: diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 4e82b4027..01d348b0a 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -12,22 +12,20 @@ class CustomFieldSerializer(serializers.Serializer): def get_custom_fields(self, obj): # 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 for cfv in obj.custom_field_values.all(): # Attempt to suppress database lookups for CustomFieldChoices by using the cached choice set from the view # context. - if cfv.field.type == CF_TYPE_SELECT and hasattr(self, 'custom_field_choices'): + if cfv.field.type == CF_TYPE_SELECT: cfc = { '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 - # 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: fields[cfv.field.name] = cfv.value diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 19d7fab5f..1ee82ace4 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -1,6 +1,7 @@ import graphviz from rest_framework import generics from rest_framework.views import APIView +from rest_framework.viewsets import ModelViewSet from django.contrib.contenttypes.models import ContentType 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 -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. custom_field_choices = {} - for field in self.custom_fields: + for field in custom_fields: for cfc in field.choices.all(): 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): diff --git a/netbox/ipam/api/urls.py b/netbox/ipam/api/urls.py index 598545ddf..19aef2798 100644 --- a/netbox/ipam/api/urls.py +++ b/netbox/ipam/api/urls.py @@ -6,39 +6,39 @@ from .views import * urlpatterns = [ # VRFs - url(r'^vrfs/$', VRFListView.as_view(), name='vrf_list'), - url(r'^vrfs/(?P\d+)/$', VRFDetailView.as_view(), name='vrf_detail'), + url(r'^vrfs/$', VRFViewSet.as_view({'get': 'list'}), name='vrf_list'), + url(r'^vrfs/(?P\d+)/$', VRFViewSet.as_view({'get': 'retrieve'}), name='vrf_detail'), # Roles - url(r'^roles/$', RoleListView.as_view(), name='role_list'), - url(r'^roles/(?P\d+)/$', RoleDetailView.as_view(), name='role_detail'), + url(r'^roles/$', RoleViewSet.as_view({'get': 'list'}), name='role_list'), + url(r'^roles/(?P\d+)/$', RoleViewSet.as_view({'get': 'retrieve'}), name='role_detail'), # RIRs - url(r'^rirs/$', RIRListView.as_view(), name='rir_list'), - url(r'^rirs/(?P\d+)/$', RIRDetailView.as_view(), name='rir_detail'), + url(r'^rirs/$', RIRViewSet.as_view({'get': 'list'}), name='rir_list'), + url(r'^rirs/(?P\d+)/$', RIRViewSet.as_view({'get': 'retrieve'}), name='rir_detail'), # Aggregates - url(r'^aggregates/$', AggregateListView.as_view(), name='aggregate_list'), - url(r'^aggregates/(?P\d+)/$', AggregateDetailView.as_view(), name='aggregate_detail'), + url(r'^aggregates/$', AggregateViewSet.as_view({'get': 'list'}), name='aggregate_list'), + url(r'^aggregates/(?P\d+)/$', AggregateViewSet.as_view({'get': 'retrieve'}), name='aggregate_detail'), # Prefixes - url(r'^prefixes/$', PrefixListView.as_view(), name='prefix_list'), - url(r'^prefixes/(?P\d+)/$', PrefixDetailView.as_view(), name='prefix_detail'), + url(r'^prefixes/$', PrefixViewSet.as_view({'get': 'list'}), name='prefix_list'), + url(r'^prefixes/(?P\d+)/$', PrefixViewSet.as_view({'get': 'retrieve'}), name='prefix_detail'), # IP addresses - url(r'^ip-addresses/$', IPAddressListView.as_view(), name='ipaddress_list'), - url(r'^ip-addresses/(?P\d+)/$', IPAddressDetailView.as_view(), name='ipaddress_detail'), + url(r'^ip-addresses/$', IPAddressViewSet.as_view({'get': 'list'}), name='ipaddress_list'), + url(r'^ip-addresses/(?P\d+)/$', IPAddressViewSet.as_view({'get': 'retrieve'}), name='ipaddress_detail'), # VLAN groups - url(r'^vlan-groups/$', VLANGroupListView.as_view(), name='vlangroup_list'), - url(r'^vlan-groups/(?P\d+)/$', VLANGroupDetailView.as_view(), name='vlangroup_detail'), + url(r'^vlan-groups/$', VLANGroupViewSet.as_view({'get': 'list'}), name='vlangroup_list'), + url(r'^vlan-groups/(?P\d+)/$', VLANGroupViewSet.as_view({'get': 'retrieve'}), name='vlangroup_detail'), # VLANs - url(r'^vlans/$', VLANListView.as_view(), name='vlan_list'), - url(r'^vlans/(?P\d+)/$', VLANDetailView.as_view(), name='vlan_detail'), + url(r'^vlans/$', VLANViewSet.as_view({'get': 'list'}), name='vlan_list'), + url(r'^vlans/(?P\d+)/$', VLANViewSet.as_view({'get': 'retrieve'}), name='vlan_detail'), # Services - url(r'^services/$', ServiceListView.as_view(), name='service_list'), - url(r'^services/(?P\d+)/$', ServiceDetailView.as_view(), name='service_detail'), + url(r'^services/$', ServiceViewSet.as_view({'get': 'list'}), name='service_list'), + url(r'^services/(?P\d+)/$', ServiceViewSet.as_view({'get': 'retrieve'}), name='service_detail'), ] diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 10b9c46e4..87200fe3a 100644 --- a/netbox/ipam/api/views.py +++ b/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 import filters -from extras.api.views import CustomFieldModelAPIView +from extras.api.views import CustomFieldModelViewSet from . import serializers @@ -11,38 +11,22 @@ from . import serializers # 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 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 # -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() serializer_class = serializers.RoleSerializer @@ -52,17 +36,9 @@ class RoleDetailView(generics.RetrieveAPIView): # 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() serializer_class = serializers.RIRSerializer @@ -72,129 +48,75 @@ class RIRDetailView(generics.RetrieveAPIView): # 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 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 # -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 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 # -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 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 # -class VLANGroupListView(generics.ListAPIView): +class VLANGroupViewSet(ModelViewSet): """ - List all VLAN groups + List and retrieve VLAN groups """ queryset = VLANGroup.objects.select_related('site') serializer_class = serializers.VLANGroupSerializer filter_class = filters.VLANGroupFilter -class VLANGroupDetailView(generics.RetrieveAPIView): - """ - Retrieve a single VLAN group - """ - queryset = VLANGroup.objects.select_related('site') - serializer_class = serializers.VLANGroupSerializer - - # # 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 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 # -class ServiceListView(generics.ListAPIView): +class ServiceViewSet(ModelViewSet): """ - List services (filterable) + List and retrieve services """ queryset = Service.objects.select_related('device').prefetch_related('ipaddresses') serializer_class = serializers.ServiceSerializer 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 diff --git a/netbox/secrets/api/urls.py b/netbox/secrets/api/urls.py index 6acae580d..2ad2abc1e 100644 --- a/netbox/secrets/api/urls.py +++ b/netbox/secrets/api/urls.py @@ -5,14 +5,14 @@ from .views import * urlpatterns = [ + # Secret roles + url(r'^secret-roles/$', SecretRoleViewSet.as_view({'get': 'list'}), name='secretrole_list'), + url(r'^secret-roles/(?P\d+)/$', SecretRoleViewSet.as_view({'get': 'retrieve'}), name='secretrole_detail'), + # Secrets url(r'^secrets/$', SecretListView.as_view(), name='secret_list'), url(r'^secrets/(?P\d+)/$', SecretDetailView.as_view(), name='secret_detail'), - # Secret roles - url(r'^secret-roles/$', SecretRoleListView.as_view(), name='secretrole_list'), - url(r'^secret-roles/(?P\d+)/$', SecretRoleDetailView.as_view(), name='secretrole_detail'), - # Miscellaneous url(r'^generate-keys/$', RSAKeyGeneratorView.as_view(), name='generate_keys'), diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 672165da3..10e95d51b 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -9,6 +9,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView +from rest_framework.viewsets import ModelViewSet from extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer from secrets.filters import SecretFilter @@ -22,23 +23,22 @@ ERR_USERKEY_INACTIVE = "UserKey has not been activated for decryption." ERR_PRIVKEY_INVALID = "Invalid private key." -class SecretRoleListView(generics.ListAPIView): +# +# Secret Roles +# + +class SecretRoleViewSet(ModelViewSet): """ - List all secret roles + List and retrieve secret roles """ queryset = SecretRole.objects.all() serializer_class = serializers.SecretRoleSerializer permission_classes = [IsAuthenticated] -class SecretRoleDetailView(generics.RetrieveAPIView): - """ - Retrieve a single secret role - """ - queryset = SecretRole.objects.all() - serializer_class = serializers.SecretRoleSerializer - permission_classes = [IsAuthenticated] - +# +# Secrets +# class SecretListView(generics.GenericAPIView): """ diff --git a/netbox/tenancy/api/urls.py b/netbox/tenancy/api/urls.py index af1d1d6aa..457327855 100644 --- a/netbox/tenancy/api/urls.py +++ b/netbox/tenancy/api/urls.py @@ -6,11 +6,11 @@ from .views import * urlpatterns = [ # Tenant groups - url(r'^tenant-groups/$', TenantGroupListView.as_view(), name='tenantgroup_list'), - url(r'^tenant-groups/(?P\d+)/$', TenantGroupDetailView.as_view(), name='tenantgroup_detail'), + url(r'^tenant-groups/$', TenantGroupViewSet.as_view({'get': 'list'}), name='tenantgroup_list'), + url(r'^tenant-groups/(?P\d+)/$', TenantGroupViewSet.as_view({'get': 'retrieve'}), name='tenantgroup_detail'), # Tenants - url(r'^tenants/$', TenantListView.as_view(), name='tenant_list'), - url(r'^tenants/(?P\d+)/$', TenantDetailView.as_view(), name='tenant_detail'), + url(r'^tenants/$', TenantViewSet.as_view({'get': 'list'}), name='tenant_list'), + url(r'^tenants/(?P\d+)/$', TenantViewSet.as_view({'get': 'retrieve'}), name='tenant_detail'), ] diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index ce08eb058..6cfeed2ef 100644 --- a/netbox/tenancy/api/views.py +++ b/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.filters import TenantFilter -from extras.api.views import CustomFieldModelAPIView +from extras.api.views import CustomFieldModelViewSet from . import serializers -class TenantGroupListView(generics.ListAPIView): +# +# Tenant Groups +# + +class TenantGroupViewSet(ModelViewSet): """ - List all tenant groups + List and retrieve tenant groups """ queryset = TenantGroup.objects.all() serializer_class = serializers.TenantGroupSerializer -class TenantGroupDetailView(generics.RetrieveAPIView): - """ - Retrieve a single circuit type - """ - queryset = TenantGroup.objects.all() - serializer_class = serializers.TenantGroupSerializer +# +# Tenants +# - -class TenantListView(CustomFieldModelAPIView, generics.ListAPIView): +class TenantViewSet(CustomFieldModelViewSet): """ - List tenants (filterable) + 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 filter_class = TenantFilter - - -class TenantDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView): - """ - Retrieve a single tenant - """ - queryset = Tenant.objects.select_related('group').prefetch_related('custom_field_values__field') - serializer_class = serializers.TenantSerializer