Open In App

Function based Views – Django Rest Framework

Improve
Improve
Improve
Like Article
Like
Save Article
Save
Share
Report issue
Report

Django REST Framework allows us to work with regular Django views. It facilitates processing the HTTP requests and providing appropriate HTTP responses. In this section, you will understand how to implement Django views for the Restful Web service. We also make use of the @api_view decorator.

Before working on Django REST Framework serializers, you should make sure that you already installed Django and Django REST Framework in your virtual environment. You can check the below tutorials:

Next, you can create a project named emt and an app named transformers. Refer to the following articles to check how to create a project and an app in Django.

In this section, we will be using PostgreSQL. You have to create a database named emt in PostgreSQL. You can check the below link for installation.

Install PostgreSQL on Windows

Note: if you need to work with SQLite, you can continue using the default database.

To make use of PostgreSQL, we need to install a Python-PostgreSQL Database Adapter (psycopg2). This package helps Django to interact with a PostgreSQL database. You can use the below command to install the psycopg2 package. Make sure the PostgreSQL bin folder is included in the PATH environmental variables, and the virtual environment is activated before executing the command.

pip install psycopg2

Now, you need to configure the PostgreSQL database in your Django project. By default, the database configuration has an SQLite database engine and database file name. You can check the setting.py Python file and replace the default database configuration with the PostgreSQL database configuration.

DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.postgresql',
       'NAME': 'emt',
       'USER': 'username',
       'PASSWORD': 'password',
       'HOST': '127.0.0.1',
       'PORT': '5432',
   }
}

Here, the name refers to the database name, the user and the password refers to the Postgres username and password.

At last, we need to install the httpie package in our virtual environment. We will be composing HTTPie commands for CRUD operation. You can activate the virtual environment and run the below command

pip install –upgrade httpie

Now, let’s create the Model and Serializer.

Creating Model and Serializer

Before getting into class-based views let’s create a model and its serializer, which helps to demonstrate the working of function-based views.

Creating Model

In Django, Models are classes that deal with databases in an object-oriented way. Each model class refers to a database table and each attribute in the model class refers to a database column. Here, we will create a simple Transformer model in Django, which stores the transformer character name, alternate mode, description, and whether the character is alive or dead. We require the following attributes for our Transformer entity:

  • name
  • alternate_mode
  • description
  • alive

In a RESTFul web service, each resource is accessed using a unique URL, which means each employee has their unique URL. And, each method of the model is composed of an HTTP Verb and Scope.

HTTP Verb Scope Semantics URL
GET Transformer Retrieve a single transformer http://localhost:8000/transformers/{id}/
GET Collection of Transformer Retrieve all transformers in the collection http://localhost:8000/transformers/
POST Collection of Transformer Create a new transformer in the collection http://localhost:8000/transformers/
PUT Transformer Update an existing transformer http://localhost:8000/transformers/{id}/
PATCH Transformer Partially update an existing transformer http://localhost:8000/transformers/{id}/
DELETE Transformer Delete an existing transformer http://localhost:8000/transformers/{id}/

Let’s get into the implementation of the Transformer model in Django. You can replace the models.py Python file with the below code:

Python3




from django.db import models
  
class Transformer(models.Model):
    name = models.CharField(max_length=150, unique=True)
    alternate_mode = models.CharField(
        max_length=250,
        blank=True,
        null=True)
    description = models.CharField(
        max_length=500,
        blank=True,
        null=True)
    alive = models.BooleanField(default=False)
  
    class Meta:
        ordering = ('name',)
  
    def __str__(self):
        return self.name


The Transformer model is a subclass of the django.db.models.Model class and defines the attributes and a Meta inner class.  It has an ordering attribute that orders the result in an ascending order based on the name. Let’s make the initial migrations using the below command (Make sure you defined your app (‘transformers.apps.TransformersConfig’) in INSTALLED_APPS in settings.py Python file)

python manage.py makemigrations

Next, apply all generated migrations using the below command:

python manage.py migrate

Applying generated migrations will create a Transformer table in the database.

Creating Serializer

Create a new serializers.py Python file in your app (transformers) folder and add the below code:

Python3




from rest_framework import serializers
from transformers.models import Transformer
  
class TransformerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Transformer
        fields = "__all__"


