DEV Community

Cover image for Developing REST API Using Django REST Framework (DRF)
Muhammad Rahiman
Muhammad Rahiman

Posted on • Edited on

Developing REST API Using Django REST Framework (DRF)

Introduction

This is a sample project guide for exploring the development of REST APIs using Django REST Framework (DRF). GitHub repository link.

Setting Up Django

1. Create new Django Project

To create your Django project from scratch (make sure to have Django installed):

$ django-admin startproject project_name
Enter fullscreen mode Exit fullscreen mode

2. Create new Virtual Environment

Next, navigate into the newly created project folder. Then, create a new virtual environment.

  • Using virtualenv:

    $ cd project_name
    $ virtualenv ENV_NAME
    $ source ENV_NAME/bin/activate
    
  • Using Python venv:

    $ cd project_name
    $ python3 -m venv
    $ source ENV_NAME/bin/activate
    

3. Install PIP Dependencies

Run the following command preferably inside your virtual environment to install all relevant dependencies:

  • Using pipenv:

    $ pipenv install -r requirements.txt # (Python 2)
    $ pipenv3 install -r requirements.txt # (Python 3)
    
  • Using venv:

    $ pip install -r requirements.txt # (Python 2)
    $ pip3 install -r requirements.txt # (Python 3)
    

4. Start New Django App

After that, start a new Django app. We will also run migrations and start up the server:

$ python manage.py startapp app_name
$ python manage.py migrate
$ python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

If everything works well, we should see an instance of a Django application running on this address — http://localhost:8000

alt text


5. Configure settings.py

Once you’ve created the app, you need to install it in your project. In project_name/settings.py, add the following line of code under INSTALLED_APPS:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app_name.apps.App_name',
]
Enter fullscreen mode Exit fullscreen mode

That line of code means that your project now knows that the app you just created exists.

Add templates folder directory in TEMPLATES (project_name/settings.py)

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates/'], # HERE
        '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',
            ],
        },
    },
]
Enter fullscreen mode Exit fullscreen mode

Add static and media folder directory in STATIC_ROOT (project_name/settings.py)

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Enter fullscreen mode Exit fullscreen mode

5. Configure urls.py

Add desired URL for the app (project_name/urls.py)

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('INSERT_URL', include('APP_NAME.urls')),
]
Enter fullscreen mode Exit fullscreen mode

Finally, create new urls.py for the app (app_name/urls.py).

Setting Up Django REST Framework

1. Install Django REST Framework

$ pip install djangorestframework
Enter fullscreen mode Exit fullscreen mode

2. Configure settings.py

Configure INSTALLED_APPS as follows. Then, remigrate using the command python manage.py migrate.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    # To include support for DEFAULT_AUTHENTICATION_CLASSES, must include this and migrate!
    'rest_framework.authtoken',
    'app_name.apps.AppName',
]
Enter fullscreen mode Exit fullscreen mode

Next, we need to add REST_FRAMEWORK settings as follows. Descriptions of the settings as included for your quick reference.

# ...

# Disable the Browsable HTML API
DEFAULT_RENDERER_CLASSES = (
    'rest_framework.renderers.JSONRenderer',
)

# Only enable the browseable HTML API in dev (DEBUG=True)
if DEBUG:
    DEFAULT_RENDERER_CLASSES = DEFAULT_RENDERER_CLASSES + (
        'rest_framework.renderers.BrowsableAPIRenderer',
    )

