Understanding Django API with Django Rest Framework

RESTful APIs

An API (Application Programming Interface) provides an interface for developers to interact with an application’s database. Instead of just giving someone full access to a database, an API sets up rules, permissions, and endpoints for various functionality: login, logout, reading a list of blogs, individual blog details, and so on.

The traditional way to construct a web API is via REST (Representational State Transfer), a well-established architecture for how websites can communicate with one another. Since computers communicate via the web this means using the HTTP protocol which supports a number of common “methods” (also called “verbs”) such as GET, PUT, POST, and DELETE.

There are also a host of related access codes that indicate whether a request was successful (200), redirected (301), missing (404), or worse (500).

JSON

It’s important to note that since an API is communicating with another computer the information being shared is not what would be sent for a standard web page. When your browser requests, for example, the Google homepage, it sends HTTP requests and receives HTTP responses with HTML, CSS, JavaScript, images, and so on.

An API is different. Typically we’re only interested in the data from a database. This data is often transformed into JSON format to be efficiently transmitted about. The API will also have a series of well-defined rules for how a frontend client can interact with it via a REST architecture. To register a new user the frontend framework will need access an API endpoint called, for example, /api/register. This API endpoint contains both a specific URL route and its own set of permissions.

Setup

First create a new directory on your computer for our code. I’ll place it on the Desktop in a folder called api but you can place it anywhere. Then configure our project.

$ cd ~/Desktop
$ mkdir blogapi && cd blogapi
$ source env/Scripts/activate
$ pip install django==3.0
(blogapi) $ django-admin startproject blog_project .
(blogapi) $ python manage.py startapp posts

We’re using source/env/Scripts/activate to activate a local environment and pip to install Django . Then we create a new Django project called blog_project as well as our first app posts.

We need to tell Django about the new app we have created. So make sure to add posts to our list of INSTALLED_APPS in the settings.py file.

# blog_project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'posts',
]

Let's create our database model with basically four fields: title, content, created_at, and updated_at.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=50)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

Note that we’re also defining what the__str__ representation of the model should be which is a Django best practice.

Let's update our database by first creating a new migration file and then applying it.

(blogapi) $ python manage.py makemigrations
(blogapi) $ python manage.py migrate

To view our data in Django’s excellent built-in admin app we add Post model to it as follows.

# posts/admin.py
from django.contrib import admin
from . models import Post

admin.site.register(Post)

Then create a superuser account so we can login to the admin . Type the command below and enter all the prompts. the email prompt is optional, but the rest of the prompts is required.

(blogapi) $ python manage.py createsuperuser

Now we can start up the local web server.

(blogapi) $ python manage.py runserver

Navigate to localhost:8000/admin and login with your superuser credentials. admin1.png

Click on “+ Add” button next to Posts and enter in some new content. admin-add.png

I created three new blog posts that you can see here.

admin-list.png

we’re done with the Django part! we don't need to create templates and views since we are creating API. Instead it’s time to add Django Rest Framework to take care of transforming our model data into an API.

Django Rest Framework(DRF)

DRF takes care of the heavy lifting of transforming our database models into a RESTful API. There are two main steps to this process: first a serializer is used to transform the data into JSON so it can be sent over the internet, then a View is used to define what data is sent.

checkout this article Django REST Framework Serializer. if you don't about Django Serializer or want's to know more about Django Serializer.

First stop the local server with Control+c and use pip to install Django Rest Framework.

(blogapi) $ pip install djangorestframework==3.10

Then add it to the INSTALLED_APPS section of our settings.py file.

# blog_project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',

    'posts',
]

Now let's create a new serializers.py file in our posts app.

(blogapi) $ touch posts/serializers.py

Recall that the serializer is used to convert our data into JSON format. Here’s how it looks like.

# posts/serializers.py
from rest_framework import serializers
from .models  import Post


class PostSerializer(serializers.ModelSerializer):

    class Meta:
        model = Post
        fields = ('id', 'title', 'content', 'created_at', 'updated_at',)

