Swagger with Django made easy | A drf-spectacular explainer

Rohit Kumar
6 min readJan 25, 2021

The problem

Let’s talk about the problem: We have an API that we want to document. We can maintain a document with that information or an HTML website, or even have it generated by some tool from our code. The downside to this approach is that we need to keep it maintained manually while it is human-readable, it’s not really machine-readable. This problem is solved by Swagger following OpenAPI guidelines.

Open API

Let’s start with understanding the Open API term first. Simply put, It is a format/specification that you have to follow when describing your APIs. It allows you to describe available endpoints, input, and output to the APIs, authentication methods, contact information, etc. In order to have robust and thorough documentation, you have to have this kind of information. And swagger helps us with that.

Swagger

It's a tool that helps you design, document, and run APIs then and there, just like postman(another tool for testing out APIs). It is based on of course latest Open API specifications. There are various tools that come with Swagger. We will focus on the tool that helps us test our APIs in our own browser. It is known as Swagger UI. More on this later.

Various tools for Django

So the task at hand is to create beautiful docs for our Django application according to the latest OpenAPI Specification(that is version 3).

We have various open-source packages:

a) django-rest-swagger — It is an inactive project for the last 3 years so we will skip that.

b) drf-yasg — It comes with a warning “drf-yasg is unlikely to soon if ever, get support for OpenAPI 3.0” so we will skip that also.

c) drf-spectacular — Now finally something latest and we can use!

Installation and validation that everything is working dandy and fine

mkdir swag
cd swag

We will be working in a directory called swag(That’s right!)

>swagenv\Scripts\activate

Activate your environment. I am using windows.

pip install django
pip install djangorestframework

pip install your frameworks in your environment.

django-admin startproject blog_project
cd blog_project
python manage.py migrate
python manage.py startapp posts

In this step, we have started a project called “blog_project”. Now we have our manage.py, initial url_conf, and settings.py. Now cd into our project and run the migrate command. It will make initial tables for our sessions, user, and group usage. Let’s start an app called “posts” and begin writing our code in it.

Now make serializers.py in your posts app and copy this

from django.contrib.auth.models import User, Group
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['url', 'username', 'email', 'groups']
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ['url', 'name']

In views.py copy and paste this

from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from rest_framework import permissions
from posts.serializers import UserSerializer, GroupSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
permission_classes = [permissions.IsAuthenticated]

Change your URL conf like this in blog_project

from django.urls import include, path
from rest_framework import routers
from posts import views
router = routers.DefaultRouter()
router.register(r"users", views.UserViewSet)
router.register(r"groups", views.GroupViewSet)
urlpatterns = [
path("", include(router.urls)),
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
]

In blog_project/settings.py add a variable REST_FRAMEWORK which will be useful for later. Add pagination while you are at it. Pagination helps you when the objects to be shown are pretty big in numbers. Not related to this demo though.

REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}

Add “rest_framework” to the INSTALLED_APPS variable in settings only.

INSTALLED_APPS = ['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework','posts',]

Now run the server!

python manage.py runserver

The server should be up and running on http://127.0.0.1:8000/

Without logged in view should be like this

Server at the root URL
No user info when not logged in

Logged in view

Getting user info when logged in

Now let’s start integrating swagger.

drf-spectacular

Install it in your environment like everything else

pip install drf-spectacular

Add “drf_spectacular” in INSTALLED_APPS in settings.py

add auto schema to REST_FRAMEWORK in settings.py again

REST_FRAMEWORK = {'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}

Open up your urls.py(URL conf) and make these changes, as these URLs are required for swagger UI

from django.urls import include, path
from rest_framework import routers
from posts import views
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

router = routers.DefaultRouter()
router.register(r"users", views.UserViewSet)
router.register(r"groups", views.GroupViewSet)

urlpatterns = [
path("", include(router.urls)),
path('admin/', admin.site.urls),
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),

path("schema/", SpectacularAPIView.as_view(), name="schema"),
path(
"docs/",
SpectacularSwaggerView.as_view(
template_name="swagger-ui.html", url_name="schema"
),
name="swagger-ui",
),

]

Update:- I have been informed by @tfranzel himself, the author of this repository, that drf-spectacular has gone through some changes. Rather than creating the custom “swagger-ui.html”, you can use the default one without any hitch. So please skip this portion of the tutorial. Please gander at the comment section if you need any further clarification. I will delete this section in a few days' time.

Now as you might have noticed we required “swagger-ui.html”. I assume you should be at the level of manage.py. Make a new directory named “templates” there and in that a new file named “swagger-ui.html”.

the directory structure should be like this

Then open “swagger-ui.html” and paste the following code

<!DOCTYPE html>
<html>

<head>
<title>Swagger</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3/swagger-ui.css">
</head>

<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<script>
const ui = SwaggerUIBundle({
url: "{% url 'schema' %}",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout",
requestInterceptor: (request) => {
request.headers['X-CSRFToken'] = "{{ csrf_token }}"
return request;
}
})
</script>
</body>

</html>

In settings.py make the following changes in the TEMPLATES dictionary DIRS key

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

If all goes well. Go to http://127.0.0.1:8000/docs/ and you will see your APIs swagger look

Swagger UI

To run it remember to first authorize yourself. Swagger provides you with various options for that. There is an authorization button on this page. Click it and you will get a pop-up something like this:

Swagger auth UI

Authorize yourself, you can use your superuser credentials for this demo

Go to GET point of /users/ and click on execute

you will get a response something like this. Now you can use this instead of the postman. It is easy to use and in your browser only. Play and experiment with it yourself.

Your Swagger UI documentation implemented in the OpenAPI 3.0 standard is ready. This concludes our blog. I hope you have learned something.

For other farmeworks

You can implement swagger in Flask also. There are quality resources on it. I will link one such resource here. This uses Flask and Flask-RESTPlus for the integration of swagger.

--

--