REST_FRAMEWORK = {
    # Disable the Browsable HTML API UI when in production (DEBUG=False)
    'DEFAULT_RENDERER_CLASSES': DEFAULT_RENDERER_CLASSES,

    # Pagination allows you to control how many objects per page are returned
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 5,

    # The default permission policy may be set globally, using the DEFAULT_PERMISSION_CLASSES setting
    # To be explained in detail later in the guide
    # 'DEFAULT_PERMISSION_CLASSES': [
    # 'rest_framework.permissions.AllowAny',
    # 'rest_framework.permissions.IsAuthenticated',
    # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    # 'rest_framework.permissions.DjangoModelPermissions',
    # 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
    # 'rest_framework.permissions.DjangoObjectPermissions',
    # 'rest_framework.permissions.TokenHasReadWriteScope',
    # ],

    # A list or tuple of authentication classes, that determines the default set of authenticators used when accessing the request.user or request.auth properties.
    # The default authentication schemes may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting
    # To be explained in detail later in the guide

    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 'rest_framework.authentication.BasicAuthentication',
        # 'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),

# ...
Enter fullscreen mode Exit fullscreen mode

Browserable HTML API is the GUI that is included in DRF which allows users to interact with the API and for the API to return a fully web-browsable HTML representation. While this is useful, this should not be accessible during production as we do not want any user to access the API without authentication (more on authentication later in the guide).

Hence, we need to override the DEFAULT_RENDERER_CLASSES value from 'rest_framework.renderers.BrowsableAPIRenderer' to 'rest_framework.renderers.JSONRenderer' depending on which environment the web app is running at.


3. Create a Model

In this guide, we will be creating a model for a simple Todo list. However, feel free to make a model of your own preference.

Go to app_name/models.py and create your model similar to the following:

from django.db import models

class Todo(models.Model):
    title = models.CharField(max_length=120)
    description = models.TextField()
    completed = models.BooleanField(default=False)

    def __str__(self):
        return self.title + " (" + str(self.id) + ")"
Enter fullscreen mode Exit fullscreen mode

Refer to this guide for more information on Django Models.


4. Configure Serializers

Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types.

Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

To create our serializers, create a file called serializers.py inside app_name:

$ touch serializers.py
Enter fullscreen mode Exit fullscreen mode

There are 2 main types of serializers:

  1. HyperlinkedModelSerializer

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks
to represent relationships, rather than primary keys.

  1. ModelSerializer

The ModelSerializer class provides a shortcut that lets you automatically create a Serializer class with
fields that correspond to the Model fields.

In this guide, we will be using ModelSerializer instead:

from rest_framework import serializers
from django.contrib.auth.models import User, Group
from .models import *

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        # fields = ('id', 'title', 'description', 'completed')
        # Shortcut for getting all fields
        fields = '__all__'
Enter fullscreen mode Exit fullscreen mode

5. Configure Views (views.py)

There are various ways to configure the views for the APIs. We can categorise them into 2 types:

  1. Function-based views (FBVs)
  2. Class-based views (CBVs)
  • APIView
  • ModelViewSet
  • Generics

Regardless of which type of view you choose, we typically need to develop 2 views:

  1. List (for listing all objects/instances of the model)
  2. Detail (for viewing a specific object/instance of the model)

Let's take a look at each type in details:

  1. Function-based views (@api_view decorator)

Function-based views or FBVs are popular for Django newcomers as it is verbose and easy to understand. REST framework provides the @api_view decorator for working with function based views. The wrapper providse a few bits of functionality such as making sure you receive Request instances in your view, and adding context to Response objects so that content negotiation can be performed.

The following is an example of the FBV for our Todo app:

    from rest_framework import status
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from .models import *
    from .serializers import *

    # FBV for list of all Todo objects
    @api_view(['GET', 'POST'])

        def todo_list(request):
        """
        List all code todos, or create a new todo.
        """
        if request.method == 'GET':
            todos = Todo.objects.all()
            serializer = TodoSerializer(todos, many=True)
            return Response(serializer.data)

        elif request.method == 'POST':
            serializer = TodoSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        @api_view(['GET', 'PUT', 'DELETE'])
        def todo_detail(request, pk):
            """
            Retrieve, update or delete a code todo.
            """
            try:
                todo = Todo.objects.get(pk=pk)
            except todo.DoesNotExist:
                return Response(status=status.HTTP_404_NOT_FOUND)

            if request.method == 'GET':
                serializer = TodoSerializer(todo)
                return Response(serializer.data)

            elif request.method == 'PUT':
                serializer = TodoSerializer(todo, data=request.data)
                if serializer.is_valid():
                    serializer.save()
                    return Response(serializer.data)
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

            elif request.method == 'DELETE':
                todo.delete()
                return Response(status=status.HTTP_204_NO_CONTENT)
Enter fullscreen mode Exit fullscreen mode
  1. Class-based views (APIView)

We can also write our API views using class-based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code DRY (Don't Repeat Yourself). The APIView class is the wrapper used for working with class-based views.

   from rest_framework import status
   from rest_framework.decorators import api_view
   from rest_framework.response import Response
   from .models import *
   from .serializers import *

   class TodoList(APIView):
       """
       List all todos, or create a new todo.
       """
       def get(self, request, format=None):
           todos = Todo.objects.all()
           serializer = TodoSerializer(todos, many=True)
           return Response(serializer.data)

       def post(self, request, format=None):
           serializer = TodoSerializer(data=request.data)
           if serializer.is_valid():
               serializer.save()
               return Response(serializer.data, status=status.HTTP_201_CREATED)
           return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

   class TodoDetail(APIView):
       """
       Retrieve, update or delete a todo instance.
       """
       def get_object(self, pk):
           try:
               return Todo.objects.get(pk=pk)
           except Todo.DoesNotExist:
               raise Http404

       def get(self, request, pk, format=None):
           todo = self.get_object(pk)
           serializer = TodoSerializer(todo)
           return Response(serializer.data)

       def put(self, request, pk, format=None):
           todo = self.get_object(pk)
           serializer = TodoSerializer(todo, data=request.data)
           if serializer.is_valid():
               serializer.save()
               return Response(serializer.data)
           return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

       def delete(self, request, pk, format=None):
           todo = self.get_object(pk)
           todo.delete()
           return Response(status=status.HTTP_204_NO_CONTENT)
Enter fullscreen mode Exit fullscreen mode
  1. Class-based views (ModelViewSets)

ModelViewSet inherits from GenericAPIView and includes implementations for various actions. In other words, you don't need implement basic actions as list, retrieve, create, update or destroy.

In comparison with APIView, APIView allow us to define functions that match standard HTTP methods like GET, POST, PUT, PATCH, etc. Meanwhile, because CRUD is so common, DRF provides ModelViewSet which allow us to define functions that match to common API object actions like LIST, CREATE, RETRIEVE, UPDATE, etc.

When compared with GenericViewSet (the 4th type), to create CRUD, GenericViewSet needs two classes(ListCreateAPIView and RetrieveUpdateDestroyAPIView).
But ModelViewSet needs only one class(ModelViewSet)

The biggest difference between ModelViewSet and other types of views is that ModelViewSet support creating url pattern automatically with DRF router while others require you to configure the API endpoint manually yourself in urls.py.

Here's an example of what a ModelViewSet views look like:

   from .models import *
   from .serializers import *
   from django.shortcuts import render
   from django.contrib.auth.models import User, Group
   from rest_framework import permissions, viewsets

   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

   class GroupViewSet(viewsets.ModelViewSet):
       """
       API endpoint that allows groups to be viewed or edited.
       """
       queryset = Group.objects.all()
       serializer_class = GroupSerializer
Enter fullscreen mode Exit fullscreen mode
  1. Class-based views (GenericViewSet)

REST framework provides a set of already mixed-in generic class-based views that we can use to trim down our views.py module even more.

GenericViewGenericsSet inherits from GenericAPIView but does not provide any implementations of basic actions. Just only get_object, get_queryset.

To create CRUD, Generics needs two classes(ListCreateAPIView and RetrieveUpdateDestroyAPIView).

This is the simplest way for creating a view for an API in DRF (literally only with 2 lines of code!) that balances between simplicity and customization (for the API endpoint/URL).

The following is an example of how a GenericViewSet for our Todo app API:

   from .models import *
   from .serializers import *
   from django.shortcuts import render
   from rest_framework.response import Response
   from rest_framework import generics, permissions, status

   class TodoList(generics.ListCreateAPIView):
       queryset = Todo.objects.all()
       serializer_class = TodoSerializer


   class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
       queryset = Todo.objects.all()
       serializer_class = TodoSerializer
Enter fullscreen mode Exit fullscreen mode

6. Configure API Endpoints (urls.py)

Earlier, we have created a new urls.py inside our app. All we have to do next is to link the views we configured just now inside the file.

The following is an example of the url patterns for each type of view that we discussed:

# todo/urls.py
from . import views
from rest_framework import routers
from django.urls import path, include
# Rename views to avoid conflict with app views
from rest_framework.authtoken import views as rest_views

"""
When using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.

If we need more control over the API URLs we can simply drop down to using regular class-based views (APIViews), and writing the URL conf explicitly.
"""

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

# Rename views to avoid conflict with app views
from rest_framework.authtoken import views as rest_views

urlpatterns = [
    # URLs for class-based views (Generics, APIViews)
    # http://localhost:8000/todo/
    # http://localhost:8000/todo/<int:pk>
    path('todo/', views.TodoList.as_view(), name='todo_list'),
    path('todo/<int:pk>', views.TodoDetail.as_view(), name='todo_detail'),

    # URLs for class-based views (ModelViewSets)
    # http://localhost:8000/general/users/
    # http://localhost:8000/general/groups/
    path('general/', include(router.urls)),

    # Include default login and logout views for use with the browsable API. 
    # Optional, but useful if your API requires authentication and you want to use the browsable API.
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    # API to generate auth token from user. Note that the URL part of the pattern can be whatever you want to use.
    path('api-token-auth/', rest_views.obtain_auth_token, name='api-token-auth'),

    # URLs for function-based views
    # http://localhost:8000/todo/
    # http://localhost:8000/todo/<int:pk>
    # path('todo/', views.todo_list),
    # path('todo/<int:pk>', views.todo_detail),
]
Enter fullscreen mode Exit fullscreen mode

Note that regardless of which type of CBV you use, the method of writing the URL pattern is the same. The exact URL pattern is not fixed and can be customised to your preference.


7. Test API Connection

At this point, we pretty much have completed the basic configuration of the REST API. Time to test and see if it works!

Run Django server using python manage.py runserver command then navigate to the API endpoint based on the URL pattern that you configured. For this project, it is http://localhost:8000/todo/ or http://127.0.0.1:8000/todo/

IMPORTANT!: Always remember that API endpoints MUST end with a slash '/' or else it will not work!

You should be seeing something similar like this:

Imgur Image

The HTML you are seeing is the Browsable HTML API that we talked about earlier. You can click the 'GET' button to retrieve all Todo instances in our model as well filling in the 'POST' form and click the 'POST' button to submit a new Todo instance.

Another alternate and more popular way of testing APIs is via the command line interface (CLI):

  • Using HTTPie (recommended):

    $ http GET localhost:8000/todo/
    
  • Using curl:

    $ curl -X GET localhost:8000/todo/
    

For GET request, you should be seeing something like this:

  • Using HTTPie (recommended):

    HTTP/1.1 200 OK
    Allow: GET, POST, HEAD, OPTIONS
    Content-Length: 153
    Content-Type: application/json
    Date: Sat, 10 Jul 2021 03:56:59 GMT
    Referrer-Policy: same-origin
    Server: WSGIServer/0.2 CPython/3.9.5
    Vary: Accept, Cookie
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    
    {
        "count": 1,
        "next": null,
        "previous": null,
        "results": [
            {
                "completed": false,
                "description": "FYP2 task for Aug",
                "id": 7,
                "title": "Research on Django-React stack"
            }
        ]
    }
    
  • Using curl:

    {"count":1,"next":null,"previous":null,"results":[{"id":7,"title":"Research on Django-React stack","description":"FYP2 task for Aug","completed":false}]}%   
    

Same goes for POST requests:

  • Using HTTPie (recommended):

    http POST localhost:8000/todo/ title="Research on RF for EDM" description="FYP2 task for Aug"
    
    HTTP/1.1 201 Created
    Allow: GET, POST, HEAD, OPTIONS
    Content-Length: 94
    Content-Type: application/json
    Date: Sat, 10 Jul 2021 04:01:48 GMT
    Referrer-Policy: same-origin
    Server: WSGIServer/0.2 CPython/3.9.5
    Vary: Accept, Cookie
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    
    {
        "completed": false,
        "description": "FYP2 task for Aug",
        "id": 10,
        "title": "Research on RF for EDM"
    }
    
  • Using curl:

    curl -X POST -d 'title=Research on SVM for EDM&description=FYP2 task for Sept' localhost:8000/todo/
    {"id":11,"title":"Research on SVM for EDM","description":"FYP2 task for Sept","completed":false}%                               
    

Configure Permissions and Authentication

So far, we've successfully configured our REST API using the default permissions and authentication settings. In this basic configuration, DRF defaults to AllowAny permission class which will allow unrestricted access, regardless of if the request was authenticated or unauthenticated. This is certainly dangerous and not ideal for our web app especially during production. Hence, we need to configure the permissions for restricted access on our REST APIs.

1. Permissions

There are several types of permission classes available:

  1. AllowAny

The AllowAny permission class will allow unrestricted access, regardless of if the request was authenticated or unauthenticated.

This permission is not strictly required, since you can achieve the same result by using an empty list or tuple for the permissions setting, but you may find it useful to specify this class because it makes the intention explicit.

  1. IsAuthenticated

The IsAuthenticated permission class will deny permission to any unauthenticated user, and allow permission otherwise.

This permission is suitable if you want your API to only be accessible to registered users.

  1. IsAdminUser

The IsAdminUser permission class will deny permission to any user, unless user.is_staff is True in which case permission will be allowed.

This permission is suitable is you want your API to only be accessible to a subset of trusted administrators.

  1. IsAuthenticatedOrReadOnly

The IsAuthenticatedOrReadOnly will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods; GET, HEAD or OPTIONS.

This permission is suitable if you want to your API to allow read permissions to anonymous users, and only allow write permissions to authenticated users.

  1. DjangoModelPermissions

This permission class ties into Django's standard django.contrib.auth model permissions. This permission must only be applied to views that has a .queryset property set.

Authorization will only be granted if the user is authenticated and has the relevant model permissions assigned.

  1. DjangoModelPermissionsOrAnonReadOnly

Similar to DjangoModelPermissions, but also allows unauthenticated users to have read-only access to the API.

  1. DjangoObjectPermissions

This permission class ties into Django's standard object permissions framework that allows per-object permissions on models. In order to use this permission class,
you'll also need to add a permission backend that supports object-level permissions, such as django-guardian.

  1. TokenHasReadWriteScope

This permission class is intended for use with either of the OAuthAuthentication and OAuth2Authentication classes, and ties into the scoping that their backends provide. More info about this here.

The permissions setting can be configured either at

  1. global-level (at settings.py)
   REST_FRAMEWORK = {

    #...

    # The default permission policy may be set globally, using the DEFAULT_PERMISSION_CLASSES setting:
    # 'DEFAULT_PERMISSION_CLASSES': [
    # 'rest_framework.permissions.AllowAny',
    # 'rest_framework.permissions.IsAuthenticated',
    # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    # 'rest_framework.permissions.DjangoModelPermissions',
    # 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
    # 'rest_framework.permissions.DjangoObjectPermissions',
    # 'rest_framework.permissions.TokenHasReadWriteScope',
    # ],
Enter fullscreen mode Exit fullscreen mode
  1. view-level (at views.py)
   class TodoList(generics.ListCreateAPIView):
       queryset = Todo.objects.all()
       # View-level permissions
       # IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access,
       # and unauthenticated requests get read-only access.
       permission_classes = [permissions.IsAuthenticatedOrReadOnly]
       serializer_class = TodoSerializer


   class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
       queryset = Todo.objects.all()
       # View-level permissions
       # IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access,
       # and unauthenticated requests get read-only access.
       permission_classes = [permissions.IsAuthenticatedOrReadOnly]
       serializer_class = TodoSerializer
Enter fullscreen mode Exit fullscreen mode

Once the permissions have been set, if you try to send a request (GET and/or POST, depending on permission class set), it will be blocked:

http POST localhost:8000/todo/ title="Research on Django-React stack" description="FYP2 task for Aug" 

HTTP/1.1 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Sat, 10 Jul 2021 04:01:28 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.9.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "detail": "Authentication credentials were not provided."
}
Enter fullscreen mode Exit fullscreen mode

2. Authentication

Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The permission and throttling policies can then use those credentials to determine if the request should be permitted.

REST framework provides a number of authentication schemes out of the box, and also allows you to implement custom schemes:

  1. BasicAuthentication

This authentication scheme uses HTTP Basic Authentication, signed against a user's username and password. Basic authentication is generally only appropriate for testing.

  1. SessionAuthentication

This authentication scheme uses Django's default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website.

  1. TokenAuthentication

This authentication scheme uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients.

To use the TokenAuthentication scheme you'll need to configure the authentication classes to include TokenAuthentication, and additionally include rest_framework.authtoken in your INSTALLED_APPS setting inside settings.py. More on this later in the guide.

Note: Don't forget that authentication by itself won't allow or disallow an incoming request, it simply identifies the credentials that the request was made with.

Authentication can be configured globally in settings.py:

REST_FRAMEWORK = {
#...

    # The default permission policy may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting:
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),
}
Enter fullscreen mode Exit fullscreen mode

One of the most common ways of authentication is via token authentication. We can easily do so via the following steps:

  1. Set 'DEFAULT_AUTHENTICATION_CLASSES' to Token Authentication
    REST_FRAMEWORK = {
    #...

        # The default permission policy may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting:
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.TokenAuthentication',
        ),
    }
