loading...

Building a Django CRUD application in minutes

sankalpjonna profile image CH S Sankalp jonna Originally published at sankalpjonna.com ・7 min read

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 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.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

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')
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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',
        }
    )),
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

originally posted on my blog

Discussion

pic
Editor guide