Here, we created a TransformerSerializer class that inherits from the rest_framework.serializers.ModelSerializer. If you would like to dig deep into different types of serialized, you can check Django REST Framework (DRF) Serialization. Now, it’s time to begin our journey towards function-based Views. We will start with regular Django views and after then we will take advantage of the @api_view decorator.

Function-based Views

Django views facilitate processing the HTTP requests and providing HTTP responses. On receiving an HTTP request, Django creates an HttpRequest instance, and it is passed as the first argument to the view function. This instance contains HTTP verbs such as GET, POST, PUT, PATCH, or DELETE.   The view function checks the value and executes the code based on the HTTP verb.  Here the code uses @csrf_exempt decorator to set a CSRF (Cross-Site Request Forgery) cookie. This makes it possible to POST to this view from clients that won’t have a CSRF token. Let’s get into the implementation process. You can add the below code in the views.py file.  

Python3




from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
  
from transformers.models import Transformer
from transformers.serializers import TransformerSerializer
  
@csrf_exempt
def transformer_list(request):
    """
    List all transformers, or create a new transformer
    """
    if request.method == 'GET':
        transformer = Transformer.objects.all()
        serializer = TransformerSerializer(transformer, many=True)
        return JsonResponse(serializer.data, safe=False)
  
    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = TransformerSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)
  
@csrf_exempt
def transformer_detail(request, pk):
    try:
        transformer = Transformer.objects.get(pk=pk)
    except Transformer.DoesNotExist:
        return HttpResponse(status=404)
  
    if request.method == 'GET':
        serializer = TransformerSerializer(transformer)
        return JsonResponse(serializer.data)
  
    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = TransformerSerializer(transformer, data=data)
  
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)
  
    elif request.method == 'DELETE':
        transformer.delete()
        return HttpResponse(status=204)


Let’s evaluate the code. Here we have two functions.

  • transformer_list()
  • transformer_detail()

transformer_list()

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

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

    if request.method == 'GET':
        transformer = Transformer.objects.all()
        serializer = TransformerSerializer(transformer, many=True)
        return JsonResponse(serializer.data, safe=False)
  • It retrieves all the transformer’s data using the objects.all() method.
  • Serializes the tasks using TransformerSerializer.
  • Then the serialized data is rendered using JsonResponse() and returns the result

Note:  The many=True argument specified serializes multiple Transformer instances.

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

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = TransformerSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)
  • Uses JSONParser to parse the request,
  • Serialize the parsed data using TransformerSerializer,
  • If data is valid, it is saved in the database and returns the rendered result with the status code.

transformer_detail()

The transformer_detail() function is capable of processing three HTTP verbs – GET, PUT, and DELETE. If the verb is GET, then the code retrieves a single transformer 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.

As a next step, we need to route URLs to views. You need to create a new Python file name urls.py in the app (transformers) folder and add the below code.

Python3




from django.urls import path
from transformers import views
  
urlpatterns = [
    path('transformers/',
         views.transformer_list,
         name = 'employee-list'),
    path('transformers/<int:pk>/',
         views.transformer_detail,
         name = 'employee-detail'),
]


Now, let’s update the root URL configuration. You can add the below code to the urls.py file (the same folder that has the settings.py file).  

Python3




from django.contrib import admin
from django.urls import path, include
  
urlpatterns = [
    path('', include('transformers.urls')),
]


Now it’s time to compose and send HTTP requests to test our views.

Let’s create a new entry. The HTTPie command is:

http POST :8000/transformers/ name=”Wheeljack” alternate_mode=”1977 Lancia Stratos Turbo” description=”Always inventing new weapons and gadgets” alive=”False”

Output

HTTP/1.1 201 Created
Content-Length: 152
Content-Type: application/json
Date: Mon, 25 Jan 2021 05:23:10 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "alive": false,
    "alternate_mode": "1977 Lancia Stratos Turbo",
    "description": "Always inventing new weapons and gadgets",
    "id": 7,
    "name": "Wheeljack"
}

Sharing the command prompt screenshot for your reference

Let’s update the alive field of the transformer (id=7) to True. The HTTPie command is:

http PUT :8000/transformers/7/ name=”Wheeljack” alternate_mode=”1977 Lancia Stratos Turbo” description=”Always inventing new weapons and gadgets” alive=”True”