Enter fullscreen mode Exit fullscreen mode
  1. Configure app_name/urls.py
   from . import views
   from rest_framework import routers
   from django.urls import path, include
   # Rename views to avoid conflict with app views
   from rest_framework.authtoken import views as rest_views

   urlpatterns = [
       #...
       # Include default login and logout views for use with the browsable API. 
       # Optional, but useful if your API requires authentication and you want to use the browsable API.
       path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),

       # API to generate auth token from user. Note that the URL part of the pattern can be whatever you want to use.
       path('api-token-auth/', rest_views.obtain_auth_token, name='api-token-auth'),
   ]
Enter fullscreen mode Exit fullscreen mode
  1. Generate token for user
- Using Django manage.py command:
Enter fullscreen mode Exit fullscreen mode
    ```shell
    $ python manage.py drf_create_token USERNAME
    ```
Enter fullscreen mode Exit fullscreen mode
- Using CLI
Enter fullscreen mode Exit fullscreen mode
    ```shell
    $ http POST API_ENDPOINT username=USERNAME password=PASSWORD
    ```
Enter fullscreen mode Exit fullscreen mode
- Using `views.py`
Enter fullscreen mode Exit fullscreen mode
    ```python
    from rest_framework.authtoken.models import Token
    token = Token.objects.create(user=...)
    print(token.key)
    ```
