Building a Django CRUD application in minutes
Tags: django, python
Published: 2020-11-21 10:56:55

To gauge the usefulness of any backend framework, a great litmus test is to see how easy it is to plug in a database to your application and expose the database models to the client via 4 operations - Create, Read, Update and Delete.

This functionality solves a big chunk of your problems, especially if you are building a product that is more front-end heavy and you want to spend as little bandwidth as possible on building your APIs.

# What is CRUD?

An application server is nothing but a layer on top of a database which provides the ability for a client application to perform the following underlying operations.

1. Create a new entry into a table in the database.
2. Read the database table entries either by fetching a list of all entries or retrieving the details of a single entry.
3. Update an existing entry in the database either by changing certain details or by replacing it entirely with a new one.
4. Delete a database table entry either by removing it from the database or by simply marking it as deleted and deactivating it.

Django has ways of doing all of this right out of the box, but I prefer to use a framework written on top of Django called the [Django Rest Framework](https://www.django-rest-framework.org/) which makes things even simpler by providing an intuitive interface to the developers.

# A sample CRUD application

I write all my blog posts on the apple notes app before I edit and publish them and it occurred to me that a note taking app is a great way to [demonstrate a CRUD application](https://github.com/sankalpjonn/django-crud-example).

To build an application like this, we need a database table that stores a list of notes consisting of a Title, Content and the last updated timestamp. The notes are sorted according to the last updated timestamp.

We need the following functions: 

1. Creating a new note with an empty title and content.
2. Updating the newly created note with a title and content.
3. Reading the fields of a note.
4. Partially updating the note by modifying either tile or content.
5. Listing all the notes in the database sorted by last updated timestamp.
6. Deleting a note by marking it as deactivated.

I will be going through all the steps involved in creating this application, but first let's start with the basics.

# Setting up a Django project

There is some basic boilerplate code involved in building any Django application. Here is a summary of all the steps needed to set up the boilerplate.

    # Step 1: create a python3 virtual environment called django_env
    python3 -m venv django_env
    
    # Step 2: Activate the virtual environment
    source django_env/bin/activate
    
    # Step 3: Install all the dependencies.
    pip install django
    pip install djangorestframework
    
    # Step 4: Create a new django project called "notesapp" and enter it. Also create the initial database tables
    django-admin startproject notesapp
    cd notesapp
    python manage.py migrate
    
    # Step 5: Create a new application within your django project called "notes"
    python manage.py startapp notes

# Creating database models

Before writing the CRUD APIs, we must first create a database model on which we wish to perform the CRUD operations. Let us create a model called Note in notes/models.py.

    from django.db import models
    
    class Note(models.Model):
        # both these fields can be empty when you create a new note for the first time
        title = models.CharField(max_length=255, null=True, blank=True)
        content = models.TextField(null=True, blank=True)
    
        # notes will be sorted using this field
        last_udpated_on = models.DateTimeField(auto_now=True)
    
        # to delete a note, we will simply set is_active to False
        is_active = models.BooleanField(default=True)
    
        def __str__(self):
            return self.title

Before we proceed we must update the schema because a new database table needs to be created in the underlying database. This can be done by creating and running migrations

    python manage.py makemigrations
    python manage.py migrate

# Creating the CRUD APIs

Now that the model has been created, we need a way to perform CRUD operations on this model by writing as little code as possible. This is where Django Rest Framework comes in. Not only can you expose CRUD APIs on this model within minutes, but you also get a pretty nifty user interface to view those APIs in action.

To do this, you must first define a serializer for the database model you created in the above step. ‍

## Defining a Serializer

A serializer is responsible for two things

1. To validate an incoming create/update request and reject it if the fields are not in the format that is required to create/update an entry in the database table.
2. To convert a database table entry into a format like JSON that can be transferred over the internet to the client. Let us create a serializer in notes/serializers.py as follows.

&#x200B;

    from rest_framework import serializers
    from .models import Note
    
    
    class NoteSerializer(serializers.ModelSerializer):
        is_active = serializers.BooleanField(read_only=True)
    
        class Meta:
            model = Note
            fields = ('id', 'title', 'content', 'last_udpated_on', 'is_active')

Inside the Meta class, we define which database model is being serialized and what all fields within that model should be serialized. We could choose to include only certain fields from the model that we would like to expose over the API.

We can also define certain fields as read\_only which indicates that these fields will be ignored if present in a create/update request but they will be present in the response a read request.

In this above case, is\_active is responsible for indicating if a note has been deleted or not. Therefore we must not allow it to be modified via a create/update request and it should be set to false only when a delete operation is performed. This will become more clear in the upcoming steps.

We can also define fields as write\_only which will indicate that these fields will not be present in a read response but we will allow the field to be modified in a create/update request. We can now proceed to writing the CRUD APIs

## Defining an API Viewset

In Django, the APIs are written in views.py and each API that does some operation on a certain database resource is a view. In our case we will be performing multiple operations on the database model and hence what we need is a viewset. Here is how you define one in notes/views.py

    from django.shortcuts import render, get_object_or_404
    
    from rest_framework.viewsets import ModelViewSet
    from .models import Note
    from .serializers import NoteSerializer
    
    
    class NoteViewSet(ModelViewSet):
        serializer_class = NoteSerializer
    
        def get_object(self):
            return get_object_or_404(Note, id=self.request.query_params.get("id"))
    
        def get_queryset(self):
            return Note.objects.filter(is_active=True).order_by('-last_udpated_on')
    
        def perform_destroy(self, instance):
            instance.is_active = False
            instance.save()

This viewset is now capable of performing all the CRUD operations and it will use the NoteSerializer to determine how the data will be received and how it will be sent back to the requesting client.

The get\_object is responsible to determine how an object is retrieved. In this case we look for an id field in the query parameters of a a request and use that to retrieve a note.This method is also used while making an update operation because to update a particular note we would have to first retrieve that note.

The get\_queryset method is responsible for determining how a list operation will work. In this case we will be listing all the notes which are still active and sort them in the decreasing order of the last\_udpated\_on timestamp.

The perform\_delete method will determine what to do when a delete operation is requested. In this case we do not want to delete the row from the database and we simply want to deactivate it. Therefore we will be setting the is\_active field to False and saving the note.

## Defining the URL path

We just have one more step to complete before we can see our APIs in action and that is to determine what URL path will invoke the above viewset. This can be done in notesapp/urls.py as follows.

    from django.contrib import admin
    from django.urls import path
    from notes.views import NoteViewSet
    from django.conf.urls import url
    
    urlpatterns = [
        # the admin path is present by default when you create the Django profject. It is used to access the Django admin panel.
        path('admin/', admin.site.urls),
    
        # the URLs for your APIs start from here
        url(r'^note$', NoteViewSet.as_view(
            {
                'get': 'retrieve',
                'post': 'create',
                'put': 'update',
                'patch': 'partial_update',
                'delete': 'destroy'
            }
        )),
        url(r'^note/all$', NoteViewSet.as_view(
            {
                'get': 'list',
            }
        )),
    ]

To explain what is going on here, we are basically saying that if the url /note is called, no matter what the HTTP method is, we will be invoking the NoteViewSet and based on what the HTTP method is we will call a particular method within this viewset.‍

1. A POST call will result in a create operation of the Note model.
2. A GET call will result in retrieving a single Note model object using the id query parameter.
3. A PUT call will result in replacing an existing Note model with a new Note and the existing Note model will be fetched using the id query parameter.
4. A PATCH call will result in modifying only a certain field inside an existing Note model instead of entirely replacing it and the existing Note model will be fetched using the id query parameter.
5. A DELETE call will result in calling a destroy function which internally calls the perform\_destroy method defined above in views.py. In case you haven't noticed, there is one thing missing in this url PATH and that is the ability to list all the existing Note model objects.

To solve this I created another URL path /note/all where I am invoking the same viewset as the above one with the difference being the GET call will now invoke a list method on the viewset instead of a retrieve method which means that the get\_queryset method defined earlier will be invoked.

# See your APIs in action

Okay enough talk, let us now run the server and view the APIs in action. You can run the Django development server using the below command and it will expose your server on the 8000 port on localhost.

    python manage.py runserver

*Please do not forget to include rest\_framework in the INSTALLED\_APPS section of notesapp/settings.py or you will end up seeing weird errors.*

To see our APIs in action, put **http://localhost:8000/note?id=1** in your browser and be greeted by a pretty nifty user interface created by Django Rest Framework to test all your APIs.

You can also test your APIs using curl like this

    # Create a new note
    curl -XPOST 'http://localhost:8000/note' 
    
    # Update a note
    curl -XPUT 'http://localhost:8000/note?id=1' -H 'Content-type: application/json' -d '{"title": "New blog post", "content": "Content of blog post"}'
    
    # Partially update a note
    curl -XPATCH 'http://localhost:8000/note?id=1' -H 'Content-type: application/json' -d '{"title": "Fresh blog post"}'
    
    # Get a note
    curl -XGET 'http://localhost:8000/note?id=1'
    
    # Delete a note
    curl -XDELETE 'http://localhost:8000/note?id=1'

[originally posted on my blog](https://www.sankalpjonna.com/posts/building-a-django-crud-application-in-minutes)