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 djangopip install djangopip install django
Enter fullscreen mode Exit fullscreen mode
- 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 mysitedjango-admin startproject mysitedjango-admin startproject mysite
Enter fullscreen mode Exit fullscreen mode
Navigate into your project folder:
cd mysitecd mysitecd mysite
Enter fullscreen mode Exit fullscreen mode
1.2 Create a Django App
Next, create an app within the project. We’ll call it todoList:
python manage.py startapp todoListpython manage.py startapp todoListpython manage.py startapp todoList
Enter fullscreen mode Exit fullscreen mode
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 modelsfrom django.contrib.auth.models import Userclass 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.titlefrom 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.titlefrom 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
Enter fullscreen mode Exit fullscreen mode
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 makemigrationspython manage.py migratepython manage.py makemigrations python manage.py migratepython manage.py makemigrations python manage.py migrate
Enter fullscreen mode Exit fullscreen mode
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 formsfrom .models import Taskclass TaskForm(forms.ModelForm):class Meta:model = Taskfields = ['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 titledef clean_description(self):description = self.cleaned_data.get('description')if not description:raise forms.ValidationError('Description is required')return descriptiondef clean_deadline(self):deadline = self.cleaned_data.get('deadline')if not deadline:raise forms.ValidationError('Deadline is required')return deadlinefrom 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 deadlinefrom 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
Enter fullscreen mode Exit fullscreen mode
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, redirectfrom django.views.generic import ListView, CreateView, UpdateView, DeleteViewfrom django.contrib.auth.views import LoginViewfrom django.contrib.auth.mixins import LoginRequiredMixinfrom django.contrib.auth.forms import UserCreationFormfrom django.urls import reverse_lazyfrom .models import Taskfrom .forms import TaskFormfrom django.contrib import messagesfrom django.utils import timezone# Task List Viewclass TodoListView(LoginRequiredMixin, ListView):model = Taskcontext_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 taskscontext['now'] = timezone.now()return context# Task Create Viewclass TaskCreate(LoginRequiredMixin, CreateView):model = Taskform_class = TaskFormtemplate_name = 'todoList/task_create.html'success_url = reverse_lazy('todoList')def form_valid(self, form):form.instance.user = self.request.usermessages.success(self.request, 'Task created successfully!')return super(TaskCreate, self).form_valid(form)# Task Update Viewclass TaskUpdate(LoginRequiredMixin, UpdateView):model = Taskform_class = TaskFormtemplate_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 Viewclass TaskDelete(LoginRequiredMixin, DeleteView):model = Taskcontext_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 Viewclass RegisterView(CreateView):form_class = UserCreationFormtemplate_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 registrationfrom django.contrib.auth import loginlogin(self.request, self.object)messages.success(self.request, 'Registration successful! Welcome!')return response# Login Viewclass CustomLoginView(LoginView):template_name = 'todoList/login.html'fields = '__all__'redirect_authenticated_user = Truedef get_success_url(self):messages.success(self.request, 'You have logged in successfully!')return reverse_lazy('todoList')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')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')
Enter fullscreen mode Exit fullscreen mode
-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 pathfrom .views import TodoListView, TaskCreate, TaskUpdate, TaskDelete, RegisterViewurlpatterns = [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'),]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'), ]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'), ]
Enter fullscreen mode Exit fullscreen mode
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><linkhref="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><!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><!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>
Enter fullscreen mode Exit fullscreen mode
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 %}{% 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 %}{% 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 %}
Enter fullscreen mode Exit fullscreen mode
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 %}{% 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 %}{% 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 %}
Enter fullscreen mode Exit fullscreen mode
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 %}{% 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 %}{% 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 %}
Enter fullscreen mode Exit fullscreen mode
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 %}{% 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 %}{% 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 %}
Enter fullscreen mode Exit fullscreen mode
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 %}{% 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 %}{% 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 %}
Enter fullscreen mode Exit fullscreen mode
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 %}{% 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 %}{% 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 %}
Enter fullscreen mode Exit fullscreen mode
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;}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; }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; }
Enter fullscreen mode Exit fullscreen mode
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 = UserCreationFormtemplate_name = 'registration/signup.html'success_url = reverse_lazy('login')class RegisterView(CreateView): form_class = UserCreationForm template_name = 'registration/signup.html' success_url = reverse_lazy('login')class RegisterView(CreateView): form_class = UserCreationForm template_name = 'registration/signup.html' success_url = reverse_lazy('login')
Enter fullscreen mode Exit fullscreen mode
Step 8: Run the Server
Once everything is set up, you can run the server:
python manage.py runserverpython manage.py runserverpython manage.py runserver
Enter fullscreen mode Exit fullscreen mode
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",]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", ]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", ]
Enter fullscreen mode Exit fullscreen mode
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 adminfrom django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('accounts/', include('django.contrib.auth.urls')), # For login/logoutpath('', include('todoList.urls')), # Include the todoList app URLs]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 ]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 ]
Enter fullscreen mode Exit fullscreen mode
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 pathfrom .views import TodoListView, TaskCreate, TaskUpdate, TaskDeleteurlpatterns = [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'),]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'), ]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'), ]
Enter fullscreen mode Exit fullscreen mode
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.gitcd django-todoListgit clone https://github.com/dhanushd1998/django-todoList.git cd django-todoListgit clone https://github.com/dhanushd1998/django-todoList.git cd django-todoList
Enter fullscreen mode Exit fullscreen mode
Happy coding!
原文链接:Get Done : A step-by-step guide in building a Django To Do List
暂无评论内容