mirror of
				https://github.com/netbox-community/netbox.git
				synced 2024-05-10 07:54:54 +00:00 
			
		
		
		
	Merge pull request #1930 from davcamer/drf-yasg
Use drf_yasg to generate swagger
This commit is contained in:
		@@ -6,6 +6,9 @@ from django.conf import settings
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.http import HttpResponseBadRequest, HttpResponseForbidden
 | 
			
		||||
from django.shortcuts import get_object_or_404
 | 
			
		||||
from drf_yasg import openapi
 | 
			
		||||
from drf_yasg.openapi import Parameter
 | 
			
		||||
from drf_yasg.utils import swagger_auto_schema
 | 
			
		||||
from rest_framework.decorators import detail_route
 | 
			
		||||
from rest_framework.mixins import ListModelMixin
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
@@ -418,14 +421,20 @@ class ConnectedDeviceViewSet(ViewSet):
 | 
			
		||||
    * `peer-interface`: The name of the peer interface
 | 
			
		||||
    """
 | 
			
		||||
    permission_classes = [IsAuthenticatedOrLoginNotRequired]
 | 
			
		||||
    _device_param = Parameter('peer-device', 'query',
 | 
			
		||||
                              description='The name of the peer device', required=True, type=openapi.TYPE_STRING)
 | 
			
		||||
    _interface_param = Parameter('peer-interface', 'query',
 | 
			
		||||
                                 description='The name of the peer interface', required=True, type=openapi.TYPE_STRING)
 | 
			
		||||
 | 
			
		||||
    def get_view_name(self):
 | 
			
		||||
        return "Connected Device Locator"
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(
 | 
			
		||||
        manual_parameters=[_device_param, _interface_param], responses={'200': serializers.DeviceSerializer})
 | 
			
		||||
    def list(self, request):
 | 
			
		||||
 | 
			
		||||
        peer_device_name = request.query_params.get('peer-device')
 | 
			
		||||
        peer_interface_name = request.query_params.get('peer-interface')
 | 
			
		||||
        peer_device_name = request.query_params.get(self._device_param.name)
 | 
			
		||||
        peer_interface_name = request.query_params.get(self._interface_param.name)
 | 
			
		||||
        if not peer_device_name or not peer_interface_name:
 | 
			
		||||
            raise MissingFilterException(detail='Request must include "peer-device" and "peer-interface" filters.')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -133,7 +133,6 @@ INSTALLED_APPS = (
 | 
			
		||||
    'django_tables2',
 | 
			
		||||
    'mptt',
 | 
			
		||||
    'rest_framework',
 | 
			
		||||
    'rest_framework_swagger',
 | 
			
		||||
    'timezone_field',
 | 
			
		||||
    'circuits',
 | 
			
		||||
    'dcim',
 | 
			
		||||
@@ -144,6 +143,7 @@ INSTALLED_APPS = (
 | 
			
		||||
    'users',
 | 
			
		||||
    'utilities',
 | 
			
		||||
    'virtualization',
 | 
			
		||||
    'drf_yasg',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Middleware
 | 
			
		||||
@@ -246,6 +246,32 @@ REST_FRAMEWORK = {
 | 
			
		||||
    'VIEW_NAME_FUNCTION': 'netbox.api.get_view_name',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# drf_yasg settings for Swagger
 | 
			
		||||
SWAGGER_SETTINGS = {
 | 
			
		||||
    'DEFAULT_FIELD_INSPECTORS': [
 | 
			
		||||
        'utilities.custom_inspectors.NullableBooleanFieldInspector',
 | 
			
		||||
        'utilities.custom_inspectors.CustomChoiceFieldInspector',
 | 
			
		||||
        'drf_yasg.inspectors.CamelCaseJSONFilter',
 | 
			
		||||
        'drf_yasg.inspectors.ReferencingSerializerInspector',
 | 
			
		||||
        'drf_yasg.inspectors.RelatedFieldInspector',
 | 
			
		||||
        'drf_yasg.inspectors.ChoiceFieldInspector',
 | 
			
		||||
        'drf_yasg.inspectors.FileFieldInspector',
 | 
			
		||||
        'drf_yasg.inspectors.DictFieldInspector',
 | 
			
		||||
        'drf_yasg.inspectors.SimpleFieldInspector',
 | 
			
		||||
        'drf_yasg.inspectors.StringDefaultFieldInspector',
 | 
			
		||||
    ],
 | 
			
		||||
    'DEFAULT_FILTER_INSPECTORS': [
 | 
			
		||||
        'utilities.custom_inspectors.IdInFilterInspector',
 | 
			
		||||
        'drf_yasg.inspectors.CoreAPICompatInspector',
 | 
			
		||||
    ],
 | 
			
		||||
    'DEFAULT_PAGINATOR_INSPECTORS': [
 | 
			
		||||
        'utilities.custom_inspectors.NullablePaginatorInspector',
 | 
			
		||||
        'drf_yasg.inspectors.DjangoRestResponsePagination',
 | 
			
		||||
        'drf_yasg.inspectors.CoreAPICompatInspector',
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Django debug toolbar
 | 
			
		||||
INTERNAL_IPS = (
 | 
			
		||||
    '127.0.0.1',
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,24 @@ from django.conf import settings
 | 
			
		||||
from django.conf.urls import include, url
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
from django.views.static import serve
 | 
			
		||||
from rest_framework_swagger.views import get_swagger_view
 | 
			
		||||
from drf_yasg.views import get_schema_view
 | 
			
		||||
from drf_yasg import openapi
 | 
			
		||||
 | 
			
		||||
from netbox.views import APIRootView, HomeView, SearchView
 | 
			
		||||
from users.views import LoginView, LogoutView
 | 
			
		||||
 | 
			
		||||
swagger_view = get_swagger_view(title='NetBox API')
 | 
			
		||||
schema_view = get_schema_view(
 | 
			
		||||
    openapi.Info(
 | 
			
		||||
        title="NetBox API",
 | 
			
		||||
        default_version='v2',
 | 
			
		||||
        description="API to access NetBox",
 | 
			
		||||
        terms_of_service="https://github.com/digitalocean/netbox",
 | 
			
		||||
        contact=openapi.Contact(email="netbox@digitalocean.com"),
 | 
			
		||||
        license=openapi.License(name="Apache v2 License"),
 | 
			
		||||
    ),
 | 
			
		||||
    validators=['flex', 'ssv'],
 | 
			
		||||
    public=True,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_patterns = [
 | 
			
		||||
 | 
			
		||||
@@ -40,7 +52,9 @@ _patterns = [
 | 
			
		||||
    url(r'^api/secrets/', include('secrets.api.urls')),
 | 
			
		||||
    url(r'^api/tenancy/', include('tenancy.api.urls')),
 | 
			
		||||
    url(r'^api/virtualization/', include('virtualization.api.urls')),
 | 
			
		||||
    url(r'^api/docs/', swagger_view, name='api_docs'),
 | 
			
		||||
    url(r'^api/docs/$', schema_view.with_ui('swagger', cache_timeout=None), name='api_docs'),
 | 
			
		||||
    url(r'^api/redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='api_redocs'),
 | 
			
		||||
    url(r'^api/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema_swagger'),
 | 
			
		||||
 | 
			
		||||
    # Serving static media in Django to pipe it through LoginRequiredMiddleware
 | 
			
		||||
    url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										76
									
								
								netbox/utilities/custom_inspectors.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								netbox/utilities/custom_inspectors.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
from drf_yasg import openapi
 | 
			
		||||
from drf_yasg.inspectors import FieldInspector, NotHandled, PaginatorInspector, FilterInspector
 | 
			
		||||
from rest_framework.fields import ChoiceField
 | 
			
		||||
 | 
			
		||||
from extras.api.customfields import CustomFieldsSerializer
 | 
			
		||||
from utilities.api import ChoiceFieldSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomChoiceFieldInspector(FieldInspector):
 | 
			
		||||
    def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
 | 
			
		||||
        # this returns a callable which extracts title, description and other stuff
 | 
			
		||||
        # https://drf-yasg.readthedocs.io/en/stable/_modules/drf_yasg/inspectors/base.html#FieldInspector._get_partial_types
 | 
			
		||||
        SwaggerType, _ = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
 | 
			
		||||
 | 
			
		||||
        if isinstance(field, ChoiceFieldSerializer):
 | 
			
		||||
            value_schema = openapi.Schema(type=openapi.TYPE_INTEGER)
 | 
			
		||||
 | 
			
		||||
            choices = list(field._choices.keys())
 | 
			
		||||
            if set([None] + choices) == {None, True, False}:
 | 
			
		||||
                # DeviceType.subdevice_role, Device.face and InterfaceConnection.connection_status all need to be
 | 
			
		||||
                # differentiated since they each have subtly different values in their choice keys.
 | 
			
		||||
                # - subdevice_role and connection_status are booleans, although subdevice_role includes None
 | 
			
		||||
                # - face is an integer set {0, 1} which is easily confused with {False, True}
 | 
			
		||||
                schema_type = openapi.TYPE_INTEGER
 | 
			
		||||
                if all(type(x) == bool for x in [c for c in choices if c is not None]):
 | 
			
		||||
                    schema_type = openapi.TYPE_BOOLEAN
 | 
			
		||||
                value_schema = openapi.Schema(type=schema_type)
 | 
			
		||||
                value_schema['x-nullable'] = True
 | 
			
		||||
 | 
			
		||||
            schema = SwaggerType(type=openapi.TYPE_OBJECT, required=["label", "value"], properties={
 | 
			
		||||
                "label": openapi.Schema(type=openapi.TYPE_STRING),
 | 
			
		||||
                "value": value_schema
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            return schema
 | 
			
		||||
 | 
			
		||||
        elif isinstance(field, CustomFieldsSerializer):
 | 
			
		||||
            schema = SwaggerType(type=openapi.TYPE_OBJECT)
 | 
			
		||||
            return schema
 | 
			
		||||
 | 
			
		||||
        return NotHandled
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NullableBooleanFieldInspector(FieldInspector):
 | 
			
		||||
    def process_result(self, result, method_name, obj, **kwargs):
 | 
			
		||||
 | 
			
		||||
        if isinstance(result, openapi.Schema) and isinstance(obj, ChoiceField) and result.type == 'boolean':
 | 
			
		||||
            keys = obj.choices.keys()
 | 
			
		||||
            if set(keys) == {None, True, False}:
 | 
			
		||||
                result['x-nullable'] = True
 | 
			
		||||
                result.type = 'boolean'
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IdInFilterInspector(FilterInspector):
 | 
			
		||||
    def process_result(self, result, method_name, obj, **kwargs):
 | 
			
		||||
        if isinstance(result, list):
 | 
			
		||||
            params = [p for p in result if isinstance(p, openapi.Parameter) and p.name == 'id__in']
 | 
			
		||||
            for p in params:
 | 
			
		||||
                p.type = 'string'
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NullablePaginatorInspector(PaginatorInspector):
 | 
			
		||||
    def process_result(self, result, method_name, obj, **kwargs):
 | 
			
		||||
        if method_name == 'get_paginated_response' and isinstance(result, openapi.Schema):
 | 
			
		||||
            next = result.properties['next']
 | 
			
		||||
            if isinstance(next, openapi.Schema):
 | 
			
		||||
                next['x-nullable'] = True
 | 
			
		||||
            previous = result.properties['previous']
 | 
			
		||||
            if isinstance(previous, openapi.Schema):
 | 
			
		||||
                previous['x-nullable'] = True
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
		Reference in New Issue
	
	Block a user