Creating, Reading, Updating, and Deleting (CRUD) Data in Django: A Function-Based Approach

Prosenjeet Shil
10 min readMay 25, 2024

--

Screenshot of Todo App

Django is a powerful Python web framework that simplifies web development by providing a clean and pragmatic design. One of the most common tasks in web development is creating CRUD (Create, Read, Update, Delete) functionality for your application. In this article, we’ll explore how to create a Django CRUD project using function-based views.

Prerequisites

Before we dive into building our CRUD project, make sure you’ve basic understanding of Python, Django, HTML, Jinja template and Bootstrap.

Setting up your Django Project

To grasp the concepts covered in this tutorial, we’ll be using Windows to demonstrate Django CRUD Operations by building a basic ‘todo’ app.

Begin by creating a new directory to house your project, then navigate into it.

Once inside the directory, set up virtualenv, a utility for establishing segregated Python environments.

pip install virtualenv
virtualenv venv
venv\scripts\activate

The command pip install virtualenv installs the necessary tool. virtualenv venv generates a virtual environment named venv. Finally, venv/scripts/activateactivates the virtual environment, enabling isolated Python development within it.

Ensure you have Python installed on your system. You can install Django using pip:

pip install django

Let’s start by creating a new Django project and a new app within that project. Open your terminal and run the following commands:

django-admin startproject todoproject
cd todoproject
python manage.py startapp todoapp

We’ve created a new project named “todoproject” and an app named “todoapp.”

Folder Structure

Our final folder structure after completing the CRUD application will look like:

TODO


├── todoproject/
│ │
│ ├── templates/
│ │ ├── layout.html
│ │ └── todoapp/
│ │ ├── todo.html
│ │ ├── show.html
│ │ └── confirmation.html
│ │
│ ├── todoapp/
│ │ ├── migrations/
│ │ │ └── (auto-generated migration files)
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── forms.py
│ │ ├── models.py
│ │ ├── tests.py
│ │ └── views.py
│ │
│ ├── todoproject/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ │
│ ├── db.sqlite3 (SQLite database file)
│ │
│ ├── manage.py
└── venv/ (virtual environment folder)

Application Registration: you need to configure in your settings.py file

Make sure your app (todoapp) is included in the INSTALLED_APPS list:

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todoapp',
]

Defining Models

In Django, models are Python classes that define the structure of your database tables. For our CRUD project, we want to manage a list of todos. Create a model for the orders in todoapp/models.py:

from django.db import models