Enter fullscreen mode Exit fullscreen mode
  1. Use token in accessing API
   $ http POST API_ENDPOINT model_fields 'Authorization: Token TOKEN'
Enter fullscreen mode Exit fullscreen mode

Where API_ENDPOINT refers to the url pattern of the API e.g. localhost:8000/todo/

API Documentation using drf_spectacular

Among the best practices and requirements when developing REST APIs for real software projects is providing in-depth documentation. drf_spectacular allows you to:

  1. Extract as much schema information from DRF as possible.
  2. Provide flexibility to make the schema usable in the real world (not only toy examples).
  3. Generate a schema that works well with the most popular client generators.

To install:

  1. Install using pip
   $ pip install drf-spectacular
Enter fullscreen mode Exit fullscreen mode
  1. Add drf-spectacular to INSTALLED_APPS in settings.py
   INSTALLED_APPS = [
       # ...
       'drf_spectacular',
   ]
Enter fullscreen mode Exit fullscreen mode
  1. Register AutoSchema with DRF
   REST_FRAMEWORK = {
       # YOUR SETTINGS
       'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
   }
Enter fullscreen mode Exit fullscreen mode
  1. Configure URL patterns inside project_name/urls.py
   from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView

   urlpatterns = [
       # YOUR PATTERNS
       path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
       # Optional UI:
       path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
       path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
   ]
