djangorestdjango-rest-frameworkswaggerredoc

Django Rest Framework custom POST URL endpoints with defined parameter (request.POST) with Swagger or other doc


previously in Django 1.11, I'ved defined Django REST API in this way:

in url.py

url(r'^api/test_token$', api.test_token, name='test_token'),

in api.py

@api_view(['POST'])
def test_token(request):
    # ----- YAML below for Swagger -----
    """
    description: test_token
    parameters:
      - name: token
        type: string
        required: true
        location: form       
    """
    token = request.POST['token']

    return Response("test_token success", status=status.HTTP_200_OK)

Now that I am migrating to Django 3.1.5, I'd like to know how the above can be achieved in the same way with Django Rest Framework (DRF). In the above particular case, it is POST API "test_token" that takes in one parameter. And generate API documentation like swagger/redoc (that can be used for testing the API)

enter image description here

Some Notes:

How can I implement this on Django 3.x ? (as in the title: Django Rest Framework custom POST URL endpoints with defined parameter with Swagger or other doc)

UPDATE:

I think there is some form of solution from here: https://github.com/tfranzel/drf-spectacular/issues/279

As I have a whole lot of APIs that uses @api_view, the changes docstring to the decorator @extend_schema may be simplest migration path. I hope someone can provide guidance on url.py to the conversion using @extend_schema. This is to get the url end points and swagger implemented. Thanks.

This is closest I'ved got with drf-spectacular however

@extend_schema( 
 parameters=[OpenApiParameter( 
     name='token', 
     type={'type': 'string'}, 
     location=OpenApiParameter.QUERY, 
     required=False, 
     style='form', 
     explode=False, 
 )], 
 responses=OpenApiTypes.OBJECT, 
) 
@api_view(['POST'])
def test_api(request):
    # ----- YAML below for Swagger -----
    """
    description: test_api
    parameters:
      - name: token
        type: string
        required: true
        location: form       
    """
    token = request.POST['token']
    
    return Response("success test_api:" + token, status=status.HTTP_200_OK)

its giving this (which is incorrect), notice the token query

curl -X POST "http://localhost:8000/api/test_token/?token=hello" -H  "accept: application/json" -H  "X-CSRFToken: JyPOSAQx04LK0aM8IUgUmkruALSNwRbeYDzUHBhCjtXafC3tnHRFsxvyg5SgMLhI" -d ""

instead of a POST input param (how to get this ?)

curl -X POST --header 'Content-Type: application/x-www-form-urlencoded' --header 'Accept: application/json' --header 'X-CSRFToken: aExHCSwrRyStDiOhkk8Mztfth2sqonhTkUFaJbnXSFKXCynqzDQEzcRCAufYv6MC' -d 'token=hello' 'http://localhost:8000/api/test_token/

THE SOLUTION:

url.py

from drf_yasg.utils import swagger_auto_schema 

from rest_framework.response import Response
from rest_framework import status

from rest_framework.decorators import parser_classes
from rest_framework.parsers import FormParser

token = openapi.Parameter('token', openapi.IN_FORM, type=openapi.TYPE_STRING, required=True)
something = openapi.Parameter('something', openapi.IN_FORM, type=openapi.TYPE_INTEGER, required=False)
@swagger_auto_schema(
    method="post",
    manual_parameters=[token, something],
    operation_id="token_api"
)
@api_view(['POST'])
# this is optional and insures that the view gets formdata
@parser_classes([FormParser])
def token_api(request):
    token = request.POST['token']
    something = request.POST['something']
    
    return Response("success test_api:" + token + something, status=status.HTTP_200_OK)


schema_view = get_schema_view(
   openapi.Info(
      title="Snippets API",
      default_version='v1',
      description="Test description",
      terms_of_service="https://www.google.com/policies/terms/",
      contact=openapi.Contact(email="contact@snippets.local"),
      license=openapi.License(name="BSD License"),
   ),
   public=True,
   permission_classes=[permissions.AllowAny],
)


urlpatterns = [
    
    path('token_api', token_api, name='token_api'),

    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    
] + required_urlpatterns

Solution

  • As you said django-rest-swagger is deprecated.

    That's why it is recommended to use drf-yasg.

    from drf_yasg import openapi
    from drf_yasg.utils import swagger_auto_schema    
    
    class ArticleViewSet(viewsets.ModelViewSet):
        @swagger_auto_schema(request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT, 
            properties={
                'test_token': openapi.Schema(type=openapi.TYPE_STRING, description='string'),
            }
        ))
        def create(self, request, *args, **kwargs):
            ...
    

    Or if you want to use a DRF action

        @swagger_auto_schema(method="post", request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT, 
            properties={
                'test_token': openapi.Schema(type=openapi.TYPE_STRING, description='string'),
            }
        ))
        @action(method=["post"], detail=False)
        def my_post_action(self, request, *args, **kwargs):
            ...
    

    Or with api view:

    # here we define that this view accepts a json (or object parameter) that has test_token parameter inside of it
    @swagger_auto_schema(method='post', 
        request_body=openapi.Schema(
        type=openapi.TYPE_OBJECT, # object because the data is in json format
        properties={
            'test_token': openapi.Schema(type=openapi.TYPE_STRING, description='this test_token is used for...'),
        }
    ), operation_id="token_view")
    # your view
    @api_view(['POST'])
    def token_view(request):
        pass
    

    And your url.py will look like so

    # define some basic info about your api for swagger
    schema_view = get_schema_view(
       openapi.Info(
          title="Snippets API",
          default_version='v1',
          description="Test description",
          terms_of_service="https://www.google.com/policies/terms/",
          contact=openapi.Contact(email="contact@snippets.local"),
          license=openapi.License(name="BSD License"),
       ),
       public=True,
       permission_classes=[permissions.AllowAny],
    )
    
    urlpatterns = [
        # define your api view url
        path('token_view/', token_view),
        # define the url of the swagger ui
        url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    ]