# Create your models here.
class Todo(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
completed = models.BooleanField(default=False)
  • Todo is the name of the model.
  • title is a CharField with a maximum length of 100 characters, suitable for storing short text.
  • description is a CharField with a maximum length of 100 characters for brief task descriptions.
  • created_at is a DateTimeField that automatically sets the current date and time when a new Todo item is created.
  • completed is a booleanField with a default value of False, indicating tasks start as not completed by default.

Now, it’s time to create the database tables for our models. Run the following commands to create the migrations and apply them:

python manage.py makemigrations
python manage.py migrate

Creating Forms

Forms in Django are used to handle user input and validate data. For our Todo application, we will create a form to add and update Todo items. This is done using Django’s forms module and the ModelForm class, which automatically generates a form based on a model. You can define the form in todoapp/forms.py:

from .models import Todo
from django import forms

class TodoForm(forms.ModelForm):
class Meta:
model = Todo
fields = '__all__'

label = {
"title" : "Title",
"description" : "Description",
}

widgets ={
"title" : forms.TextInput(attrs={"placeholder":"Buy Groceries"}),
"description" : forms.TextInput(attrs={"placeholder":"Visit super market and buy some groceries"}),
}
  • We import the forms module from Django and the Todo model from our models file
  • The TodoForm class inherits from forms.ModelForm. This class will generate a form based on the Todo model.
  • The Meta class defines metadata for the form:
    - model: Specifies the model to base the form on, in this case, Todo.
    - fields: Specifies which fields to include in the form. Using '__all__' includes all fields from the Todo model.
    - labels: Provides custom labels for the form fields.
    - widgets: Customizes the HTML widgets for the form fields, adding placeholders to the input elements for better user experience.

Creating Func-Based Views

Function-based views are a simple and straightforward way to handle CRUD operations in Django. In this example, we’ll create views for creating, reading, updating, and deleting todos.

  1. Create a todo (Create View) In todoapp/views.py, define a view function for creating a new todo:
from django.shortcuts import render, redirect
from .forms import TodoForm
from .models import Todo

# Create your views here.

def createTodoView(request):
form = TodoForm
if request.method == "POST":
form = TodoForm(request.POST)
if form.is_valid():
form.save()
return redirect("show_url")
template_name = "todoapp/todo.html"
context = {"form": form}
return render(request, template_name, context)

Here’s the implementation:

  • Import Dependencies: We import necessary modules like render, redirect, TodoForm, and Todo model.
  • Define createTodoView Function: This function handles both rendering the form and processing form submission.
    - If the request method is GET, it renders the form.
    - If the request method is POST, it validates the form data, saves the new Todo item if valid, and redirects to the list of Todo items.
  • Template Rendering: The view renders the todo.html template, passing the form object as context for rendering the form.

By following this approach, users can access a form to create new Todo items and submit the form to add them to the database.

2. Show todo(List View) Now, let’s create a view to display a list of all todos in todoapp/views.py:

def showTodoView(request):
obj = Todo.objects.all()
template_name = "todoapp/show.html"
context = {"obj": obj}
return render(request, template_name, context)

Here’s the implementation:

  • Define showTodoView Function: This function retrieves all Todo items using Todo.objects.all() and passes them to the template for rendering.
  • Template Rendering: The view renders the show.html template, passing the obj (Todo items) as context for rendering the list.

3. Update a todo (Update View) To update a todo, create a view in todoapp/views.py:

def updateTodoView(request, f_id):
obj = Todo.objects.get(id=f_id)
form = TodoForm(instance=obj)
if request.method == "POST":
form = TodoForm(request.POST, instance=obj)
if form.is_valid():
form.save()
return redirect("show_url")
template_name = "todoapp/todo.html"
context = {"form": form}
return render(request, template_name, context)

Here’s the implementation:

  • Define updateTodoView Function:
    - This function retrieves the Todo item to be updated based on its ID.
    - It initializes a form instance with the retrieved Todo item data.
    - If the request method is POST, it validates the form data, saves the updated Todo item if valid, and redirects to the list of Todo items.
    - If the request method is GET, it renders the form for editing the Todo item.
  • Template Rendering: The view renders the todo.html template, passing the form object as context for rendering the form.

4. Delete a todo (Delete View) Finally, let’s create a view to delete a todo in todoapp/views.py:

def deleteTodoView(request, f_id):
obj = Todo.objects.get(id = f_id)
if request.method == "POST":
obj.delete()
return redirect("show_url")
template_name = "todoapp/confirmation.html"
context = {"obj": obj}
return render(request, template_name, context)

Here’s the implementation:

  • Define deleteTodoView Function:
    - This function retrieves the Todo item to be deleted based on its ID.
    - If the request method is POST, it deletes the Todo item from the database and redirects to the list of Todo items.
    - If the request method is GET, it renders a confirmation page asking the user to confirm the deletion.
  • Template Rendering: The view renders the confirmation.html template, passing the obj (Todo item) as context for rendering the confirmation message.

Creating Templates

Templates in Django are used to generate HTML dynamically. We’ll create templates to render the user interface for our Todo application.

Add Bootstrap and Crispy forms for styling
Before creating templates, let’s add Bootstrap and Crispy Forms for styling our application.

pip install crispy-bootstrap5
pip install django-crispy-forms

Update settings.py to include crispy forms:

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todoapp',
'crispy_forms',
'crispy_bootstrap5',
]

CRISPY_TEMPLATE_PACK = 'bootstrap5'

Now, create HTML templates for the views in the todoproject/templates directory. You’ll need templates for the following views:

  • layout.html: for creating base html file with navbar.

Similarly, create HTML templates for the views in the todoproject/templates/todoapp directory. You’ll need templates for the following views:

  • todo.html: For the create and update forms.
  • show.html: For listing all orders.
  • confirmation.html: For confirming order deletion.
  1. Navbar
    todoproject/templates/layout.html
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% block title %}
<title>Layout Page</title>
{% endblock %}
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'todo_url' %}">Todo App</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{% url 'todo_url' %}">Add Todo</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'show_url' %}">Show Todo</a>
</li>
</ul>
</div>
</div>
</nav>

{% block content %} {% endblock %}

<script
src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"
></script>
</body>
</html>

Explaination:

  • Bootstrap Navbar Component:
    This navbar section is copied from the Bootstrap Navbar component, which provides pre-designed styles and functionality for navigation bars.
  • Customization:
    The navigation links (Add Todo and Show Todo) have been customized to fit the requirements of the Todo application.
  • Jinja Tags and Blocks:
    Jinja tags and blocks, denoted by {% %} and {{ }} respectively, are a part of Django's templating language. They allow dynamic rendering of content, including variables, loops, conditional statements, and template inheritance. In this case, {% url 'todo_url' %} and {% url 'show_url' %} are used to generate URLs for the navigation links dynamically. These tags are processed by Django's template engine before rendering the HTML page, replacing them with the corresponding URLs.

2. Add and Update
todoproject/templates/todoapp/todo.html

{% extends 'layout.html' %} 
{% load crispy_forms_tags %}

{% block title %}
<title>Add ToDo Page</title>
{% endblock %}

