Simple CRUD Application with Django Rest Framework

Internship at OpenGenus

Get FREE domain for 1st year and build your brand new site

In this article, we have explained the ideas of REST and CRUD and developed a Simple CRUD Application with Django Rest Framework.

Table of contents:

  1. Introduction to REST and CRUD
  2. Time to Setup
  3. Time to Code
  4. Let us Test our code

We will get started now with our Simple CRUD Application with Django Rest Framework.

Introduction to REST and CRUD

Ever wondered how your favorite web applications work? like Twitter, Facebook and so on, Yes they look fabulous but what really makes them tick; Its a little thing known as a REST API; so what is a REST API?

An API is an application programming interface. It is a set of rules that allow programs to talk to each other. The developer creates the API on the server and allows the client to talk to it.

REST determines how the API looks like. It stands for “Representational State Transfer”. It is a set of rules that developers follow when they create their API. One of these rules states that you should be able to get a piece of data (called a resource) when you link to a specific URL.

A REST API communicates with that beautiful front-end you see through HTTP Requests by those specified urls; So you can perform operations like Create a facebook account, Read your facebook posts, Update your facebook password and if you happen to have beef with Mark Zukerberg, Delete your facebook account.

Take note of the actions Create, Read, Update and Delete; they are the very basic rules that makes an API to be considered RESTful, typically to create a resource we would utilize a POST request to a url in our API, GET to Read, PUT to update and DELETE to delete.

Time to Setup

I would advise you to read up on HTTP VERBS, without further ado, lets get our hands a little dirty

First things first, make sure you have python installed on your PC or Mac.

Let's create a folder for our project.

mkdir  simple-todo-application

Open the folder with your favorite code editor and create a requirements.txt in the root folder of simple-todo-application

In your requirements.txt file you should have this

Django==3.1.3
djangorestframework==3.12.2

Before we do any installing, lets create an isolated environment for our project, Its best pratice to do this, because it could get confusing differentiating between the packages you in your global python environment and the one you require in your application, so best to create a virtual environment so all the packages you use are unique to your application. How'd we do that?

Make sure you are in the application folder and run the command you have below:

N.B: If you have python 3 and python 2, you might want to run python3 instead of python if you want to create an isolated environment in python 3

python -m venv ./venv

You should have a ./venv folder in your application folder. Now activate the isolated environment, so all our packages will be installed there, a virtual environment is activated differently for Mac OSX and Windows.

Mac OSX

source venv/bin/activate

Windows

.\venv\Scripts\activate.bat

After activating, you should see (venv) in your terminal, you can install the packages, we stated in the requirements.txt by using the command below:

pip install -r requirements.txt

Next let's bootstrap the django application:

django-admin startproject app

you have the following folders in your application directory:

structure

Time to Code

Next lets create an app for our todo, make sure you cd app before running the command below:

django-admin startapp todo

You should have a new directory todo created and it should have the following files and folder.

    ./todo
        ./migrations
        __init__.py
        admin.py
        apps.py
        models.py
        tests.py
        views.py

Create two additional files serializers.py and urls.py in the directory ./todo.

Don't worry you will know why those two files exist pretty soon.

First, in the ./app/app folder, go to the settings.py and add to INSTALLED_APPS, todo app and rest_framework.

    .
    .
    # Application definition
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'rest_framework', # add this
        'todo' # add this too
    ]

First let's define the model for our todo, Go to the models.py file in the ./app/todo and you should have this below:

    from django.db import models

    class Todo(models.Model):
        title = models.CharField(max_length=255)
        description= models.TextField()
        deadline = models.DateTimeField()
        isCompleted= models.BooleanField(default=False)

