How to building a CRUD app using Django REST Framework

·

9 min read

Django REST Framework (DRF) is a django framework that allows developers to quickly and easily build APIs.
Today we'll be using DRF to build a movies review app.
Before we dive into the specifics of the Movie Review App, let's briefly recap what CRUD operations entail:

  • Create: This operation involves adding new data entries to the system. In the context of the Movie Review App, users can create new reviews for movies they have watched.

  • Read: Reading refers to retrieving and viewing existing data from the system. Users can read information about movies, including their titles, descriptions, and reviews.

  • Update: Updating allows users to modify existing data entries. In the Movie Review App, users may want to update their review scores or edit the content of their reviews.

  • Delete: Deleting involves removing data entries from the system. Users might decide to delete their reviews if they are no longer relevant or accurate.

Now that we have a clear understanding of CRUD operations, let's explore how they are implemented in the Movie Review App.

Step 1:
Create a folder to hold the project. Navigate to the folder in the terminal.

Step 2: Create the virtual environment
We will create a virtual environment to isolate dependencies. Run the following code snippet.

python -m venv venv

After creating the virtual environment, we have to activate it:

source ./venv/bin/activate

After running the code snippet above, the terminal prompt will now include "(venv)". This means the virtual environment is active.

Please note, the above code snippet works for users using Unix-like operating systems.

Step 3: Install django and django rest framework.

pip install django djang_rest_framework

Step 4: Create a project

django-admin startproject core .

We call our project "core" because this is where the core code of our project will live. We add the fullstop at the end to tell django-admin to create the project in the folder we are currently in (which is the folder we created earlier.)

Step 5: Create an app

django-admin startapp api

We call our app "api" because this is where our api code will live.

Now, navigate to your favourite IDE and we can start developing the app.

Step 6: Setting up Django REST Framework

To make sure we can use our apis we need to do a little more set up django rest framework. Navigate to the following files and add the following code snippets:

# core/urls.py
INSTALLED_APPS = [
    'rest_framework',
    'api'
]
# core/urls.py
from django.contrib import admin
from django.urls import path, include
from api import urls

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

Now we are ready to start building our APIs.

Step 7: Create the models
The model is the data that we want our app to handle. For instance, we want our users to be able to register and log in, create movies, create reviews, and add movies as their favourites. So we need to create a User model, a Movie model, a Review model, and a Favourites model.

# api/models.py
from django.db import models


class Movies(models.Model):
    title = models.CharField(max_length = 255)
    release_year = models.IntegerField(null = True, blank = True)
    director = models.CharField(max_length = 255, null = True, blank = True)
    plot = models.TextField(null = True, blank = True)
    poster_url = models.CharField(max_length = 255, null = True, blank = True)

    def __str__(self):
        return self.title

class Users(models.Model):
    username = models.CharField(max_length = 50)
    email = models.EmailField(max_length = 100)
    password_hash = models.CharField(max_length = 255)
    registration_date = models.DateTimeField(auto_now_add = True)

    def __str__(self):
        return self.username

class Reviews(models.Model):
    movie = models.ForeignKey(Movies, on_delete = models.CASCADE)
    user = models.ForeignKey(Users, on_delete = models.CASCADE)
    rating = models. DecimalField(max_digits = 3, decimal_places = 1)
    review = models.TextField()
    review_date = models.DateTimeField(auto_now_add = True)

class Favourites(models.Model):
    user = models.ForeignKey(Users, on_delete = models.CASCADE)
    movie = models.ForeignKey(Movies, on_delete = models.CASCADE)

Next we create a serializer.

Step 8: Create the serializer
A serializer is the component responsible for converting complex data types (such as Django model instances) into native Python data types that can be easily converted into JSON. Why JSON? Because this is the format in which the data will be transmitted in the http request and responses.

In the api folder create a file called serializers.py. All your serializers will live in this file. Add the following code snippet.

# api/serializer.py
from rest_framework import serializers
from .models import MoviesI

class MoviesSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movies
        fields = ["title", "release_year", "director", "plot", "poster_url"

The serializer defines the structure of the output data returned by the API. They specify which fields from the database models should be included in the response and how those fields should be formatted. In the code snippet above we are specifying that we will be using the Movies model and the fields of the output data will be title, release year, director, plot, and poster url.

Step 9: Migrate the models
Django comes with an ORM (Object Relational Mapper) built in. An ORM is a program that allows us to interact with a database using our favourite programming language instead of SQL. So instead of writing SQL to create our tables, we use regular python and the Django ORM writes the SQL for us.
At this point we have created our tables (in the form of models) and now we need to instruct the Django ORM to use our models to create the tables using SQL.
We do that by running two commands:

python3 manage.py makemigrations
python3 manage.py migrate

'makemigrations' writes the SQL and 'migrate' executes the SQL.

Step 10: Create the super user
Run the following code snippet to create the super user

python3 manage.py createsuperuser

Follow the prompts provided.
Before using doing this, you must have migrated your project, otherwise the superuser database will not be created.
Creating a superuser is essential for easy management of database records, user authentication, testing, debugging, and content management of the web application.

Step 11: Get functionality
In views.py, add the following code snippet:

# api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Movies
from .serializers import MoviesSerializer


class MoviesApiView(APIView):    
    #lists all the movies
    def get(self, request):
        movies = Movies.objects.all() #1
        serializer = MoviesSerializer(movies, many = True) #2
        return Response(serializer.data, status = status.HTTP_200_OK)

We create a class called MoviesApiView which inherits from the APIView class.

#1 - we query the Movies model to return all the records using all()

#2 - we use the MoviesSerializer to convert the model instances (held in the movies variable) into json and store it in the serializer variable

#3 - we return the serialized data (i.e serializer.data) and the status code to the user through the Response object.

Step 12: Add the view to urls.py

# api/urls.py
from django.urls import path
from .views import (
    MoviesApiView, #1
)

urlpatterns = [
    path('movies/', MoviesApiView.as_view()) #2
]

Once we've written the logic that handles the get request, we need to attach it to a particular endpoint.

#1 - we import the MoviesApiView class

#2 - we connect the 'movies/' endpoint with the MoviesApiView class. Now, whenever we get a request through this endpoint, the method in the MoviesApiView class will handle it.

Step 13: Post functionality

# api/views.py

#users can create a movie
    def post(self, request):
        data = { #1
            'title': request.data.get('title'),
            'release_year': request.data.get('release_year'),
            'director': request.data.get('director'),
            'plot': request.data.get('plot'),
        }

        serializer = MoviesSerializer(data = data) #2

        if serializer.is_valid(): #3
            serializer.save()
            return Response(serializer.data, status = status.HTTP_201_CREATED)
        return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

#1 - we separate the data in the Request object into its constituents in a dictionary.

#2 - we convert the data from json to native python data types using the MoviesSerializer.

#3 - we check if the serialized data is valid. If it is, save it to the database (using save()). If it's not return an error through the Response object.

Step 14: Add the the view to urls.py
The 'movies/' endpoint we added earlier is also used to handle the post request.

Step 15: Get a particular data item functionality

# api.views.py
class MoviesDetailApiView(APIView):
    #helper method to get an object given the movie_id
    def get_object(self, movie_id): #1
        try:
            return Movies.objects.get(id = movie_id)
        except Movies.DoesNotExist:
            return None

    #retrieve movie object given movie_id
    def get(self, request, movie_id):
        movie_instance = self.get_object(movie_id) #2

        if not movie_instance: #3
            return Response(
                {"res": "Object with movie id does not exist"},
                status = status.HTTP_400_BAD_REQUEST
            )
        serializer = MoviesSerializer(movie_instance)
        return Response(serializer.data, status = status.HTTP_200_OK)

#1 - this is a helper function that retrieves a particular database record given its id.

#2 - we pass in the movie_id to the helper function and it returns that particular movie object. The movie object is then stored in the variable "movie_instance"

#3 - if there is data in the movie_instance variable, we serialize the data and return it. If there is no data we return an error.

Step 17: Add the view to urls.py

urlpatterns = [
    path('movies/', MoviesApiView.as_view()),
    path('movie/<int:movie_id>/', MoviesDetailApiView.as_view()),
]

The movie_id we require in the get method and the helper function comes through the url.

Step 18: Update functionality

#update a movie object given movie_id
    def put(self, request, movie_id):
        movie_instance = self.get_object(movie_id) #1

        if not movie_instance:
            return Response(
                {"res": "Object with movie id does not exist"},
                status = status.HTTP_400_BAD_REQUEST
            )

        data = { #2
            'title': request.data.get('title'),
            'release_year': request.data.get('release_year'),
            'director': request.data.get('director'),
            'plot': request.data.get('plot')
        }

        serializer = MoviesSerializer(instance = movie_instance, data = data) #3

        if serializer.is_valid(): #4
            serializer.save()
            return Response(serializer.data, status = status.HTTP_200_OK)
        return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

#1 - we use the helper function to retrieve a particular record and store it in the movie_instance variable.

#2 - we separate the data in the Request object into its constituents in a dictionary.

#3 - we use the MovieSerializer to overwrite all the data in the movie_instance variable with the data from the request.

#4 - we check if the serialized data is valid. If it is we save it to the database and return the data to the frontend. If it's not, we return an error.

Step 19: Add the view to urls.py
The 'movies/<int:movie_id>/' endpoint we added earlier is also used to handle the put request.

Step 20: Delete functionality

#delete a movie object given movie_id
    def delete(self, request, movie_id):
        movie_instance = self.get_object(movie_id) #1

        if not movie_instance:
            return Response(
                {"res": "Object with movie id does not exist"},
                status = status.HTTP_400_BAD_REQUEST
            )

        movie_instance.delete() #2

        return Response(
            {"res": "Object deleted!"},
            status = status.HTTP_200_OK
        )

#1 - we use the helper function to retrieve a particular record and store it in the movie_instance variable.

#2 - use the delete() method to delete the record

Step 16: Test APIs in the browser
In the terminal run "python3 manage.py runserver". Then go to the browser and test if you can create, read, update, and delete data using the apis we just created.