Enter fullscreen mode Exit fullscreen mode

Note that the URL paths are customisable.

Try to access the URLs upon running the Django server and you should see something similar as shown below:

Imgur Image
Imgur Image

References

  1. https://www.digitalocean.com/community/tech_talks/building-a-rest-api-with-django-rest-framework
  2. https://simpleisbetterthancomplex.com/tutorial/2018/11/22/how-to-implement-token-authentication-using-django-rest-framework.html
  3. https://www.django-rest-framework.org/tutorial/quickstart/
  4. https://www.django-rest-framework.org/tutorial/1-serialization/
  5. https://www.django-rest-framework.org/tutorial/3-class-based-views/
  6. https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/
  7. https://www.django-rest-framework.org/api-guide/authentication/
  8. https://www.django-rest-framework.org/api-guide/permissions/
  9. https://github.com/tfranzel/drf-spectacular
  10. https://stackoverflow.com/questions/41379654/difference-between-apiview-class-and-viewsets-class
  11. https://www.geeksforgeeks.org/implement-token-authentication-using-django-rest-framework/
  12. https://stackoverflow.com/questions/25125959/django-rest-framework-generics-or-modelviewsets
  13. https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html
  14. https://httpie.io/
  15. http://www.tomchristie.com/rest-framework-2-docs/api-guide/permissions#how-permissions-are-determined

Top comments (7)

Collapse
 
injemam profile image
Injemam

Hi Rahiman,
Very good tutorial 🤘
I'm looking for an tutorial where i can learn how to put ratelimit based on user stripe subscription.

Example:
Basic_plan_user : 20000 request/month
Premium_user : 500000 request/months

If you know any tutorial it will be very helpful.
Or any plans to create then please let me know.

Collapse
 
krishmanohar profile image
KrishManohar

Great writeup

Collapse
 
mdrhmn profile image
Muhammad Rahiman

Thanks!

Collapse
 
abdenasser profile image
Nasser El Idrissi

awesome, you can also check this article I wrote about scaffolding Django Rest Apis
dev.to/abdenasser/scaffold-django-...

Collapse
 
mdrhmn profile image
Muhammad Rahiman

Nice! Thanks for the reference!

Collapse
 
kalpata59640859 profile image
Kalpataru Sahoo

why i can't see mysql file in project folder but it is visble in mysql commandline client and mysql workbench ..pls give any solution

Collapse
 
mdrhmn profile image
Muhammad Rahiman

Hi there! Apologies for the late reply. Unfortunately, I'm not really familiar with MySQL but I hope your issue has been resolved!