django-rest-swagger: How can I specify the parameter type in the docstring
Question:
I am using django-rest-framwork and django-rest-swagger.
The problem is that I’m fetching data directly from the body of the request:
def put(self, request, format=None):
"""
This text is the description for this API
username -- username
password -- password
"""
username = request.DATA['username']
password = request.DATA['password']
but when I try the request from the swagger-ui I can’t specify the “parameter type” (it’s by default query and can’t find a way to change it from the docstring)
I have managed to get around my problem by changing some line in the function build_query_params_from_docstring from the file “introspectors.py” but I was wondering if there is another way to do it.
Answers:
UPDATE: This answer only works for django-rest-swagger < 2, see the comment from @krd below.
The docs: http://django-rest-swagger.readthedocs.org/en/latest/yaml.html
If you want to put form-data:
def put(self, request, format=None):
"""
This text is the description for this API.
---
parameters:
- name: username
description: Foobar long description goes here
required: true
type: string
paramType: form
- name: password
paramType: form
required: true
type: string
"""
username = request.DATA['username']
password = request.DATA['password']
For a JSON body you can do something like:
def put(...):
"""
...
---
parameters:
- name: body
description: JSON object containing two strings: password and username.
required: true
paramType: body
pytype: RequestSerializer
"""
...
Define a filter-class in your viewset. django-rest does not do this yaml stuff for parameters anymore. The fields you define in your filterclass will appear as fields in your openapi / swagger documentation. This is very neat.
READ full documentation.
http://www.django-rest-framework.org/apiguide/filtering/#djangofilterbackend
from django_filters.rest_framework.filterset import FilterSet
class ProductFilter(FilterSet):
class Meta(object):
models = models.Product
fields = (
'name', 'category', 'id', )
class PurchasedProductsList(generics.ListAPIView):
"""
Return a list of all the products that the authenticated
user has ever purchased, with optional filtering.
"""
model = Product
serializer_class = ProductSerializer
filter_class = ProductFilter
def get_queryset(self):
user = self.request.user
return user.purchase_set.all()
the fields defined in the filterseet will show up in de documentation.
but there will be no description.
The only way I’ve had success in defining parameter types is by creating a view that defines what I want without using the generator.
class SwaggerSchemaView(APIView):
permission_classes = [IsAuthenticatedOrReadOnly,]
renderer_classes = [renderers.OpenAPIRenderer, renderers.SwaggerUIRenderer]
schema = coreapi.Document(
title='Thingy API thing',
'range': coreapi.Link(
url='/range/{start}/{end}',
action='get',
fields=[
coreapi.Field(
name='start',
required=True,
location='path',
description='start time as an epoch',
type='integer'
),
coreapi.Field(
name='end',
required=True,
location='path',
description='end time as an epoch',
type='integer'
)
],
description='show the things between the things'
),
}
)
and then using that class in urls.py
urlpatterns = [
url(r'^$', SwaggerSchemaView.as_view()),
...
]
Similar to John VanBuskirk’s answer, here is what I have:
The actual manual created doc:
drf_api/business/schema.py
# encoding: utf-8
from __future__ import unicode_literals
from __future__ import absolute_import
import coreapi
schema = coreapi.Document(
title='Business Search API',
url='/api/v3/business/',
content={
'search': coreapi.Link(
url='/',
action='get',
fields=[
coreapi.Field(
name='what',
required=True,
location='query',
description='Search term'
),
coreapi.Field(
name='where',
required=True,
location='query',
description='Search location'
),
],
description='Search business listings'
)
}
)
Then copied the get_swagger_view function and customized it:
drf_api/swagger.py
# encoding: utf-8
from __future__ import unicode_literals
from __future__ import absolute_import
from rest_framework import exceptions
from rest_framework.permissions import AllowAny
from rest_framework.renderers import CoreJSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_swagger import renderers
from django.utils.module_loading import import_string
def get_swagger_view(schema_location):
"""
Returns schema view which renders Swagger/OpenAPI.
"""
class SwaggerSchemaView(APIView):
_ignore_model_permissions = True
exclude_from_schema = True
permission_classes = [AllowAny]
renderer_classes = [
CoreJSONRenderer,
renderers.OpenAPIRenderer,
renderers.SwaggerUIRenderer
]
def get(self, request):
schema = None
try:
schema = import_string(schema_location)
except:
pass
if not schema:
raise exceptions.ValidationError(
'The schema generator did not return a schema Document'
)
return Response(schema)
return SwaggerSchemaView.as_view()
Then hook it up to the urls.py
from ..swagger import get_swagger_view
from . import views
schema_view = get_swagger_view(schema_location='drf_api.business.schema.schema')
urlpatterns = [
url(r'^swagger/$', schema_view),
For latest django-rest-framework > 3.7
and django-rest-swagger > 2
, go through the below link to find working solution
https://github.com/marcgibbons/django-rest-swagger/issues/549#issuecomment-371860030
For Django Rest Framework >= 2.0
I am using a serializer and apply to a the view function with a decorator:
from types import MethodType
from typing import Optional, List, Callable, Any
from rest_framework.decorators import api_view as drf_api_view
from rest_framework.serializers import BaseSerializer
Function = Callable[..., Any]
def api_view(
http_method_names: Optional[List[str]] = None,
use_serializer: Optional[BaseSerializer] = None
) -> Function:
if use_serializer is None:
return drf_api_view(http_method_names)
def api_view_deco_wrap(view: Function) -> Function:
nonlocal http_method_names, use_serializer
decorated_view = drf_api_view(http_method_names)(view)
if use_serializer:
decorated_view.cls.get_serializer =
MethodType(lambda s: use_serializer(), decorated_view.cls)
return decorated_view
return api_view_deco_wrap
Then in the function I use:
@util.api_view(["POST"], use_serializer=serializers.MySerializer)
def replace(request, pk):
pass
And works!!!
[enter image description here][1]For me it worked with the ManualSchema
. You have to define the description and fields. See:
from rest_framework import views
import coreapi
import coreschema
from rest_framework.schemas import ManualSchema
class RegisterView(views.APIView):
schema = ManualSchema(
description="User register endpoint.",
fields=[
coreapi.Field(
'username',
required=True,
location='path',
description='A unique username',
schema=coreschema.String(),
),
coreapi.Field(
'password',
required=True,
location='path',
schema=coreschema.String(),
),
]
)
)
...
I am using django-rest-framwork and django-rest-swagger.
The problem is that I’m fetching data directly from the body of the request:
def put(self, request, format=None):
"""
This text is the description for this API
username -- username
password -- password
"""
username = request.DATA['username']
password = request.DATA['password']
but when I try the request from the swagger-ui I can’t specify the “parameter type” (it’s by default query and can’t find a way to change it from the docstring)
I have managed to get around my problem by changing some line in the function build_query_params_from_docstring from the file “introspectors.py” but I was wondering if there is another way to do it.
UPDATE: This answer only works for django-rest-swagger < 2, see the comment from @krd below.
The docs: http://django-rest-swagger.readthedocs.org/en/latest/yaml.html
If you want to put form-data:
def put(self, request, format=None):
"""
This text is the description for this API.
---
parameters:
- name: username
description: Foobar long description goes here
required: true
type: string
paramType: form
- name: password
paramType: form
required: true
type: string
"""
username = request.DATA['username']
password = request.DATA['password']
For a JSON body you can do something like:
def put(...):
"""
...
---
parameters:
- name: body
description: JSON object containing two strings: password and username.
required: true
paramType: body
pytype: RequestSerializer
"""
...
Define a filter-class in your viewset. django-rest does not do this yaml stuff for parameters anymore. The fields you define in your filterclass will appear as fields in your openapi / swagger documentation. This is very neat.
READ full documentation.
http://www.django-rest-framework.org/apiguide/filtering/#djangofilterbackend
from django_filters.rest_framework.filterset import FilterSet
class ProductFilter(FilterSet):
class Meta(object):
models = models.Product
fields = (
'name', 'category', 'id', )
class PurchasedProductsList(generics.ListAPIView):
"""
Return a list of all the products that the authenticated
user has ever purchased, with optional filtering.
"""
model = Product
serializer_class = ProductSerializer
filter_class = ProductFilter
def get_queryset(self):
user = self.request.user
return user.purchase_set.all()
the fields defined in the filterseet will show up in de documentation.
but there will be no description.
The only way I’ve had success in defining parameter types is by creating a view that defines what I want without using the generator.
class SwaggerSchemaView(APIView):
permission_classes = [IsAuthenticatedOrReadOnly,]
renderer_classes = [renderers.OpenAPIRenderer, renderers.SwaggerUIRenderer]
schema = coreapi.Document(
title='Thingy API thing',
'range': coreapi.Link(
url='/range/{start}/{end}',
action='get',
fields=[
coreapi.Field(
name='start',
required=True,
location='path',
description='start time as an epoch',
type='integer'
),
coreapi.Field(
name='end',
required=True,
location='path',
description='end time as an epoch',
type='integer'
)
],
description='show the things between the things'
),
}
)
and then using that class in urls.py
urlpatterns = [
url(r'^$', SwaggerSchemaView.as_view()),
...
]
Similar to John VanBuskirk’s answer, here is what I have:
The actual manual created doc:
drf_api/business/schema.py
# encoding: utf-8
from __future__ import unicode_literals
from __future__ import absolute_import
import coreapi
schema = coreapi.Document(
title='Business Search API',
url='/api/v3/business/',
content={
'search': coreapi.Link(
url='/',
action='get',
fields=[
coreapi.Field(
name='what',
required=True,
location='query',
description='Search term'
),
coreapi.Field(
name='where',
required=True,
location='query',
description='Search location'
),
],
description='Search business listings'
)
}
)
Then copied the get_swagger_view function and customized it:
drf_api/swagger.py
# encoding: utf-8
from __future__ import unicode_literals
from __future__ import absolute_import
from rest_framework import exceptions
from rest_framework.permissions import AllowAny
from rest_framework.renderers import CoreJSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_swagger import renderers
from django.utils.module_loading import import_string
def get_swagger_view(schema_location):
"""
Returns schema view which renders Swagger/OpenAPI.
"""
class SwaggerSchemaView(APIView):
_ignore_model_permissions = True
exclude_from_schema = True
permission_classes = [AllowAny]
renderer_classes = [
CoreJSONRenderer,
renderers.OpenAPIRenderer,
renderers.SwaggerUIRenderer
]
def get(self, request):
schema = None
try:
schema = import_string(schema_location)
except:
pass
if not schema:
raise exceptions.ValidationError(
'The schema generator did not return a schema Document'
)
return Response(schema)
return SwaggerSchemaView.as_view()
Then hook it up to the urls.py
from ..swagger import get_swagger_view
from . import views
schema_view = get_swagger_view(schema_location='drf_api.business.schema.schema')
urlpatterns = [
url(r'^swagger/$', schema_view),
For latest django-rest-framework > 3.7
and django-rest-swagger > 2
, go through the below link to find working solution
https://github.com/marcgibbons/django-rest-swagger/issues/549#issuecomment-371860030
For Django Rest Framework >= 2.0
I am using a serializer and apply to a the view function with a decorator:
from types import MethodType
from typing import Optional, List, Callable, Any
from rest_framework.decorators import api_view as drf_api_view
from rest_framework.serializers import BaseSerializer
Function = Callable[..., Any]
def api_view(
http_method_names: Optional[List[str]] = None,
use_serializer: Optional[BaseSerializer] = None
) -> Function:
if use_serializer is None:
return drf_api_view(http_method_names)
def api_view_deco_wrap(view: Function) -> Function:
nonlocal http_method_names, use_serializer
decorated_view = drf_api_view(http_method_names)(view)
if use_serializer:
decorated_view.cls.get_serializer =
MethodType(lambda s: use_serializer(), decorated_view.cls)
return decorated_view
return api_view_deco_wrap
Then in the function I use:
@util.api_view(["POST"], use_serializer=serializers.MySerializer)
def replace(request, pk):
pass
And works!!!
[enter image description here][1]For me it worked with the ManualSchema
. You have to define the description and fields. See:
from rest_framework import views
import coreapi
import coreschema
from rest_framework.schemas import ManualSchema
class RegisterView(views.APIView):
schema = ManualSchema(
description="User register endpoint.",
fields=[
coreapi.Field(
'username',
required=True,
location='path',
description='A unique username',
schema=coreschema.String(),
),
coreapi.Field(
'password',
required=True,
location='path',
schema=coreschema.String(),
),
]
)
)
...