Use drf_yasg to generate swagger

drf_yasg provides more complete swagger output, allowing for generation
of usable clients.

Some custom work was needed to accommodate Netbox's custom field
serializers, and to provide x-nullable attributes where appropriate.
This commit is contained in:
Dave Cameron 2018-02-22 17:46:50 -05:00
parent 38a208242b
commit b83de7eb11
5 changed files with 102 additions and 5 deletions

View File

@ -133,7 +133,6 @@ INSTALLED_APPS = (
'django_tables2', 'django_tables2',
'mptt', 'mptt',
'rest_framework', 'rest_framework',
'rest_framework_swagger',
'timezone_field', 'timezone_field',
'circuits', 'circuits',
'dcim', 'dcim',
@ -144,6 +143,7 @@ INSTALLED_APPS = (
'users', 'users',
'utilities', 'utilities',
'virtualization', 'virtualization',
'drf_yasg',
) )
# Middleware # Middleware
@ -246,6 +246,28 @@ REST_FRAMEWORK = {
'VIEW_NAME_FUNCTION': 'netbox.api.get_view_name', '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_PAGINATOR_INSPECTORS': [
'utilities.custom_inspectors.NullablePaginatorInspector',
'drf_yasg.inspectors.DjangoRestResponsePagination',
'drf_yasg.inspectors.CoreAPICompatInspector',
]
}
# Django debug toolbar # Django debug toolbar
INTERNAL_IPS = ( INTERNAL_IPS = (
'127.0.0.1', '127.0.0.1',

View File

@ -4,12 +4,24 @@ from django.conf import settings
from django.conf.urls import include, url from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
from django.views.static import serve 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 netbox.views import APIRootView, HomeView, SearchView
from users.views import LoginView, LogoutView 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 = [ _patterns = [
@ -40,7 +52,9 @@ _patterns = [
url(r'^api/secrets/', include('secrets.api.urls')), url(r'^api/secrets/', include('secrets.api.urls')),
url(r'^api/tenancy/', include('tenancy.api.urls')), url(r'^api/tenancy/', include('tenancy.api.urls')),
url(r'^api/virtualization/', include('virtualization.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 # Serving static media in Django to pipe it through LoginRequiredMiddleware
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),

View File

@ -0,0 +1,60 @@
from drf_yasg import openapi
from drf_yasg.inspectors import FieldInspector, NotHandled, PaginatorInspector
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)
if set([None] + list(field._choices.keys())) == {None, True, False}:
# Special case face and connection_status because the only keys for choices are True and False,
# but the underlying field is still a NullBooleanField
value_schema = openapi.Schema(type=openapi.TYPE_BOOLEAN)
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 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

View File

@ -1,2 +1,3 @@
django-rest-swagger
psycopg2 psycopg2
pycrypto pycrypto

View File

@ -3,10 +3,10 @@ django-cors-headers>=2.1.0
django-debug-toolbar>=1.9.0 django-debug-toolbar>=1.9.0
django-filter>=1.1.0 django-filter>=1.1.0
django-mptt>=0.9.0 django-mptt>=0.9.0
django-rest-swagger>=2.1.0
django-tables2>=1.19.0 django-tables2>=1.19.0
django-timezone-field>=2.0 django-timezone-field>=2.0
djangorestframework>=3.7.7 djangorestframework>=3.7.7
drf-yasg>=1.4.4
graphviz>=0.8.2 graphviz>=0.8.2
Markdown>=2.6.11 Markdown>=2.6.11
natsort>=5.2.0 natsort>=5.2.0