30Jan
Building Rest API With Django Using Django Rest Framework and Django Rest Auth
Building Rest API With Django Using Django Rest Framework and Django Rest Auth

Introduction:

Building Rest API seems to be complex when trying to achieve this on your own with Django,  thanks to the Django Rest framework project which has come to reduce the complexity.
According to the Django Rest framework website:

“Django REST framework is a powerful and flexible toolkit for building Web APIs.”

Here are some flexibilities Django rest framework offers as specified on their official website:

  • The Web browsable API is a huge usability win for your developers.
  • Authentication policies including packages for OAuth1a and OAuth2.
  • Serialization that supports both ORM and non-ORM data sources.
  • Customizable all the way down – just use regular function-based views if you don’t need the more powerful features.
  • Extensive documentation, and great community support.

Prerequisite for this tutorial:

  • You have python installed on your local machine
  • Basic knowledge of python
  • Basic knowledge of Django

Having fulfilled the above requirement for this tutorial let’s now proceed. In this article, we will be building a simple blog API using Django Rest Framework and will add to our API authentication using Django Rest Auth which is design to work well with REST.

Creating a Python environment for our project

There is a need to create an environment for this project in other not to conflict with the packages we want to use with other projects using the default python environment.  This ensures that what even we install on our blog API is unique to the project. So let’s run the below command:

$ python -m venv venv

This creates a folder venv containing our environment. Note that you can give any name to your environment aside venv. Here I decided to call it venv. Next, we need to ensure that this new environment is active, which is necessary because we don’t want any package we install to affect the packages in the root python environment on the system. Activate this environment by  running the below command :

$ . venv/bin/activate
The activated environment on terminal
The activated environment on terminal

Notice the (venv) on the terminal, it indicates that I am on the newly created environment and what even the python or pip command I run will be targeted on this environment.

Installing Django in our environment

Now that we have our environment, we need to install Django in it, we can install any version of Django and it will not in any way affect the versions in other projects we have on our local machine.  let’s install Django by running the following commands. This will install the latest version of Django in our environment.

$ pip install django

Creating our project

We will create our new project using the django-admin commands. Note that if the above process failed the django-admin command will not be available for use.  Now that Django is installed let us now set up our project for our blog by running the below Django command on the terminal,

$ django-admin startproject blog_api

Navigate into the just created project folder blog_api.

$ cd blog_api

Inside this folder, we will still have another blog_api folder and manage.py file

Installing other packages needed

Lets now proceed to install the packages we need for this tutorial which is Django Rest framework and Rest Auth packages.

Installing Django Rest Framework

We will start by installing and configuring our Django Rest Framework.

$ pip install djangorestframework

lets now add  ‘rest_framework’ to your INSTALLED_APPS setting.

INSTALLED_APPS = [   
 ...    
 'rest_framework',
]

Installing Django Rest Auth

The next package we will be installing is Django rest auth, and this will be done by running the following command

$ pip install django-rest-auth

and also add ‘rest_auth’ to your INSTALLED_APPS

INSTALLED_APPS = [
    ...,
    'rest_framework',
    'rest_framework.authtoken',
    ...,
    'rest_auth'
]

add all URL made available by django-rest-auth to our urlpatterns list in urls.py :

from django.conf.urls import url, include

urlpatterns = [
    ...,
    url(r'^rest-auth/', include('rest_auth.urls'))
]

For handling registration for our blog, we will opt into using the Django Rest Auth registration, this registration app depends on another package call django-allauth. Django-allauth is a package with great support for authentication, Rest auth depends on it to provide REST ready endpoints for its rich features.

We will install it by running the following command on the terminal:

$ pip install django-rest-auth[with_social]

Let’s updated our INSTALLED_APP to include the newly added package.

INSTALLED_APPS = [
    ...,
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'rest_auth.registration',
]

SITE_ID = 1

Then we add our rest_auth.registration URLs in url.py

urlpatterns = [
    ...,
    url(r'^rest-auth/', include('rest_auth.urls')),
    url(r'^rest-auth/registration/', include('rest_auth.registration.urls'))
]

Finally, let’s run the migration in other for the models that come with Django Rest auth and all-auth to be added to our database. For our database, we will be using sqlite3 as already configured with Django.

$ python manage.py migrate

let’s add an email backend to our app in our settings.py so as to prevent rest-auth from throwing an error when trying to send an email on successful registration.

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Here we use the console EmailBackend, this email back-end logs the email message to the console just as its name implies. Also, add a little configuration to rest framework to use token authentication

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}

let’s start our server so we can see what we have already setup.

$ python manage.py runserver

Django will attempt starting this server on port 8000, but you can specify a different port if port 8000 is in use as Django will want to make use of this port by

$ python manage.py runserver 8001

The advantage the Django Rest framework offers us is that it provides us with browseable API, which means we can actually test our API directly from the browser without any third-party API testing tool.

Visit http://127.0.0.1:8001/rest-auth/registration/

Register endpoint browser API preview
Register endpoint browser API preview

and visit http://127.0.0.1:8001/rest-auth/login/

Login endpoint browser API preview
Login endpoint browser API preview

Create our blog AppIn Django, every section of a project is called an app, so will now create the blog app by running the below commands

$ django-admin startapp blog

This will create our blog app structure in a folder called blog.
Now we modify our models.py that resides in the blog app to add models that will represent the database tables for our blog.

from django.db import models
from django.contrib.auth.models import User
# Create your models here.

class Post(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    user = models.ForeignKey(User, on_delete=models.CASCADE)

class Comment(models.Model):
    content = models.TextField()
    user = models.ForeignKey(User,  on_delete=models.CASCADE)
    post = models.ForeignKey(Post,  on_delete=models.CASCADE)

Django is not yet aware of our new app, add our blog app to INSTALLED_APP

INSTALLED_APPS = [
    ...,
    'blog',
]

Now that Django is aware of our app, let’s generate a migration for our blog app by running the below command in the root directory of our project.

$ python manage.py makemigrations blog

Now we want to ensure that only authenticated users can create posts and comments by adding the below code to our app REST_FRAMEWORK  dictionary in settings.py

REST_FRAMEWORK = {
    ....
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ]
}

Django provides us with serializer classes to help prepare our data as JSON responses, so we are going to create serializers for our blog models, create a file serializers.py in blog folder and add the following content

from rest_framework import  serializers
from django.contrib.auth.models import User
from .models import Post, Comment

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ['url', 'username', 'email', 'is_staff']

class PostSerializer(serializers.HyperlinkedModelSerializer):
    user = UserSerializer(read_only=True)
    class Meta:
        model = Post
        fields = ['url','title','user', 'content']

class CommentSerializer(serializers.ModelSerializer):
    user = UserSerializer(read_only=True)

    class Meta:
        model = Comment
        fields = [ 'content', 'user']

Here we are extending the HyperlinkedModelSerializer class from our Django Rest Framework for the UserSerializer and PostSerializer, this will help generate a URL as part of the serialized data for all responses. For CommentSerializer we extend the ModelSerializer. The classes extending the ModelSerializer serializes data with the model’s o on the database. Here we now override the Meta class to provide the model we want to serialize and the database fields we want to expose in our API responses. For exposing user in our PostSerializer and CommentSerializer, we tell Django rest framework to generate our user using the UserSerializer class and it should only be read and not written:

user = UserSerializer(read_only=True)

This automatically maps the user_id on the model to the user model since the column is a foreign key to the user that created the post. So rest framework handles this for us too.

Now let’s update our blog views.py

let’s import the modules we need in our views.py to create the views necessary for our API

from django.shortcuts import render
from rest_framework import viewsets, permissions
from .serializers import UserSerializer, PostSerializer, CommentSerializer
from . import models
from rest_framework.response import Responsefrom rest_framework.decorators import action, api_view, permission_classes

We will expose our user’s CRUD by creating a UserViewSet that inherit from ModelViewSet class from our Rest framework.

class UserViewSet(viewsets.ModelViewSet):
    queryset = models.User.objects.all()
    serializer_class = UserSerializer

Here we provide the queryset for which the viewset should get the user objects from as well as the serializer class the view will use to serialize our response. Notice we are inheriting from ModelViewSet of the Django Rest Framework, which has been implemented to interact with our model out of the box.

class PostViewSet(viewsets.ModelViewSet):
    queryset = models.Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    def create(self, request):
        serializer = PostSerializer(
            data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True) # check all fields is valid before attempting to save
        serializer.save(user=request.user)
        return Response(serializer.data)



We did the same thing for the PostViewSet but with a little adjustment, we override the create method to give it our custom behavior to enable us to pass the user that created the post to the serializer.

Now we want to be able to create and get comments for our post, so we add a new method to our PostViewSet

    @action(detail=False, methods=['POST','GET'])
    def comments(self, request, pk):
        post = models.Post.objects.get(pk=pk)
        if request.method == 'GET':
            self.serializer_class = CommentSerializer
            queryset = models.Comment.objects.filter(post=post);
            serializer = CommentSerializer(queryset,many=True, context={'request':request})
            return Response(serializer.data)
         else:
            self.serializer_class = CommentSerializer
            queryset = models.Comment.objects.filter(post=post);
            serializer = CommentSerializer(data=request.data, context={'request':request})
            serializer.is_valid(raise_exception=True)
            serializer.save(user=request.user,post=post)
            return Response(serializer.data)

In the action @action(detail=False, methods=[‘POST’, ‘GET’]), we specified that this method will handle only POST and GET requests. Notice that we are passing request the context value to our serializer, this is to enable the serializer to generate the URL we requested for in our serializer. if this is not provided, an exception will be thrown. Also, we looked out for the method (  if request.method == ‘GET’:) that is used for the request and handle the process accordingly.

We also want to be able to delete comment made, so add an additional method remove_comments

  
    @action(detail=False, methods=['DELETE'])
    def remove_comment(self, request, pk, comment):
        comment = models.Comment.objects.get(pk=comment)
        if comment.delete():
            return Response({'message':'Comment deleted'})
        else:
            return Response({'message':'unable to delete comment'})

Let’s make available our endpoints by creating a urls.py file in our blog folder and add the following content :

from django.urls import path, include
from django.conf.urls import url
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'posts', views.PostViewSet)


urlpatterns = [
    path('', include(router.urls)),
    url(r'posts/(?P<pk>\d+)/comments/$', view=views.PostViewSet.as_view({'get':'comments', 'post':'comments'})),
    url(r'posts/(?P<pk>\d+)/comments/(?P<comment>\d+)/$', view=views.PostViewSet.as_view({'delete':'remove_comment'}))
]

We created our URL endpoints for our blog app, by registering our viewsets in our urlpatterns array.
Here we are using DefaultRouter class from Django,  And then manually register our comments endpoint to get, create, and delete them by binding the URL to the views that handle it.

Finally, let’s make this available for our project by registering the URLs to our project urls.py.

urlpatterns = [
    ...
    url(r'^', include('blog.urls')),
]

We now run our project again to test out our endpoints

$ python manage.py runserver 8001

Let’s try out our posts endpoint http://127.0.0.1:8001/posts/

Get Post endpoint browser API preview

Get Post endpoint browser API previewOther available endpoints generate for posts by our viewset include
http://127.0.0.1:8001/posts/ (POST,GET)
http://127.0.0.1:8001/posts/:pk (GET, PUT, DELETE)
http://127.0.0.1:8001/posts/:pk/comments (GET,POST)
http://127.0.0.1:8001/posts/:pk/comments/:comment_id (DELETE)
its generate similarly for Users the following
http://127.0.0.1:8001/users/ (POST,GET)
http://127.0.0.1:8001/users/:pk (GET, PUT, DELETE)

pk here represent the primary key of the object you want to retrieve it data

Conclusion

In this article, we have been able to create a simple API with authentication and registration using the Django Rest Framework and Rest Auth. We have seen how easy Django rest Framework help use create API, Django rest auth helping us add an API ready authentication system.
we can build APIs rapidly in easily with d=Django rest framework and Django rest auth without much load of stress.
You can  access the source code for this article at Github.com

One Reply to “Building Rest API With Django Using Django Rest Framework and Django Rest Auth”

  1. Very nice. I got my solution here.

Leave a Reply