Introduction
Managing tasks effectively is crucial in our busy lives, and a to-do list application can be an excellent tool for staying organized. This blog post walks you through the development of a to-do list application using Django, a powerful and versatile web framework in Python. The project, titled django-todoList, is designed to help users create, manage, and track their daily tasks seamlessly.
Prerequisites
Before we begin, ensure that you have the following:
Python installed (preferably version 3.8 or above).
Django installed. If not, you can install it using the following command.
pip install django
- A basic understanding of how Django works and familiarity with Python and HTML.
Step 1: Setting Up the Project
1.1 Create a Django Project
First, create a new Django project using the command:
django-admin startproject mysite
Navigate into your project folder:
cd mysite
1.2 Create a Django App
Next, create an app within the project. We’ll call it todoList:
python manage.py startapp todoList
Step 2: Define Models
In Django, models are used to define the structure of your data. For the GetDone To-Do app, we need a model to represent a Task.
Navigate to todoList/models.py and define the Task model:
from django.db import models
from django.contrib.auth.models import User
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
complete = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
deadline = models.DateTimeField(null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
This model includes fields like title, description, deadline, and complete to store details of each task. We also associate each task with a user via the user foreign key.
2.1 Migrate the Database
Once the model is ready, run the migrations to create the table for this model in the database:
python manage.py makemigrations
python manage.py migrate
Step 3: Create Forms
We need forms to handle user input for creating and updating tasks. In todoList/forms.py, create the TaskForm:
from django import forms
from .models import Task
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ['title', 'description', 'deadline', 'complete']
widgets = {
'title': forms.TextInput(attrs={'placeholder': 'Enter task title'}),
'description': forms.Textarea(attrs={'placeholder': 'Enter task description', 'rows': 4}),
'deadline': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'complete': forms.CheckboxInput(),
}
def clean_title(self):
title = self.cleaned_data.get('title')
if not title:
raise forms.ValidationError('Title is required')
return title
def clean_description(self):
description = self.cleaned_data.get('description')
if not description:
raise forms.ValidationError('Description is required')
return description
def clean_deadline(self):
deadline = self.cleaned_data.get('deadline')
if not deadline:
raise forms.ValidationError('Deadline is required')
return deadline
The TaskForm uses Django’s ModelForm to automatically create form fields for the Task model.
Step 4: Define Views
Next, we need to create views to handle user requests, such as creating tasks, updating them, and listing them.
In todoList/views.py, define the views:
from django.shortcuts import render, redirect
from django.views.generic import ListView, CreateView, UpdateView, DeleteView
from django.contrib.auth.views import LoginView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from .models import Task
from .forms import TaskForm
from django.contrib import messages
from django.utils import timezone
# Task List View
class TodoListView(LoginRequiredMixin, ListView):
model = Task
context_object_name = 'tasks'
template_name = 'task_list.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user_tasks = Task.objects.filter(user=self.request.user)
context['tasks'] = Task.objects.filter(user=self.request.user)
context['incomplete_tasks_count'] = user_tasks.filter(complete=False).count() # Count incomplete tasks
context['now'] = timezone.now()
return context
# Task Create View
class TaskCreate(LoginRequiredMixin, CreateView):
model = Task
form_class = TaskForm
template_name = 'todoList/task_create.html'
success_url = reverse_lazy('todoList')
def form_valid(self, form):
form.instance.user = self.request.user
messages.success(self.request, 'Task created successfully!')
return super(TaskCreate, self).form_valid(form)
# Task Update View
class TaskUpdate(LoginRequiredMixin, UpdateView):
model = Task
form_class = TaskForm
template_name = 'todoList/task_update.html'
success_url = reverse_lazy('todoList')
def form_valid(self, form):
messages.success(self.request, 'Task updated successfully!')
return super(TaskUpdate, self).form_valid(form)
# Task Delete View
class TaskDelete(LoginRequiredMixin, DeleteView):
model = Task
context_object_name = 'task'
template_name = 'todoList/task_delete.html'
success_url = reverse_lazy('todoList')
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
if response.status_code == 302:
messages.success(self.request, 'Task deleted successfully!')
return response
# User Registration View
class RegisterView(CreateView):
form_class = UserCreationForm
template_name = 'todoList/register.html'
success_url = reverse_lazy('todoList')
def form_valid(self, form):
response = super().form_valid(form)
# Log the user in after successful registration
from django.contrib.auth import login
login(self.request, self.object)
messages.success(self.request, 'Registration successful! Welcome!')
return response
# Login View
class CustomLoginView(LoginView):
template_name = 'todoList/login.html'
fields = '__all__'
redirect_authenticated_user = True
def get_success_url(self):
messages.success(self.request, 'You have logged in successfully!')
return reverse_lazy('todoList')
-TodoListView: Lists all tasks for the logged-in user.
-TaskCreate: Handles task creation.
-TaskUpdate: Allows users to update a task.
-TaskDelete: Provides a confirmation page for deleting a task.
T-he LoginRequiredMixin ensures that only logged-in users can access these views.
Step 5: Configure URLs
In todoList/urls.py, map URLs to their respective views:
from django.urls import path
from .views import TodoListView, TaskCreate, TaskUpdate, TaskDelete, RegisterView
urlpatterns = [
path('', TodoListView.as_view(), name='todoList'),
path('create/', TaskCreate.as_view(), name='create_task'),
path('edit/<int:pk>/', TaskUpdate.as_view(), name='edit_task'),
path('delete/<int:pk>/', TaskDelete.as_view(), name='delete_task'),
]
These URL patterns will map each view to a specific URL. For example, the task list is displayed at the root URL of the app, and users can create, edit, or delete tasks by visiting specific URLs.
Step 6: Create Templates
Create the following HTML templates to render the views:
6.1 base.html
The base template provides a consistent layout for all pages:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Task Manager{% endblock %}</title>
<!-- Load Static files -->
{% load static %}
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
</head>
<body class="container p-4">
<!-- Display messages -->
<div class="toast-container position-relative top-0 end-0 p-3 ml-6">
{% if messages %}
{% for message in messages %}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-body">
{{ message }}
</div>
</div>
{% endfor %}
{% endif %}
</div>
<main>
<h1 class=""> Get Done <span class="done-icon">✔</span></h1>
{% block content %}{% endblock %}
</main>
<!-- Bootstrap JS and Popper.js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Toast display on page load -->
<script>
window.onload = function () {
let toastElements = document.querySelectorAll('.toast');
toastElements.forEach(toastElement => {
var toast = new bootstrap.Toast(toastElement);
toast.show();
});
};
</script>
</body>
</html>
6.2 login.html
This template is for logging in of the user:
{% extends 'base.html' %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="card p-4">
<h2 class="mb-4">Sign in</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" type="submit">Login</button>
</form>
<p class="mt-3">Don't have an account? <a href="{% url 'register' %}">Sign up</a></p>
</div>
{% endblock %}
6.3 register.html
This template is for new user registration :
{% extends 'base.html' %}
{% block title %}Register{% endblock %}
{% block content %}
<div class="card p-4">
<h2 class="mb-4">Sign up</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" type="submit">Sign Up</button>
</form>
<p class="mt-3">Already have an account? <a href="{% url 'login' %}">Sign In</a></p>
</div>
{% endblock %}
6.4 task_create.html
This template handle task creation. They contain forms to input task details.:
{% extends 'base.html' %}
{% block title %}Create Task{% endblock %}
{% block content %}
<h2 class="mb-4">Create Task</h2>
<form method="post" class="card p-4">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-success" type="submit">Save</button>
<a href="{% url 'todoList' %}" class="btn btn-secondary mt-1">Cancel</a>
</form>
{% endblock %}
6.5 task_delete.html
This template is for deleting a particular task:
{% extends 'base.html' %}
{% block title %}Delete Task{% endblock %}
{% block content %}
<h2 class="mb-4">Delete Task</h2>
<p class="delete-text">Are you sure you want to delete this task: "{{task.title}}" ?</p>
<form method="post" class="d-inline">
{% csrf_token %}
<button class="btn btn-danger" type="submit">Confirm</button>
</form>
<a href="{% url 'todoList' %}" class="btn btn-secondary mt-1">Cancel</a>
{% endblock %}
6.6 task_list.html
This template lists all tasks for the logged-in user:
{% extends 'base.html' %}
{% block title %}Your Tasks{% endblock %}
{% block content %}
<div class="logout-section"><a href="{% url 'logout' %}" class="btn btn-secondary mt-1"
title="Do you want to sign out?">Sign out</a></div>
<div class="header">
<div class="mb-2 username">
<h3 class="superusers">Hello, {{ request.user.username }}</h3>
</div>
<a href="{% url 'create_task' %}" title="Add a new task">
<div class="add-icon">
+
</div>
</div></a>
<h5 class="count-container">You have <span class="task-count">{{ incomplete_tasks_count }}</span> incomplete tasks.</h5>
{% if tasks %}
<ul class="list-group">
{% for task in tasks %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<a href="{% url 'edit_task' task.id %}" class="text-decoration-none" title="Open the task">
<span style="text-decoration: {% if task.complete %}line-through{% endif %};">
<div class="title">{{ task.title }}</div>
</span>
<div class="mb-0 description">{{ task.description }}</div>
<div class="deadline">
Deadline: {{ task.deadline|date:"M d, Y h:i A" }}
{% if task.deadline < now %}
<span class="text-danger ms-2" title="Deadline is past!">❗</span>
{% endif %}
</div>
</a>
<span>
<a href="{% url 'delete_task' task.id %}" class="text-danger ms-3" title="Delete the task">❌️</a>
</span>
</li>
{% endfor %}
</ul>
{% else %}
<p>No pending tasks.</p>
{% endif %}
{% endblock %}
6.7 task_update.html
These templates handle task updates. They contain forms to input task details.
{% extends 'base.html' %}
{% block title %}Update Task{% endblock %}
{% block content %}
<h2 class="mb-4">Update Task</h2>
<form method="post" class="card p-4">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-success" type="submit">Save</button>
<a href="{% url 'todoList' %}" class="btn btn-secondary mt-1">Cancel</a>
</form>
{% endblock %}
Step 7: Add CSS for styling
a {
text-decoration: none;
}
h1 {
text-align: center;
margin-bottom: 40px;
color: #333;
align-items: center;
justify-content: center;
font-weight: bold;
}
h2 {
text-align: start;
margin-bottom: 10px;
color: #333;
align-items: center;
justify-content: center;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 10px;
}
body {
font-family: "Lato", serif;
margin: 0;
padding: 0;
background-color: #f8f9fa;
}
.container {
max-width: 750px;
margin: 20px auto;
background: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
input[type=text],
input[type=password],
textarea {
border: 1px solid #6c757d;
}
form label {
display: block !important;
margin-bottom: 5px;
}
.done-icon {
font-size: 38px;
color: green;
margin-left: 10px;
}
.card {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
button {
border-radius: 5px;
}
.count-container {
margin-left: 16px;
}
.superusers {
color: #333;
display: inline-block;
overflow: hidden;
white-space: nowrap;
border-right: 1px solid #333;
width: 0;
animation: typing 2s steps(30) 1s forwards, blink 0.75s step-end infinite;
}
.username {
width: 370px;
}
/* Typing & Cursor blink animation effect */
@keyframes typing {
from {
width: 0;
}
to {
width: 100%;
}
}
@keyframes blink {
50% {
border-color: transparent;
}
}
.delete-text {
font-size:16px;
}
.add-icon {
display: flex;
justify-content: center;
align-items: center;
background-color: #007bff;
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
font-size: 24px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s;
}
.add-icon a {
text-decoration: none;
color: white;
}
.add-icon:hover {
background-color: #0056b3;
}
.logout-section {
position: absolute;
margin-top: -88px;
}
.task {
padding: 10px;
border-bottom: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.task-count {
font-style: italic;
color: blue;
font-weight: bold;
}
.task:last-child {
border-bottom: none;
}
.title {
font-size: 22px;
font-weight: bold;
color: #007bff;
}
.title:hover {
color: #0056b3;
font-size: 24px;
}
.description {
margin: 5px 0;
font-size: 16px;
color: #6c757d;
}
.deadline {
font-size: 14px;
color: #adb5bd;
}
.tooltip {
position: relative;
cursor: pointer;
}
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
/* Position above the icon */
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 5px;
border-radius: 4px;
white-space: nowrap;
font-size: 12px;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.tooltip:hover::after {
opacity: 1;
}
Step 8: Add User Authentication
In views.py, you can use Django’s built-in user authentication views to handle user registration and login. For example, you can use UserCreationForm to allow users to sign up:
class RegisterView(CreateView):
form_class = UserCreationForm
template_name = 'registration/signup.html'
success_url = reverse_lazy('login')
Step 8: Run the Server
Once everything is set up, you can run the server:
python manage.py runserver
Visit http://127.0.0.1:8000/todoList to see your To-Do List app in action!
Understanding settings.py and urls.py in the mysite Folder
settings.py
The settings.py file is a crucial part of every Django project. It contains the configuration settings for your project, such as database settings, installed apps, middleware, static files configuration, and more. This file controls the behavior of your project and allows Django to connect the dots between various components.
Here's a brief overview of key settings in settings.py for your GetDone To-Do List app:
Key Sections in settings.py:
Installed Apps: In the INSTALLED_APPS list, you register all the apps used in your project. For instance:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todoList', # Our custom app
]
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / "todoList/static",
]
Here, we’ve added todoList, which is the app that manages the tasks, alongside the default apps provided by Django for user authentication, admin panel, and static files.
urls.py
In Django, the urls.py file handles the routing of HTTP requests to views. It is where you map URL patterns (such as /tasks/, /login/) to corresponding views that will handle them.
In the mysite/urls.py, you usually include URLs for the whole project and link them to the app-level urls.py file.
Here’s what urls.py looks like in your GetDone app:
Key Sections in urls.py:
Project-Level urls.py (mysite/urls.py): The urls.py file in the mysite folder is the main router for your entire Django project. It includes the URLs for the admin panel, authentication, and links to your app's specific URLs. Here’s an example:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')), # For login/logout
path('', include('todoList.urls')), # Include the todoList app URLs
]
path('admin/', admin.site.urls): This line includes the Django admin panel.
path('accounts/', include('django.contrib.auth.urls')): This includes built-in authentication URLs for login, logout, and password management.
path('', include('todoList.urls')): This includes the app-specific URLs (defined in todoList/urls.py), so users can navigate through tasks and other features.
App-Level urls.py (todoList/urls.py): This file maps specific URLs to views within the todoList app. It contains paths for viewing tasks, creating tasks, and other task-related actions. For example:
from django.urls import path
from .views import TodoListView, TaskCreate, TaskUpdate, TaskDelete
urlpatterns = [
path('', TodoListView.as_view(), name='todoList'),
path('create/', TaskCreate.as_view(), name='create_task'),
path('edit/<int:pk>/', TaskUpdate.as_view(), name='edit_task'),
path('delete/<int:pk>/', TaskDelete.as_view(), name='delete_task'),
]
TodoListView.as_view(): This view lists all tasks for the logged-in user.
TaskCreate.as_view(): This view handles the task creation form.
TaskUpdate.as_view(): This view handles the task update form.
TaskDelete.as_view(): This view handles the task deletion confirmation page.
Communication Between Files
Django’s architecture allows for smooth communication between the different files and components:
URLs and Views:
The urls.py maps URLs to views, such as task creation or list viewing. Views are defined in views.py.
Models and Views:
Views interact with models (defined in models.py) to retrieve and manipulate data (tasks). For example, in TodoListView, the view fetches tasks associated with the logged-in user using Task.objects.filter(user=self.request.user).
Forms and Views:
Forms (like TaskForm in forms.py) handle user input and interact with models to validate and save the data.
Templates:
Templates render the final output in HTML, displaying data passed from views and handling user input through forms.
Conclusion
With these steps, you’ve built a fully functional To-Do list app using Django. You’ve implemented user authentication, task management (create, edit, delete), and learned how Django’s MVC (MTV) architecture facilitates the smooth communication between models, views, templates, and URLs. This guide serves as a solid foundation for building more complex Django applications in the future.
Full code of the application is available to clone at
git clone https://github.com/dhanushd1998/django-todoList.git
cd django-todoList
Happy coding! 🚀
Top comments (0)