{% block content %}
<h1 class="text-center mt-3 mb-3">Add Todo</h1>
<div class="container">
<form method="post" class="jumbotron">
{% csrf_token %}
{{form|crispy}}
<input type="submit" value="Add Todo" class="btn btn-success">
</form>
</div>
{% endblock %}

Explaination:

  • {% extends 'layout.html' %}: This statement extends another template named layout.html, enabling template inheritance.
  • {% load crispy_forms_tags %}: Loads necessary tags and filters from the Crispy Forms library.
  • {% block title %} ... {% endblock %}: Defines the title of the current page.
  • {% block content %} ... {% endblock %}: Defines the main content of the current page, including the form and submit button.
  • A CSRF token ({% csrf_token %}) to prevent Cross-Site Request Forgery attacks.
  • The form rendered using Crispy Forms ({{ form|crispy }}), which applies Bootstrap styling to the form fields.

3. Show
todoproject/templates/todoapp/show.html

{% extends 'layout.html' %}

{% block title %}
<title>Show Todo</title>
{% endblock %}

{% block content %}
<div class="container">
<h1 class="text-center mt-3 mb-3">Show Todo</h1>
<table class="table table-dark table-bordered">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Description</th>
<th scope="col">Created at</th>
<th scope="col">Status</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for i in obj %}
<tr>
<td>{{i.title}}</td>
<td>{{i.description}}</td>
<td>{{i.created_at}}</td>
<td>
{% if i.completed %}
Completed
{% else %}
Not Completed
{% endif %}
</td>
<td>
<button class="btn btn-warning"><a href="{% url 'update_url' i.id %}">Update</a></button>&nbsp;&nbsp;
<button class="btn btn-danger"><a href="{% url 'delete_url' i.id %}">Delete</a></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

Explaination:

  • The table structure is adapted from Bootstrap’s table component, providing a visually appealing layout for displaying data.
  • For Loop ({% for i in obj %}):
    - Iterates over each item (i) in the obj list.
    - Each item represents a Todo object retrieved from the database.
    - For each Todo item, the table row (<tr>) is generated with columns for the title, description, created_at, status, and actions.
  • Conditional Statement ({% if i.completed %} ... {% else %} ... {% endif %}):
    - Checks if the Todo item is completed.
    - If completed, displays “Completed”; otherwise, displays “Not Completed”.
  • Action Buttons:
    - Two buttons are provided for each Todo item: “Update” and “Delete”.
    - Each button is styled using Bootstrap classes (btn btn-warning and btn btn-danger).
    - The href attribute of each button links to the respective update and delete URLs, passing the Todo item's ID (i.id).

4. Delete
todoproject/templates/todoapp/confirmation.html

{% extends 'layout.html' %}

{% block title %}
<title>Confirmation Page</title>
{% endblock %}

{% block content %}
<div class="container">
<form class="jumbotron" method="post">
{% csrf_token %}
<h2>Are you sure you want to delete <span class="text-danger">{{obj.title}}</span>? </h2>
<input type="submit" value="YES" class="btn btn-danger">
<button class="btn btn-success"><a href="{% url 'show_url' %}">No</a></button>
</form>
</div>
{% endblock %}

Explaination:

  • Confirmation Form:
    - The form is wrapped in a Bootstrap jumbotron for styling.
    - It contains a CSRF token ({% csrf_token %}) for security.
    - Displays a confirmation message with the title of the Todo item to be deleted ({{ obj.title }}).
    - Provides two buttons: “YES” for confirming the deletion and “No” for canceling the action.
    - The “YES” button submits the form with a value of “YES” and is styled as a danger button (btn btn-danger).
    - The “No” button is styled as a success button (btn btn-success) and links back to the "Show Todo" page ({% url 'show_url' %}).

Wiring up URLs

Then, in your app’s todoapp/urls.py file, define the URLs for your views:

from django.urls import path
from . import views

urlpatterns = [
path('', views.createTodoView, name='todo_url'),
path('show/', views.showTodoView, name='show_url'),
path('up/<int:f_id>', views.updateTodoView, name= 'update_url'),
path('del/<int:f_id>', views.deleteTodoView, name= 'delete_url'),
]

Finally, configure the URLs for your views. In your project’s todoproject/urls.py file, include the URLs for the todoapp app:

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

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

Testing your CRUD project

With everything set up, you can start your Django development server:

python manage.py runserver

Visit http://localhost:8000/ in your browser, and you should be able to create, read, update, and delete ToDo’s in your Django CRUD project using function-based views.

In this tutorial, you’ve learned how to create a Django CRUD project using function-based views. You can further enhance your project by adding features like authentication, pagination, or search functionality. Django’s flexibility and extensive ecosystem make it a great choice

--

--

Prosenjeet Shil

Python developer sharing insights on full stack development: Python, database management, Django, DRF, React JS, and more. Follow for tips and tutorials.