# How to generate an OpenAPI document with Django and Django REST framework
Source: https://speakeasy.com/openapi/frameworks/django

import { Callout } from "@/mdx/components";




OpenAPI is a tool for defining and sharing REST APIs, and Django can be paired with Django REST framework to build such APIs.

This guide walks you through generating an OpenAPI document from a Django project and using it to create SDKs with Speakeasy, covering the following steps:

1. Setting up a simple Django REST API with `djangorestframework`
2. Integrating `drf-spectacular`
3. Creating the OpenAPI document to describe the API
4. Customizing the OpenAPI schema
5. Using the Speakeasy CLI to create an SDK based on the schema
6. Integrating SDK creation into CI/CD workflows

## Requirements

This guide assumes you have a basic understanding of Django project structure and how REST APIs work.

You will also need the following installed on your machine:

- Python version 3.8 or higher
- Django

  You can install Django using the following command:

  ```bash filename="Terminal"
  pip install django
  ```

- Django REST Framework

  You can install Django REST Framework using the following command:

  ```bash filename="Terminal"
  pip install djangorestframework
  ```

## Example Django REST API repository

<Callout title="Example repository" type="info">
The source code for the completed example is available in the [**Speakeasy Django example repository**](https://github.com/speakeasy-api/django-openapi-example).
</Callout>

The example repository contains all the code covered in this guide. You can clone it and follow along with the tutorial or use it as a reference to add to your own Django project.

---

## Creating the OpenAPI document to describe an API

To better understand the process of generating an OpenAPI document with Django, let's start by inspecting some simple CRUD endpoints for an online library, along with a `Book` class and a serializer for the data.

### Models, serializers, and views

Let's look at the key components of our Django REST API:

### Book Model

First, let's examine the `books/models.py` file, which contains a `Book` model with validation fields:

```python filename="models.py"
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=100)
    published_year = models.IntegerField()
```

### Book Serializer

Next, let's look at the `books/serializers.py` file, which defines a `BookSerializer` for serializing and deserializing Book data:

```python filename="serializers.py"
from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['id', 'title', 'author', 'published_year']
```

### Book Views

The `books/views.py` file contains a `BookViewSet` that handles CRUD operations for the Book model:

```python filename="views.py"
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows books to be viewed or edited.
    """
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    @action(detail=True, methods=['get'])
    def author_books(self, request, pk=None):
        """
        Returns all books written by the same author as the specified book.
        """
        try:
            book = self.get_object()
        except Book.DoesNotExist:
            return Response(
                {"error": "Book not found"},
                status=status.HTTP_404_NOT_FOUND
            )

        author_books = Book.objects.filter(author=book.author).exclude(id=book.id)
        serializer = self.get_serializer(author_books, many=True)
        return Response(serializer.data)
```

This code defines a simple Django REST API with CRUD operations for the `Book` model. The `BookViewSet` provides a way to interact with the `Book` model through the API. It also contains a custom action called `author_books` that retrieves all books by the same author.

### URL Configuration

The `books/urls.py` file maps the `BookViewSet` to the `/books` endpoint:

```python filename="urls.py"
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import BookViewSet

router = DefaultRouter()
router.register(r'books', BookViewSet)

urlpatterns = [
    path('', include(router.urls)),
]
```

And in the `books_project/urls.py` file, the router is included in the main Django URL configuration:

```python filename="urls.py"
from django.contrib import admin
from django.urls import path, include
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    path('swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
    path('api/', include('books.urls')),
]
```



## Integrate `drf-spectacular`

Django no longer supports the built-in OpenAPI document generation, so we'll use the `drf-spectacular` package to generate the OpenAPI document.

Run the following to install `drf-spectacular`:

```bash filename="Terminal"
pip install drf-spectacular
```

### Setting up drf-spectacular

First, open the `books_project/settings.py` file to see how `drf-spectacular` is configured:

```python filename="settings.py"
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third party apps
    'rest_framework',
    'drf_spectacular',

    # Local apps
    'books',
]
```

Adding `'drf_spectacular'` to the `INSTALLED_APPS` list enables OpenAPI document generation for your Django project.

Next, check the `REST_FRAMEWORK` configuration object, which sets the schema class used to create the OpenAPI document:

```python filename="settings.py"
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
```

The `SPECTACULAR_SETTINGS` dictionary contains additional settings for OpenAPI document generation that you can customize to fit your project:

```python filename="settings.py"
SPECTACULAR_SETTINGS = {
    'TITLE': 'Library API',
    'DESCRIPTION': 'A simple API for managing books in a library',
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
    'COMPONENT_SPLIT_REQUEST': True,
    'SERVERS': [
        {'url': 'http://localhost:8000', 'description': 'Local Development server'},
        {'url': 'https://api.example.com', 'description': 'Production server'},
    ],
    'TAGS': [
        {'name': 'books', 'description': 'Book operations'},
    ],
    'EXTENSIONS_TO_SCHEMA_FUNCTION': lambda generator, request, public: {
        'x-speakeasy-retries': {
            'strategy': 'backoff',
            'backoff': {
                'initialInterval': 500,
                'maxInterval': 60000,
                'maxElapsedTime': 3600000,
                'exponent': 1.5,
            },
            'statusCodes': ['5XX'],
            'retryConnectionErrors': True,
        }
    }
}
```

In the `books_project/urls.py` file, the OpenAPI schema and Swagger UI endpoints are added alongside the `api/` endpoint:

```python filename="urls.py"
from django.contrib import admin
from django.urls import path, include
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    path('swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
    path('api/', include('books.urls')),
]
```


### Apply migrations and run the server

To inspect and interact with the OpenAPI document, you need to apply database migrations and run the development server.

Apply database migrations:

```bash filename="Terminal"
python manage.py makemigrations
python manage.py migrate
```

Run the development server:

```bash filename="Terminal"
python manage.py runserver
```

You can now access the API and documentation:

* Visit `http://127.0.0.1:8000/api/books/` to interact with the book API.
* Visit `http://127.0.0.1:8000/swagger/` for Swagger documentation.

### OpenAPI document generation

Now that we understand our Django REST API, we can generate the OpenAPI document using `drf-spectacular` with the following command:

```bash filename="Terminal"
python manage.py spectacular --file openapi.yaml
```

### Exploring the Generated OpenAPI Document

Running the command generates an OpenAPI document in the `openapi.yaml` file. Let's look at some key sections:

#### Document Header

The beginning of the document contains general information about the API:

```yaml filename="openapi.yaml"
openapi: 3.0.3
info:
  title: Library API
  description: A simple API for managing books in a library
  version: 1.0.0
  contact: {}
components:
  schemas:
    Book:
      type: object
      properties:
        id:
          type: integer
          readOnly: true
      # More properties follow...
```

#### Settings Influence on Generated Document

The values in `SPECTACULAR_SETTINGS` directly influence the OpenAPI document generation. For example, the title, description, and version in the settings:

```python filename="settings.py"
SPECTACULAR_SETTINGS = {
    'TITLE': 'Library API',
    'DESCRIPTION': 'A simple API for managing books in a library',
    'VERSION': '1.0.0',
    # Other settings...
}
```

These values appear in the OpenAPI document:

```yaml filename="openapi.yaml"
info:
  title: Library API
  description: A simple API for managing books in a library
  version: 1.0.0
```

#### Server Information

The server URLs specified in the settings appear in the document as well:

```yaml filename="openapi.yaml"
servers:
  - url: http://localhost:8000
    description: Local Development server
  - url: https://api.example.com
    description: Production server
```

#### Model Parameters

The fields we defined in our Django models are also reflected in the OpenAPI document. For example, the `Book` model fields:

```python filename="models.py"
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=100)
    published_year = models.IntegerField()
```

These fields appear in the OpenAPI document's schema definitions:

```yaml filename="openapi.yaml"
Book:
  type: object
  properties:
    id:
      type: integer
      readOnly: true
    title:
      type: string
      maxLength: 100
    author:
      type: string
      maxLength: 100
    published_year:
      type: integer
  required:
    - title
    - author
    - published_year
```

The OpenAPI document captures all the essential information about our API, including endpoints, parameters, request bodies, responses, and schemas. This document can then be used to generate client SDKs or API documentation.


## OpenAPI document customization

The OpenAPI document generated by `drf-spectacular` may not be detailed enough for all use cases. Fortunately, it can be customized to better serve information about your API endpoints. You can add descriptions, tags, examples, and more to make the documentation more informative and user-friendly.

In the [customized](https://github.com/speakeasy-api/django-openapi-example/tree/customized) branch of the example repository, you can find a customized OpenAPI document that demonstrates the available options for modifying your generated document.

The `drf-spectacular` package provides decorators to directly modify the schema for your views and viewsets.

- `@extend_schema_view`: Allows customization of all methods in a viewset.
- `@extend_schema`: Allows customization of individual methods or actions.

### Customizing the API Schema

Let's explore how to enhance the OpenAPI document by customizing the schema of the `BookViewSet`. Here's an updated version of the `books/views.py` file with added annotations:

```python filename="views.py"
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample, OpenApiResponse
from .models import Book
from .serializers import BookSerializer