Simple ToDo List App in Django Framework+ htmx + Bootstrap 5

Intermediate developer exploring Python Django. In-depth insights, tutorials & tips. Unlocking the potential of web development. #Python #Django #WebDevelopment
Check out my previous blog about Revolutionize Your Chat App with Django Channels: Unleashing Real-Time Communication Power!.
Introduction
In this tutorial, we will walk through the process of creating a Todo list application using the Django web framework. Django is a powerful and popular framework that enables the rapid development of web applications. By following this step-by-step guide, you’ll learn the fundamentals of Django while building a functional and interactive Todo list application.

Why htmx?
htmx is a lightweight JavaScript library that allows for seamless interaction between the client and server. It enables you to build dynamic web applications without the need for writing complex JavaScript code. The name “htmx” stands for “hypertext markup extensions.”
Here are some key features and benefits of using htmx:
Simplicity: htmx simplifies the development process by leveraging existing HTML, CSS, and JavaScript knowledge. It allows you to achieve dynamic behavior by adding attributes directly to your HTML elements.
Progressive Enhancement: htmx follows the progressive enhancement approach, meaning that it enhances the user experience without relying on JavaScript. If JavaScript is disabled or not supported, the application will still function with standard HTML forms and links.
Efficient Server Interaction: htmx enables smooth communication between the client and server by sending and receiving HTML or JSON fragments instead of whole page reloads. This results in faster and more responsive applications.
Declarative Attribute-Based Syntax: htmx utilizes attributes, such as
hx-get,hx-post,hx-patch, andhx-swap, to define the desired behavior of HTML elements. These attributes can be added directly to your existing HTML code, making it easy to understand and maintain.Wide Browser Support: htmx is compatible with modern browsers and supports a wide range of frameworks and technologies, including Django, as demonstrated in the previous examples.
By using htmx, you can enhance your web application by adding dynamic behavior and interactivity without the need for complex JavaScript code. It simplifies the development process, improves performance, and provides a better user experience.
You can visit the beautiful official documentation htmx at https://htmx.org/.
So, Let’s Create a simple application 👍
Prerequisites
Before diving into this tutorial, ensure that you have the following prerequisites in place:
Basic understanding of Python and Django Framework.
Python (version 3.6 or above) and pip installed on your system.
Familiarity with the Django framework and its concepts.
[Note: Use Virtual Environment For Good. 👍 ]
Steps
1. Setting Up the Django Project
To start building our Simple htmx application, we need to set up a new Django project. We’ll cover the installation process and project initialization.
To get started, follow these steps to set up the Django framework:
Install Django Framework using pip:
pip install django
Create a new Django project:
django-admin startproject myproject
Create a new Django app within your project:
cd myproject
python manage.py startapp myapp
2. Configure Django
Open your Django project’s settings.py file and write the following code :
INSTALLED_APPS = [
# ...
# ..
# .
# 👇 1. Add this line
'myapp',
]
TEMPLATES = [
{
# 👇 2. Add this line
'DIRS': ['templates'],
},
]
3. Add the URLs
In this, For accessing our application myapp urls we have to add the following line to the myproject/urls.py file.
Open the urls.py from inside of myproject folder and write the following code:
from django.contrib import admin
from django.urls import path, include # 👈 1. Add this line
urlpatterns = [
path('admin/', admin.site.urls),
# 👇 2. Add the app url on this
path('', include('myapp.urls'))
]
URL Configuration of views:
Create a new file urls.py inside the myapp folder and write the below code:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='home'),
]
htmxpatterns = [
path('create_todo/', views.create_todo, name='create_todo'),
path('delete_todo/<int:pk>/', views.delete_todo, name='delete_todo'),
path('mark_todo/<int:pk>/', views.mark_todo, name='mark_todo'),
]
urlpatterns += htmxpatterns
Django’s urls.py file to define URL patterns for routing requests to the appropriate view functions.
path(‘’, views.index, name=’home’), : A simple index view(function-based view) for rendering home page and todo list data.
Additionally, it introduces a separate htmxpatterns list to define additional URL patterns specifically for htmx-enabled functionality.
'create_todo/': Maps to thecreate_todoview function in theviewsmodule and is named'create_todo'.'delete_todo/<int:pk>/': Maps to thedelete_todoview function, which expects an integer parameterpkin the URL. This pattern allows for deleting a specific todo item.'mark_todo/<int:pk>/': Maps to themark_todoview function, which expects an integer parameterpkin the URL. This pattern allows for marking a specific todo item as completed.
urlpatterns += htmxpatterns : This appends the htmxpatterns list to the existing urlpatterns list, effectively combining them.
4. Add The Todo Model
Now, Open the models.py file from inside of myapp and write the following code:
from django.db import models
# 👇 1. Add the following line's
class Todo(models.Model):
title = models.CharField(max_length=100)
completed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
The from django.db import models statement is used to import the models module from the Django framework's database abstraction layer.
In this, the Todo class is defined as a subclass of models.Model. This means that Todo is a Django model, representing a database table.
Inside the Todo class, the following fields are defined:
title: This is aCharFieldwith a maximum length of 100 characters. It represents the title or name of the todo item.completed: This is aBooleanFieldwith a default value ofFalse. It represents the completion status of the todo item.created_at: This is aDateTimeFieldwithauto_now_add=True. It represents the timestamp when the todo item was created. Theauto_now_addattribute ensures that the field is automatically set to the current date and time when a new todo item is created.
Additionally, there is a __str__ method defined within the Todo class. This method returns a string representation of the Todo object, which is useful for displaying meaningful information when working with instances of the Todo model.
5. Add the View Function
Open, The views.py from myapp folder and write the below code for showing and redirecting to our templates:
from django.shortcuts import render
from .models import Todo
# Rendering home page
def index(request):
todos = Todo.objects.all().order_by('-id')
return render(request, 'index.html', {'todos': todos})
A basic function-based view to render the home page template and to-do list data.
Let’s create views for our URLs.
Add those lines too in views.py from myapp folder :
# Creating a new Todo
def create_todo(request):
title = request.POST.get('title')
todo = Todo.objects.create(title=title)
todo.save()
todos = Todo.objects.all().order_by('-id')
return render(request, 'todo-list.html', {'todos': todos})
# Marking completed True
def mark_todo(request, pk):
todo = Todo.objects.get(pk=pk)
todo.completed = True
todo.save()
todos = Todo.objects.all().order_by('-id')
return render(request, 'todo-list.html', {'todos': todos})
# Deleting a Todo
def delete_todo(request, pk):
todo = Todo.objects.get(pk=pk)
todo.delete()
todos = Todo.objects.all().order_by('-id')
return render(request, 'todo-list.html', {'todos': todos})
Creating a new Todo create_todo : This function handles a POST request and retrieves the title from the request's POST data. It then creates a new Todo object with the provided title using the Todo.objects.create() method. The todo.save() method saves the new Todo object to the database. Finally, it retrieves all the Todo objects from the database, ordered by their ID in descending order, and renders the 'todo-list.html' template with the updated list of todos.
Marking a Todo as completed mark_todo : This function takes a primary key (pk) as a parameter and retrieves the corresponding Todo object using Todo.objects.get(). It then sets the completed field of the Todo object to True and saves the changes to the database. Afterward, it retrieves all the Todo objects from the database, ordered by their ID in descending order, and renders the 'todo-list.html' template with the updated list of todos.
Deleting a Todo delete_todo: This function also takes a primary key (pk) as a parameter and retrieves the corresponding Todo object using Todo.objects.get(). It then deletes the Todo object from the database using the delete() method. Similarly, it retrieves all the Todo objects from the database, ordered by their ID in descending order, and renders the 'todo-list.html' template with the updated list of todos.
In each function, after modifying the Todo objects or deleting one, the template ‘todo-list.html’ is rendered to display the updated list of todos.
[Note: Make sure that the associated URL patterns are correctly defined and mapped to these functions in the Django application’s URL configuration.]
6. Templates
Create a folder named templates inside the myproject folder and create a file inside templates folder called base.html and write the below code:
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}</title>
<!-- Bootstrap 5 css -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<!-- Custom styles -->
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
</head>
<body>
<div class="container mt-5">
{% block content %}
{% endblock %}
</div>
<!-- Bootstrap 5 js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<!-- HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.2" integrity="sha384-L6OqL9pRWyyFU3+/bjdSri+iIphTN/bvYyM37tICVyOJkWZLpP2vGn6VUEXgzg6h" crossorigin="anonymous"></script>
{% block js_script %}
{% endblock %}
</body>
</html>
Let’s Create our index.html page: Open the templates folder and create a new file called index.html and write the following code:
{% extends 'base.html' %}
{% block title %} Home {% endblock %}
{% block content %}
<h3 class="my-5">Welcome to Your Todo List</h3>
<form hx-post="{% url 'create_todo' %}" hx-target="#todoList" class="mx-auto">
{% csrf_token %}
<div class="mb-3 row align-items-center">
<label for="todoText" class="col-auto col-form-label">Enter your todo here: </label>
<div class="col-6">
<input type="text" name="title" class="form-control" id="todoText" required>
</div>
<div class="col-auto">
<button class="btn btn-success">Add</button>
</div>
</div>
</form>
<div id="todoList">
{% include 'todo-list.html' %}
</div>
{% endblock %}
{% block js_script %}
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
})
</script>
{% endblock %}
form: This is a form element that uses htmx attributes. The hx-post attribute specifies the URL for the form submission, and the hx-target attribute indicates the element on the page that should be updated with the response. The {% csrf_token %} tag adds a CSRF token to the form for security purposes.
div todoList : This <div> element with the id "todoList" is the target element that will be updated with the response from the form submission. It includes another template called 'todo-list.html' using the {% include %} tag. The 'todo-list.html' template likely contains the HTML representation of the Todo list.
js_script block : This block defines a JavaScript script that adds a CSRF token to the headers of htmx requests. It ensures that the CSRF protection is applied when making htmx requests.
In summary, this Django template extends a base template and defines a content block that includes a form for creating new Todo items. It uses htmx to handle form submissions asynchronously and update the ‘todoList’ section of the page with the response.
Now, Create a new file called todo-list.html for rendering our todo list in a separate file and write the following code:
{% if todos %}
<div class="card" style="width: 18rem;">
<ul class="list-group list-group-flush">
{% for todo in todos %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div class="{% if todo.completed %} text-success text-decoration-line-through {% endif %}">
{{ todo.title }}
</div>
<div>
<span class="action badge rounded-pill text-bg-warning" hx-get="{% url 'mark_todo' todo.pk %}"
hx-target="#todoList" style="cursor: pointer;">✔</span>
<span class="action badge rounded-pill text-bg-danger" hx-delete="{% url 'delete_todo' todo.pk %}"
hx-target="#todoList" hx-confirm="Are you sure you wish to delete?" style="cursor: pointer;">X</span>
</div>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<h5>Currently, you don't have any todos.</h5>
{% endif %}
{% if todos %} : This line checks if the todos variable exists and is not empty. It is a conditional statement that determines whether to render the list of todos or display a message when there are no todos.
This code block creates a card container and an unordered list (ul) with the CSS classes list-group and list-group-flush. It starts a loop that iterates over each todo object in the todos variable.
This part represents each todo item in the list. It uses the li element with the CSS classes list-group-item, d-flex, justify-content-between, and align-items-center for proper styling. Inside the list item, there are two div elements.
The first div contains the todo's title. If the todo is marked as completed (based on the todo.completed attribute), the CSS classes text-success and text-decoration-line-through are added to the div to visually indicate completion.
The second div contains action buttons for marking a todo as complete and deleting it. These are represented by the span elements with CSS classes action, badge, and rounded-pill. The hx-get attribute specifies the URL for marking the todo as complete, while the hx-delete attribute specifies the URL for deleting the todo. The hx-target attribute defines the element where the updated to-do list should be rendered. The hx-confirm attribute provides a confirmation message when deleting the todo.
This line marks the end of the for loop and closes the ul and div elements.
This block of code is executed when the todos variable is empty or doesn't exist. It displays an h5 heading with the message "Currently, you don't have any todos."
Overall, this Django template code snippet generates an HTML representation of the todos by iterating over the todos variable and applying conditional logic to handle different scenarios, such as displaying the list of todos or showing a message when there are no todos. It also incorporates htmx attributes (hx-get and hx-delete) to enable dynamic interactions for marking todos as complete and deleting them without page refreshes.
7. Testing and Running
Now that we have set up the basic structure of our application using Django Framework, it’s time to test and run the app. Follow these steps:
Step 1: Open your Command-Line Interface:
Open your command-line interface and navigate to the root directory of your Django project.
To proceed, please open the terminal within the myproject folder and execute the following command:
python manage.py makemigrations
python manage.py migrate
Step 2: Start the Server:
To start the server, run the following command in your command-line interface:
python manage.py runserver
This command will launch the Django development server.
Step 3: Testing
After running the server and accessing the project interface at http://127.0.0.1:8000/.
Open any browser and hit : http://127.0.0.1:8000/ URL and you can the home page of our website looks like this:

Let’s create some todo’s: Now, Write your todo on input box and click on “Add” button.

Update todo as completed: Now, click on check mark(“✔”) and it will mark your todo task mark as completed.

Delete todo: Now, click on cross mark (“X”) and it will delete a todo from the list.

Final Result,

Checkout Full Live Demo:
Conclusion
By incorporating htmx into our Django Todo list application, we have enabled dynamic updates without page refreshes, creating a more seamless user experience. htmx simplifies the implementation of interactive web applications by reducing the need for complex JavaScript code.
Feel free to explore htmx’s documentation to discover more features and possibilities for enhancing your application further.
Happy coding!
Support my passion for sharing development knowledge by making a donation to Buy Me a Coffee. Your contribution helps me create valuable content and resources. Thank you for your support!
Thank you for taking the time to read this blog about Simple ToDo List App in Django + htmx + Bootstrap 5. I hope that you found the information presented here useful and informative.
If you have any questions or comments about the information presented in this blog, please feel free to reach out. Thank you again for reading!



