OpenAPI 3 support in Django Rest Framework is still a work in progress. Things are moving quickly so there's not a lot of up to date info about this topic. It's not clear which features of OpenAPI 3 spec are supported in DRF and researching this info on your own means wasting a lot of time.

In this article I'll go over the sections of the OpenAPI spec and talk about its support in DRF. If you don't know how to generate an OpenAPI 3 spec in DRF you can read about it here.

Since things change quickly I'll try to keep this post up to date (DRF version tested in this post: 3.10.2).

Overview

Here's a bird's-eye view of an OpenAPI spec:

info (general API info like title, license, etc)

servers (basically a base url for your API service)

paths (this is your actual application) path (url to a DRF view) operation ( get , post etc) parameters (url/query, headers and cookies parameters) request media types (e.g application/json ) body schema response status code (e.g. 200 or 500 ) media types body schema

components (chunks of schema that can be reused in this spec)

The ideal scenario is where DRF generates an OpenAPI schema by inferring as much info from the application code as possible. It is not difficult to populate the info and servers parts, so we are not really interested in them. Ideally, DRF should generate the paths section of the spec, as this is where the actual application is described. components section allows to keep schema readable and short by defining and reusing certain spec parts, however DRF doesn't use it at all. If DRF doesn't generate something automatically, it is still possible to customize the process by overriding a SchemaGenerator or AutoSchema (view inspector).

Paths and Operations

In DRF a path is an endpoint URL and an operation is an actual view (for CBVs it is a method that handles an HTTP verb, e.g. GET ). Paths and operations spec is generated automatically in DRF. It can infer supported HTTP verbs for both FBVs and CBVs.

# views.py from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.views import APIView class CBView(APIView): def get(self, request, *args, **kwargs): return Response() def post(self, request, *args, **kwargs): return Response() @api_view(['GET', 'POST']) def fb_view(request): return Response() # urls.py urlpatterns = [ path('api/cb_view/', views.CBView.as_view()), path('api/fb_view/', views.fb_view), ]

The above code would result in the following OpenAPI spec (I only included a paths section since this is the one we're mostly interested in):

paths: /api/cb_view/: get: operationId: ListCBs parameters: [] responses: '200': content: application/json: schema: {} post: operationId: CreateCB parameters: [] responses: '200': content: application/json: schema: {} /api/fb_view/: get: operationId: Listfb_views parameters: [] responses: '200': content: application/json: schema: {} post: operationId: Createfb_view parameters: [] responses: '200': content: application/json: schema: {}

Besides weird operationId values, all urls and views are handled correctly. I also tested ViewSets and Generic Views and they all produced correct paths and operations spec. Finally, DRF metadata spec generation is not supported out of the box, however this is not an important feature.

Parameters

There are 4 kinds of parameters in OA3 spec: path, query, header and cookie. In DRF parameters are automatically inferred from urls, builtin filters and pagination backends.

URL parameters

# views.py from rest_framework.response import Response from rest_framework.views import APIView class Record(APIView): def get(self, request, *args, **kwargs): return Response() # urls.py urlpatterns = [ path('api/records/<int:pk>/<uuid:uuid>', views.Record.as_view()), ]

paths: /api/records/{id}/{uuid}: get: operationId: RetrieveRecord parameters: - name: uuid in: path required: true description: '' schema: type: string - name: id in: path required: true description: '' schema: type: string # should have been an integer responses: '200': content: application/json: schema: {}

As you can see, DRF infers multiple parameters in URLs, however it doesn't support automatic path converters to schema type mapping, so all path parameters are treated as a string . So all the essential functionality is there, which is great! Unfortunately, you can't customize various parameter attributes (e.g. required , description , etc) without overriding the whole view inspector class.

Builtin filters, pagination and custom parameters

Builtin DRF filters and pagination backends come with their own parameters. Fortunately, DRF includes them in a spec automatically. If your endpoint accepts custom parameters and you'd like to include them in a spec, you should define them via custom filters by overriding get_schema_operation_parameters :

from rest_framework.views import APIView from rest_framework.filters import BaseFilterBackend, OrderingFilter class CustomFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, view): return queryset def get_schema_operation_parameters(self, view): return [ { 'name': 'q', 'required': False, 'in': 'query', 'schema': { 'type': 'string', }, }, { 'name': 'X-Param', 'required': False, 'in': 'header', 'schema': { 'type': 'string', }, }, ] class BookList(APIView): pagination_class = PageNumberPagination filter_backends = [OrderingFilter, CustomFilter] def get(self, request, *args, **kwargs): pass

paths: /api/books/: get: operationId: ListBooks parameters: # builtin parameters - name: page required: false in: query description: A page number within the paginated result set. schema: type: integer - name: ordering required: false in: query description: Which field to use when ordering the results. schema: type: string # custom parameters - name: q required: false in: query schema: type: string - name: X-Param required: false in: header schema: type: string responses: '200': content: application/json: schema: {}

