I’ve been working with Python for over a decade, and Django has always been my go-to framework for building web applications quickly and efficiently. One of the best ways to get comfortable with Django is by building practical projects. Today, I’ll walk you through creating a simple to-do list app using Python Django.
This tutorial is perfect if you want to learn how to handle forms, models, and views while building something useful. Creating a to-do list app is a classic beginner project, but I’ll ensure that I add some practical touches that reflect real-world usage.
Let’s get started!
What You’ll Need
Before we get in, ensure you have the following installed:
- Python 3.8+
- Django 4.x
- Basic knowledge of Python and HTML
If you don’t have Django installed yet, run:
pip install djangoRead Integrity Error in Django
Step 1: Set Up Your Django Project and App
First, start a new Django project and navigate to it:
django-admin startproject todo_project
cd todo_projectNow, create a new app called tasks:
python manage.py startapp tasksNext, add the tasks app to your project’s settings. Open todo_project/settings.py and add 'tasks' to the INSTALLED_APPS list:
INSTALLED_APPS = [
# Default Django apps...
'tasks',
]Step 2: Create the Task Model
The core of our to-do list is the Task model, which will store individual tasks.
Open tasks/models.py and add:
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
completed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.titleHere’s what each field means:
title: The task name (e.g., “Buy groceries”).description: Optional details about the task.completed: Whether the task is done.created_at: Timestamp for when the task was created.
Run migrations to create the database table:
python manage.py makemigrations
python manage.py migrateStep 3: Create Views to Handle Tasks
We need views to display, add, update, and delete tasks.
Open tasks/views.py and add the following:
from django.shortcuts import render, redirect, get_object_or_404
from .models import Task
from .forms import TaskForm
def task_list(request):
tasks = Task.objects.order_by('-created_at')
return render(request, 'tasks/task_list.html', {'tasks': tasks})
def task_create(request):
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('task-list')
else:
form = TaskForm()
return render(request, 'tasks/task_form.html', {'form': form})
def task_update(request, pk):
task = get_object_or_404(Task, pk=pk)
if request.method == 'POST':
form = TaskForm(request.POST, instance=task)
if form.is_valid():
form.save()
return redirect('task-list')
else:
form = TaskForm(instance=task)
return render(request, 'tasks/task_form.html', {'form': form})
def task_delete(request, pk):
task = get_object_or_404(Task, pk=pk)
if request.method == 'POST':
task.delete()
return redirect('task-list')
return render(request, 'tasks/task_confirm_delete.html', {'task': task})Step 4: Create a Form for Tasks
To simplify form handling, create a Django ModelForm.
Create a new file tasks/forms.py and add:
from django import forms
from .models import Task
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ['title', 'description', 'completed']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Task Title'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Task Description'}),
'completed': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}Read Python Django “Template does not exist” error
Step 5: Configure URLs
In tasks/urls.py, set up URL patterns:
from django.urls import path
from . import views
urlpatterns = [
path('', views.task_list, name='task-list'),
path('add/', views.task_create, name='task-add'),
path('edit/<int:pk>/', views.task_update, name='task-edit'),
path('delete/<int:pk>/', views.task_delete, name='task-delete'),
]Then include these URLs in the main project’s urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('tasks.urls')),
]Step 6: Create Templates
We’ll need three templates: one for the task list, one for the form, and one for the delete confirmation.
Create a folder named templates/tasks/ inside your tasks app directory.
a) task_list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My To-Do List</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">My To-Do List</h1>
<a href="{% url 'task-add' %}" class="btn btn-primary mb-3">Add New Task</a>
<table class="table table-striped">
<thead>
<tr>
<th>Task</th>
<th>Description</th>
<th>Completed</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for task in tasks %}
<tr>
<td>{{ task.title }}</td>
<td>{{ task.description|default:"No description" }}</td>
<td>
{% if task.completed %}
<span class="badge bg-success">Yes</span>
{% else %}
<span class="badge bg-warning text-dark">No</span>
{% endif %}
</td>
<td>
<a href="{% url 'task-edit' task.pk %}" class="btn btn-sm btn-info">Edit</a>
<a href="{% url 'task-delete' task.pk %}" class="btn btn-sm btn-danger">Delete</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center">No tasks added yet.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>b) task_form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{% if form.instance.pk %}Edit Task{% else %}Add Task{% endif %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="container mt-5">
<h1>{% if form.instance.pk %}Edit Task{% else %}Add Task{% endif %}</h1>
<form method="post" novalidate>
{% csrf_token %}
<div class="mb-3">
{{ form.title.label_tag }}
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger">{{ form.title.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.description.label_tag }}
{{ form.description }}
{% if form.description.errors %}
<div class="text-danger">{{ form.description.errors }}</div>
{% endif %}
</div>
<div class="form-check mb-3">
{{ form.completed }}
{{ form.completed.label_tag }}
</div>
<button type="submit" class="btn btn-success">Save</button>
<a href="{% url 'task-list' %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
</body>
</html>c) task_confirm_delete.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Delete Task</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="container mt-5">
<h1>Delete Task</h1>
<p>Are you sure you want to delete the task: <strong>{{ task.title }}</strong>?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Yes, Delete</button>
<a href="{% url 'task-list' %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
</body>
</html>Check out Parse JSON in Python Django
Step 7: Run Your App
Start the Django development server:
python manage.py runserverOpen your browser and navigate to http://127.0.0.1:8000/. You’ll see your to-do list app in action.
I executed the above example code and added the screenshot below.



Read Query in Descending and Ascending in Python Django
Alternative Method: Use Django Admin for Task Management
If you prefer not to build custom views and templates, Django’s built-in admin panel is a great way to manage tasks quickly.
Register your model in tasks/admin.py:
from django.contrib import admin
from .models import Task
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_display = ('title', 'completed', 'created_at')
list_filter = ('completed', 'created_at')
search_fields = ('title', 'description')Create a superuser to access the admin:
python manage.py createsuperuserRun the server and visit http://127.0.0.1:8000/admin/ to add, edit, or delete tasks without any custom interface.
Building a to-do list app in Django is a fantastic way to understand the framework’s key features like models, views, forms, and templates. The project we built is simple but expandable; you can add user authentication, deadlines, or notifications as next steps.
If you’re new to Django, don’t hesitate to explore the official docs as you go. Practical projects like this one will deepen your understanding and prepare you for more complex applications.
Try customizing this app with your features or styling. Happy coding!
You may also like to read:
- Calculator App in Python Django
- Python Django “Module not found” error.
- Query in Descending and Ascending in Python Django

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.