A Model basically defines how our database tables are going to be structured, In this tutorial we are not going to focus too much on the database we will use, so we will go by with the default Django sqlite db (isn't django awesome?).

After creating our model, we need to migrate that model to our tables in our db.

First you'd run:

python manage.py makemigrations

This will detect any models created and make the migrations file and then we create the tables by using the command below:

python manage.py migrate

We should have our tables created that is our todo Table and some other default tables that come with django (We will not talk about that now)

If you have an sqlite Explorer, your DB should have the following tables.

tables

We'd also create a serializer for the todo in the serializers.py file in the todo folder.

A Serializer allows us to convert complex data types to native python data types and then convert back to JSON or XML

    from rest_framework import serializers
    from .models import Todo

    class TodoSerializer(serializers.ModelSerializer):
        class Meta:
            model = Todo
            fields = "__all__"

Remember the little intro about REST API, let's create the CRUD endpoints for our todo; so we can create, read, update and delete todos.

In the views.py in your todo folder, lets define what happens when retrieve a request to perform CRUD on our todo.

First, let's add a function to get a single todo from the DB when the user either passes an id or not.

    from django.http.response import Http404
    from django.shortcuts import render
    from rest_framework.views import APIView
    from .models import Todo
    from .serializers import TodoSerializer
    from rest_framework.response import Response

    class TodoAPIView(APIView):

        # READ a single Todo
        def get_object(self, pk):
            try:
                return Todo.objects.get(pk=pk)
            except Todo.DoesNotExist:
                raise Http404

        def get(self, request, pk=None, format=None):
            if pk:
                data = self.get_object(pk)
                serializer = TodoSerializer(data)

            else:
                data = Todo.objects.all()
                serializer = TodoSerializer(data, many=True)

                return Response(serializer.data)
                

Now let's add a function to create a todo.

    .
    .
    def post(self, request, format=None):
        data = request.data
        serializer = TodoSerializer(data=data)

        # Check if the data passed is valid
        serializer.is_valid(raise_exception=True)

        # Create Todo in the DB
        serializer.save()

        # Return Response to User

        response = Response()

        response.data = {
            'message': 'Todo Created Successfully',
            'data': serializer.data
        }

        return response

What if we wanted to change the deadline for a todo, we need to use put inherited from the API view class.

    .
    .
    def put(self, request, pk=None, format=None):
        # Get the todo to update
        todo_to_update = Todo.objects.get(pk=pk)

        # Pass the instance to update to the serializer, and the data and also partial to the serializer
        # Passing partial will allow us to update without passing the entire Todo object
        serializer = TodoSerializer(instance=todo_to_update,data=request.data, partial=True)

        serializer.is_valid(raise_exception=True)
        serializer.save()
        response = Response()

        response.data = {
            'message': 'Todo Updated Successfully',
            'data': serializer.data
        }

        return response


Now for the last operation i.e. deleting a todo.

    .
    .
    def delete(self, request, pk, format=None):
        todo_to_delete =  Todo.objects.get(pk=pk)

        # delete the todo
        todo_to_delete.delete()

        return Response({
            'message': 'Todo Deleted Successfully'
        })

You have your views.py looking like this:

    from django.http.response import Http404
from django.shortcuts import render
from rest_framework.views import APIView
from .models import Todo
from .serializers import TodoSerializer
from rest_framework.response import Response

class TodoAPIView(APIView):

    # READ a single Todo
    def get_object(self, pk):
        try:
            return Todo.objects.get(pk=pk)
        except Todo.DoesNotExist:
            raise Http404

    def get(self, request, pk=None, format=None):
        if pk:
            data = self.get_object(pk)
        else:
            data = Todo.objects.all()

        serializer = TodoSerializer(data, many=True)

        return Response(serializer.data)

    def post(self, request, format=None):
        data = request.data
        serializer = TodoSerializer(data=data)

        serializer.is_valid(raise_exception=True)

        serializer.save()

        response = Response()

        response.data = {
            'message': 'Todo Created Successfully',
            'data': serializer.data
        }

        return response

    def put(self, request, pk=None, format=None):
        todo_to_update = Todo.objects.get(pk=pk)
        serializer = TodoSerializer(instance=todo_to_update,data=request.data, partial=True)

        serializer.is_valid(raise_exception=True)

        serializer.save()

        response = Response()

        response.data = {
            'message': 'Todo Updated Successfully',
            'data': serializer.data
        }

        return response

    def delete(self, request, pk, format=None):
        todo_to_delete =  Todo.objects.get(pk=pk)

        todo_to_delete.delete()

        return Response({
            'message': 'Todo Deleted Successfully'
        })

But we are not done, we'd have to setup routes so that our front-end can communicate with our REST API and create todos.

In the urls.py in our todo folder, we'd define the routes:

    from django.urls import path
    from .views import TodoAPIView

    urlpatterns = [
        path('todo', TodoAPIView.as_view()),
        path('todo/<str:pk>', TodoAPIView.as_view()) # to capture our ids
    ]

We still not done, our front-end can still communicate, so we need to add the urls for our todo to the global urls.py file located in our ./app where we found the settings.py file and we add the todo urls as below:

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

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

Now we can perform CRUD using the endpoint /api/todo.

We have sucessfully crreated a CRUD application with Django Rest Framework, but our work is not complete until we document so our front-end developers are not lost.

Let us Test our code

Make sure you have a REST Client installed either Postman or Insonmia.

Personally I prefer Insomia.

I'd Create a Todo First as shown below:

Create-Todo-1

As you can see, I don't pass the isCompleted as part of my request cause I have already set a default value, when we defined the model, but if you want to overrite the default you can pass it.

I'd add some more todos, Go ahead and create some more as well.

Let's get all the todos you've added.

Get-all-todos

What if we just wanted to one todo, take note of the id field, we would pass part of our routes as localhost:8000/api/todo/{id} as below:

Get-a-Todo

So I think I am done with the task I created for myself to finish writing this tutorial, So I will just go ahead and update the isCompleted to True as shown below:

update-Todo

Okay, Now that's done, we can simply delete a todo, let's say I don't want to do task 3 anymore.

Delete-Todo

Lets get all todos to check if the todo actually got deleted.

Get-Todos-after-Delete

There you go, with this article at OpenGenus, we have successfully created a CRUD application, now go forth and build the next facebook. Cheeers!