Specifying parameters via custom filters each time might be an overkill but it is probably a good code pattern to follow anyway. Here's what DRF doesn't support: inferring parameters from builtin authentication classes, versioning, format suffixes are somewhat broken (don't work with ViewSets, produce duplicate operationId , etc).

Request and Response body

OA3 allows to specify body media types supported by an endpoint. DRF has a concept of parsers and renderers, where the former handles the request body and the latter – the response body. By default, each DRF view is configured to support 3 request parsers: JSONParser , FormParser and MultiPartParser . Those parsers handle the following media types: application/json , application/x-www-form-urlencoded and multipart/form-data . This means that you can submit the same contents using different request body formats and DRF would handle them (e.g. {"title": "Example"} is the same as title=Example ).

When it comes to OA3 spec generation, DRF doesn't infer anything from the view's parsers or renderers to generate appropriate media types, it produces application/json in every scenario. This is a problem and I've already opened a PR which addresses this.

There is no support for multiple response codes: at the moment every response spec is generated with a 200 status code and there is no way to specify other responses without overriding a view inspector. To customize any request or response attributes (e.g. description , required , response headers, etc), you'd need to override a view inspector too.

Schema

An actual body schema is produced by inferring the fields and its attributes from DRF serializer. As I said in the beginning, DRF doesn't utilize the components section, that's why it doesn't put the generated schema in this section. This means the body spec is duplicated for request and response. Also, even though this is mostly a DRF limitation, you can't use distinct schemas (via oneOf , anyOf , etc) for request, response or both.

When it comes to actual mapping of serializer fields to OA3 schema fields, DRF does a pretty good job. The majority of keywords and attributes are supported correctly. Here's a sample model and a serializer where I tried to include as much various field and attribute combinations as I could to demo the functionality:

# models.py from django.db import models from django.core.validators import MinLengthValidator class Author(models.Model): first_name = models.CharField(max_length=200) class Book(models.Model): KINDS = [ ('hc', 'Hardcover'), ('sc', 'Softcover') ] title = models.CharField(max_length=200) kind = models.CharField(max_length=2, choices=KINDS) description = models.CharField(max_length=300, validators=[MinLengthValidator(10)]) pages = models.IntegerField(null=True) status = models.BooleanField() author = models.ForeignKey(Author, on_delete=models.CASCADE) # serializers.py from rest_framework import serializers from .models import Author, Book class AuthorSerializer(serializers.ModelSerializer): class Meta: model = Author fields = '__all__' class NestedSerializer(serializers.Serializer): title = serializers.CharField() class BookSerializer(serializers.ModelSerializer): author = AuthorSerializer() nested_test = NestedSerializer() char_test = serializers.CharField(default='value', required=False, help_text='Test field') email_test = serializers.EmailField() ip_test = serializers.IPAddressField('IPv4') class Meta: model = Book fields = '__all__' # views.py from rest_framework import generics class BookDetail(generics.UpdateAPIView): queryset = Book.objects.all() serializer_class = BookSerializer allowed_methods = ['put']

paths: /api/books/{id}/: put: operationId: UpdateBook parameters: - name: id in: path required: true description: A unique integer value identifying this book. schema: type: string requestBody: content: application/json: schema: required: - author - nested_test - email_test - ip_test - title - kind - description - status properties: author: required: - first_name properties: id: type: integer readOnly: true first_name: type: string maxLength: 200 type: object nested_test: required: - title properties: title: type: string type: object char_test: type: string default: value description: Test field email_test: type: string format: email ip_test: type: string format: ipv4 title: type: string maxLength: 200 kind: enum: - hc - sc description: type: string maxLength: 300 minLength: 10 pages: type: integer nullable: true status: type: boolean responses: '200': content: application/json: schema: required: - author - nested_test - email_test - ip_test - title - kind - description - status properties: id: type: integer readOnly: true author: required: - first_name properties: id: type: integer readOnly: true first_name: type: string maxLength: 200 type: object nested_test: required: - title properties: title: type: string type: object char_test: type: string default: value description: Test field email_test: type: string format: email ip_test: type: string format: ipv4 title: type: string maxLength: 200 kind: enum: - hc - sc description: type: string maxLength: 300 minLength: 10 pages: type: integer nullable: true status: type: boolean

As you can see DRF supports a lot of OA3 schema fields features. There are still some bugs or unsupported parts but it's not a big deal. Here's a rough list of what is not supported (this is what I've been able to find, it's not an exhaustive list):

ListField min_length & max_length attributes don't map to minItems & maxItem schema attributes ignores the attributes of a child 's field

Related fields PrimaryKeyRelatedField results in type: string (should be integer ) HyperlinkedRelatedField , HyperlinkedIdentityField could have included format: uri .

DictField : ignores the attributes of a child 's field

: ignores the attributes of a 's field FileField : doesn't add a format: binary to a field's schema

: doesn't add a to a field's schema read_only fields are not included in request body, write_only fields are not included in response body. Not sure about this one, but I think it should include the same set of fields in both request & response with relevant attributes

These are not big issues, so the field mapping support is still pretty good.

Unreleased but solved issues

In other words issues that are already fixed but not yet released. Here's a list of new commits since the latest release, most of them are about OpenAPI schema generation.

There is also a number of OpenAPI related PRs that are either completed but not merged or still in progress:

Summary

As you can see the OpenAPI 3 support in DRF is still far from complete but the majority of the functionality is there. Improved API for spec customization would also be nice (e.g. overriding a method to customize a schema attribute rather than a whole view inspector). This is a work in progress, so hopefully the issues will be fixed within the next couple releases. For now, the best approach to work with a spec would be to generate a static version and then modifying the relevant parts manually.