Build API using Django REST framework

In the previous post, we built a web API using pure Django to understand how APIs work under the hood. In this post, we will learn to build API in an easy way .i.e. by using the Django REST framework. It is a powerful and flexible toolkit for building Web APIs.

django rest framework logo

Why Django REST framework?

Django Rest Framework lets you create Restful API in a simple way. Some of the reasons to use Django REST framework are:

  • Builtin features that follows don’t repeat yourself policies (DRY) such as serialization, pagination
  • Authentication policies (OAuth)
  • Web browsable api
  • Great Documentation

Overview of Project

We will create an app that returns the blog posts of users in JSON with some additional features i.e. searching, pagination, and authentication.

DecisionMentor app

We will implement a generic view of rest_framework. By the end of this post, we will create two API endpoints:

  1. PostAPIList(`/api/posts/`)
    PostAPIList will return a query-set and POST a new blog post. It will also return search result of posts with ‘api/posts/?q= ‘
  2. PostAPIDetail (`/api/post/id/`)
    PostAPIDetail will return a single blog post object, edit the blog post, and delete the blog post.

So now let’s go ahead and dive in.

Setup project

First and foremost create a project (drfapi) named as you like, inside a virtual environment (restapi).

  • create an app called blog
  • add the app inside INSTALLED_APP of settings.py
  • migrate the builtin tables to database (SQLite)
  • create a superuser to access the admin panel
migrate posts table in database

If you are new to Django check my previous posts Getting Started with Django and Django Admin Panel.

Install Django REST framework

Install the following packages using pip inside virtual environment:

#terminal
pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter  # Filtering support

Add 'rest_framework' to your INSTALLED_APPS setting.

#settings.py
INSTALLED_APPS = [
    ...
    'blog',
    'rest_framework',
]

Add the following to your root urls.py file to use the browsable API and add the REST framework’s login and logout views.

django rest framework login view
#urls.py
urlpatterns = [
    ...
    path('api-auth/', include('rest_framework.urls')),
]

Create Post Model

Let’s create a model called Posts with owner, content, image, updated, and timestamp fields.

#posts/models.py
from django.db import models

def upload_posts_image(instance, filename):
    return "posts/{owner}/{filename}".format(owner=instance.owner, filename=filename)

class Posts(models.Model):
    owner = models.ForeignKey('auth.User', related_name='posts', on_delete=models.CASCADE)
    content = models.TextField(null=True, blank=True)
    image = models.ImageField(upload_to=upload_posts_image, null=True, blank=True)
    updated = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return str(self.content)[:30]

Create Serializer class

First thing we need to get started on our Web API is to provide a way of serializing and deserializing the blog posts instances into representations such as json.

We can do this by declaring serializers that work very similar to Django’s forms. Create a file inside the blog app called serializers.py.

#blog/serializers.py
from rest_framework import serializers
from blog.models import Posts

class PostSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Posts
        fields = ['id', 'content','image','owner']

In the same way that Django provides both Form classes and ModelForm classes, REST framework includes both Serializer classes, and ModelSerializer classes. Here we use ModelSerializer from rest_framework.

And owner field is read-only and saves the user who is logged in.

Note: you can validation data in serializers class similar to forms

Add Authentication

We’d like all blog posts to be visible to anyone, but also make sure that only the user that created a post is able to update or delete it. To do that we’re going to need to create a custom permission.

In the blog app, create a new file, permissions.py

#blog/permissions.py
from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the posts.
        return obj.owner == request.user

Write views

We will create two endpoints so two class is required .i.e. PostAPIList and PostAPIDetail

create a list view class as:

#blog/views.py
from .models import Posts
from .serializers import PostSerializer
from rest_framework import generics, permissions
from blog.permissions import IsOwnerOrReadOnly

class PostAPIList(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    serializer_class = PostSerializer

    def get_queryset(self):
        qs = Posts.objects.all()
        query = self.request.GET.get('q')
        if query is not None:
            qs = qs.filter(content__icontains=query)
        return qs

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

The class PostAPIList works (/api/posts/) as:

  • inherits ListCreateAPIView from generic class of rest_framework
  • inherits built-in permissions from rest_framework (IsAuthenticatedOrReadOnly)
  • uses the PostSerializer for serializing the data into JSON
  • get_queryset function returns all the posts data. Also, filter the content based on query passed through URL. `/?q=`
  • prrform_create function saves the logged in users in the owner field of posts table

create a detail view class as:

class PostAPIDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly]
    queryset = Posts.objects.all()
    serializer_class = PostSerializer

The class PostAPIDetail works (/api/posts/<id>) as:

  • inherits RetrieveUpdateDestroyAPIView from generic class of rest_framework
  • inherits built-in permissions from rest_framework (IsAuthenticatedOrReadOnly). Also, inherits custom permissions (IsOwnerOrReadOnly) to make sure that only the user that created a blog post can update or delete it.
  • uses the PostSerializer for deserializing JSON data that is used to save in database

Add pagination

Pagination is used to split the data across several pages. REST framework includes support for customizable pagination styles. This allows you to split large result sets into individual pages of data.

The pagination style may be set globally, using the DEFAULT_PAGINATION_CLASS and PAGE_SIZE setting keys. For example, to use the built-in limit/offset pagination, you would do something like this:

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

Map URLs

Each class in views.py represents an API endpoint such that there are two API endpoints.

Add a new urls.py file in the blog application directory

#blog/urls.py
from django.urls import path
from blog import views

urlpatterns = [
    path('posts/', views.PostAPIList.as_view()),
    path('posts/<int:pk>/', views.PostAPIDetail.as_view()),
]

Since the views are in class-based view, it is converted into function-based view using as_view().

Also include the app URLs in project URLs

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('blog.urls')),
    ...
]

Now you can test the API through browser by starting developing server.

List of all the posts i.e. read only
http://127.0.0.1:8000/api/posts/
(not authenticated)
List of all posts and also write new posts
http://127.0.0.1:8000/api/posts/
(authenticated)
detail view of single post (read only)
http://127.0.0.1:8000/api/posts/1/
(not authenticated)
detail view of single posts with the feature of put and delete
http://127.0.0.1:8000/api/posts/1/
(authenticated)

Wrapping up

In this post, we learn to build the REST API using the Django REST framework.

Here is a GitHub link to code for Building API using Django REST framework.