Output

HTTP/1.1 200 OK
Content-Length: 151
Content-Type: application/json
Date: Mon, 25 Jan 2021 05:26:22 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "alive": true,
    "alternate_mode": "1977 Lancia Stratos Turbo",
    "description": "Always inventing new weapons and gadgets",
    "id": 7,
    "name": "Wheeljack"
}

Let’s retrieve all the transformers. The HTTPie command is:

http GET :8000/transformers/

Output

HTTP/1.1 200 OK
Allow: GET, POST, OPTIONS
Content-Length: 629
Content-Type: application/json
Date: Mon, 25 Jan 2021 05:43:36 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

[
    {
        "alive": true,
        "alternate_mode": "1977 Lancia Stratos Turbo",
        "description": "Always inventing new weapons and gadgets",
        "id": 7,
        "name": "Wheeljack"
    },
    {
        "alive": true,
        "alternate_mode": "1979 Porsche 924",
        "description": "Eager and Daring",
        "id": 5,
        "name": "Cliffjumper"
    },
    {
        "alive": true,
        "alternate_mode": "1979 VW Beetle",
        "description": "Small, eager, and brave. Acts as a messenger, scout, and spy",
        "id": 3,
        "name": "Bumblebee"
    },
    {
        "alive": false,
        "alternate_mode": "1979 Freightliner Semi",
        "description": "Optimus Prime is the strongest and most courageous and leader of all Autobots",
        "id": 1,
        "name": "Optimus Prime"
    }
]

Sharing the command prompt screenshot

@api_view 

The @api_view is a decorator in the rest_framework.decorators module, and it is the base class for all the Django REST framework views. We can provide the allowed HTTP verbs as the http_methods_names argument (list of strings) in the @api_view decorator. If the RESTful Web service receives an unsupported HTTP Verb, the decorator returns an appropriate response rather than an unexpected error in Django.     

@api_view(http_method_names=['GET'])

You can replace the views.py file with the below code.

Python3




from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
  
from transformers.models import Transformer
from transformers.serializers import TransformerSerializer
  
@api_view(['GET','POST'])
def transformer_list(request):
    """
    List all transformers, or create a new transformer
    """
    if request.method == 'GET':
        transformer = Transformer.objects.all()
        serializer = TransformerSerializer(transformer, many=True)
        return Response(serializer.data)
  
    elif request.method == 'POST':
        serializer = TransformerSerializer(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','PATCH','DELETE'])
def transformer_detail(request, pk):
    try:
        transformer = Transformer.objects.get(pk=pk)
    except Transformer.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)
  
    if request.method == 'GET':
        serializer = TransformerSerializer(transformer)
        return Response(serializer.data)
  
    elif request.method == 'PUT':
        serializer = TransformerSerializer(transformer, 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 == 'PATCH':
        serializer = TransformerSerializer(transformer,
                                           data=request.data,
                                           partial=True)
  
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors,
                        status=status.HTTP_400_BAD_REQUEST)
  
    elif request.method == 'DELETE':
        transformer.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


In the new code, the transformer_list and transformer_detail functions are wrapped with the @api_vew decorator. The transformer_list function supports GET and POST Verbs, whereas, transformer_detail method supports GET, PUT, PATCH, and DELETE verbs. These supported verbs are passed as decorator parameters for each method.  Here, we removed rest_framework.parsers.JSONParser class. This way we can let the code work with different parsers. We also replaced the JSONResponse class with a more generic rest_framework.response.Response class.

Let’s run the HTTPie command with OPTIONS verb to list the supported methods and verbs in the transformer_list method.

http OPTIONS :8000/transformers/

Output

HTTP/1.1 200 OK
Allow: GET, OPTIONS, POST
Content-Length: 225
Content-Type: application/json
Date: Mon, 25 Jan 2021 16:52:10 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "description": "List all transformers, or create a new transformer",
    "name": "Transformer List",
    "parses": [
        "application/json",
        "application/x-www-form-urlencoded",
        "multipart/form-data"
    ],
    "renders": [
        "application/json",
        "text/html"
    ]
}

Sharing the command prompt screenshot