On the top two files we’re importing serializers from DRF and our models. Next, we create a serializer class and create a Meta class within it. The fields control which database attributes are available. In this case we’re exposing all our fields including id which is the primary key Django automatically adds to all database records.

Next we need to create our views. Just as Django has generic class based views, so too DRF has generic views we can use. Let’s add a view to list all blog posts and a detail view for a specific post.

Creating the View using Class base approach

Update the views.py file in posts as follows.

# posts/views.py
from rest_framework import generics

from .models import Post
from .serializers import PostSerializer


class PostList(generics.ListAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer


class PostDetail(generics.RetrieveAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

At the top we import generics from DRF as well as our models and serializers files. Then we create two views: PostList and PostDetail. Both are just for GETs however RetrieveAPIView is for a single instance of a model. The complete list of generic views generic views is on the DRF site.

Creating the View using Function base approach

Update the views.py file in posts as follows.

# posts/views.py

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer



@api_view(['GET', 'POST'])
def PostList(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = PostSerializer(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 PostDetail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        post = Post.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = PostSerializer(post,many=False)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = PostSerializer(post, 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':
        post.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

The PostList() function is capable of processing two HTTP verbs – GET and POST.

If the verb is GET, the code retrieves all the Post instances.

but, If the verb is POST, the code creates a new Post. Here the data is provided in JSON format while composing the HTTP request.

The PostDetail() function is capable of processing three HTTP verbs – GET, PUT, and DELETE. If the verb is GET, then the code retrieves a single Post instance based on the primary key. If the verb is PUT, the code updates the instance and save it to the database, and if it is DELETE, then the code deletes the instance from the database.

The final piece is urls. We need to create the url routes–known as endpoints in an API–where the data is available.

Start at the project-level urls.py file.

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


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

We’ve added include to the second line of imports and then created a path called api/ for our posts app.

Next we create our posts app urls.py file.

(blogapi) $ touch posts/urls.py

And then include the code below.

# posts/urls.py
from django.urls import path

from .views import PostList,PostDetail

urlpatterns = [
    path('', PostList.as_view()),
    path('<int:pk>/', PostDetail.as_view()),
]

Note the above url.py is for View created with the class base approach. for urls.py created with function base approach, include the code below.

# posts/urls.py
from django.urls import path

from .views import PostList,PostDetail

urlpatterns = [
    path('', PostList,name = 'post-list'),
    path('<int:pk>/', PostDetail,name = 'post-detail'),
]

All blog routes will be at api/ so our PostList which has the empty string '' will be at api/ and postDetail at api/# where # represents the primary key of the entry. For example, the first blog post has a primary id of 1 so it will be at the route api/1, the second post at api/2, and so on.

Browsable API

Time to view our work and check out a DRF killer feature. Start up the server.

Then go to http://127.0.0.1:8000/api/.

post-list.png

Check out that! The api/ endpoint displays all three of my blog posts in JSON format. It also shows in the header that only GET, HEAD, OPTIONS are allowed. No POSTing of data.

Now go to http://127.0.0.1:8000/api/1/ and you’ll see only the data for the first entry.

post-detail.png

Implementing CRUD

I promised at the beginning of the article that this tutorial would cover not just reading/getting content but the full CRUD syntax. Watch how easy DRF makes it to transform our API into one that supports CRUD!

Open up the posts/views.py file and change class PostDetail(generics.RetrieveAPIView) to class PostDetail(generics.RetrieveUpdateDestroyAPIView).

Now refresh the page at http://127.0.0.1:8000/api/1/ and you can see updates in our graphical UI.

post-crud.png

You can use the “Delete” button to delete content, “Put” to either update content, and “Get” to retrieve it as before. For example, navigate to the URL endpoint for our third post: http://127.0.0.1:8000/api/3/.

Then use the graphical interface for PUT at the bottom of the page to create a new entry.

post-update.png

And you can GET it after making the change: post-update.png