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
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/
and visit http://127.0.0.1:8001/rest-auth/login/
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 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
Very nice. I got my solution here.