The output displays the details of the transformer_list method. It allows GET, OPTIONS, and POST HTTP Verbs. It also displays the different parses and renders that the function supports.

Let’s look at the transformer_detail function. The HTTPie command is

http OPTIONS :8000/transformers/1/

Output

HTTP/1.1 200 OK
Allow: OPTIONS, DELETE, GET, PATCH, PUT
Content-Length: 177
Content-Type: application/json
Date: Mon, 25 Jan 2021 16:59:23 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "description": "",
    "name": "Transformer Detail",
    "parses": [
        "application/json",
        "application/x-www-form-urlencoded",
        "multipart/form-data"
    ],
    "renders": [
        "application/json",
        "text/html"
    ]
}

You can notice that the transformer_detail() function supports OPTIONS, DELETE, GET, PATCH, and PUT HTTP verbs. It also displays the different parses and renders that the function supports.

The @api_view decorator can parse different content types by choosing the appropriate parser. From the above output, we can notice the different parsers supported in the @api_view decorator. When we use the @api_view decorator, it automatically makes use of APIView class and its settings. This way we will be able to use the parsers and renders. 

The @api_view decorator helps the Django REST framework to examine the Content-Type header in the data attribute and identifies the exact parser to parse the request. It also invokes the rest_framework.negotiation.DefaultContentNegotiation class to select the suitable renderer for the request. 

Now, it’s time to compose and send different HTTP requests. Let’s create a new entry. The HTTPie command is

http POST :8000/transformers/ name=”Prowl” alternate_mode=”1979 Nissan 280ZX Police Car” description=”Strives to find reason and logic in everything” alive=”False”

Output

HTTP/1.1 201 Created
Allow: GET, POST, OPTIONS
Content-Length: 149
Content-Type: application/json
Date: Mon, 25 Jan 2021 16:18:56 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "alive": false,
    "alternate_mode": "1979 Nissan 280ZX Police Car",
    "description": "Strives to find reason and logic in everything",
    "id": 10,
    "name": "Prowl"
}

Sharing the command prompt screenshot 

Let’s update the alive field value to True. The HTTPie command is:

http PATCH :8000/transformers/10/ alive=”True”

Output

HTTP/1.1 200 OK
Allow: PATCH, PUT, DELETE, OPTIONS, GET
Content-Length: 148
Content-Type: application/json
Date: Mon, 25 Jan 2021 16:22:35 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "alive": true,
    "alternate_mode": "1979 Nissan 280ZX Police Car",
    "description": "Strives to find reason and logic in everything",
    "id": 10,
    "name": "Prowl"
}

Sharing the command prompt screenshot

Let’s retrieve all the entries. The HTTPie command is:

http GET :8000/transformers/

Output

HTTP/1.1 200 OK
Allow: GET, POST, OPTIONS
Content-Length: 739
Content-Type: application/json
Date: Mon, 25 Jan 2021 16:23:52 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

[
    {
        "alive": true,
        "alternate_mode": "1979 Nissan 280ZX Police Car",
        "description": "Strives to find reason and logic in everything",
        "id": 10,
        "name": "Prowl"
    },
    {
        "alive": true,
        "alternate_mode": "1977 Lancia Stratos Turbo",
        "description": "Always inventing new weapons and gadgets",
        "id": 7,
        "name": "Wheeljack"
    },
    {
        "alive": true,
        "alternate_mode": "1979 Porsche 924",
        "description": "Eager and Daring",
        "id": 5,
        "name": "Cliffjumper"
    },
    {
        "alive": true,
        "alternate_mode": "1979 VW Beetle",
        "description": "Small, eager, and brave. Acts as a messenger, scout, and spy",
        "id": 3,
        "name": "Bumblebee"
    },
    {
        "alive": false,
        "alternate_mode": "1979 Freightliner Semi",
        "description": "Optimus Prime is the strongest and most courageous and leader of all Autobots",
        "id": 1,
        "name": "Optimus Prime"
    }
]

Sharing the command prompt screenshot

In this section, we understood how to code Django views to process HTTP Request and returns HTTP Response. We also realized the importance of wrapping our views with the @api_view decorator. Finally, we composed and sent different requests including the OPTIONS HTTP request, which displays the supported HTTP verbs, parsers, and renderers.



Last Updated : 16 Feb, 2021
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads