A TODO app is a simple tool that helps you keep track of tasks you need to complete.
It allows you to add, manage, and mark tasks as done, keeping you organized and on top of your responsibilities.
What we will create
Creating Project and App: We start by setting up a Django project and creating a specific app within it to handle our TODO tasks.
Creating Database: Django automatically sets up a database when you create a project. We'll define models to store our TODO items, which will be saved and retrieved from this database.
Creating Forms: We'll create forms to add and update TODO items, making it easy for users to input their tasks. These forms will be tied to our models, ensuring data consistency and validation.
CRUD Operations: CRUD stands for Create, Read, Update, and Delete—the core operations we'll implement for managing TODO items. This lets users add new tasks, view them, make changes, and delete tasks they no longer need.
Move Around TODO Item on Window: This feature allows users to drag and rearrange their TODO items, helping them prioritize tasks visually. It's a simple yet effective way to keep tasks in order.
Move to Bin: Users can move tasks they no longer need into a "bin" or trash, where they can either be permanently deleted or restored later. This adds a layer of safety before completely removing a task.
How to Create a Django Project
To create a Django project, you'll need to start by installing Django and setting up a new project.
This involves using the django-admin startproject
command followed by the name of your project.
For a detailed guide, you can follow this existing blog, we are assuming you have given name o project as todoProject
How to Create a Django Project.
make sure you have reach to running server properly.
Missed Something? check this github code
How to Create an App Inside a Django Project
Once your Django project is set up, you can create an app within it using the python manage.py startapp
command followed by the name of your app. if server is still running stop it. make sure to have virtual env activated.
To create a TODO app, you would run.
cd todoProject
python manage.py startapp todo
This command will generate a directory structure with the necessary files to start building the app.
Missed Something? check this github code
Creating the Database
In Django, models are Python classes that represent database tables.
Each attribute of the model corresponds to a field in the table, allowing you to define the structure of your data directly in code.
For our TODO list, we'll create a model to store each task.
Here’s how you can define the TodoItem
model in your models.py
file
from django.db import models class TodoItem(models.Model): title = models.CharField(max_length=100) description = models.TextField(blank=True, null=True) completed = models.BooleanField(default=False) def __str__(self): return self.title
After defining the model, you need to register your app in the INSTALLED_APPS
list within settings.py
INSTALLED_APPS = [ # other apps 'todo', # add this line to register your todo app ]
Next, run the following commands to create the necessary database tables
python manage.py makemigrations python manage.py migrate
These commands will generate and apply migrations, creating the database structure based on your models.
Missed Something? check this github code
Register Model to Admin Site
To manage your TODO items through Django's admin interface, you need to register the TodoItem
model with the admin site.
This allows you to create, update, and delete tasks directly
from the admin panel.
- Open the
admin.py
file in yourtodo
app directory. - Add the following code to register the
TodoItem
modelfrom django.contrib import admin from .models import TodoItem @admin.register(TodoItem) class TodoItemAdmin(admin.ModelAdmin): list_display = ('title', 'description', 'completed') list_filter = ('completed',) search_fields = ('title', 'description')
This setup does the following:
list_display
: Specifies the fields to be displayed in the admin list view.list_filter
: Adds filters in the sidebar to filter tasks by completion status.search_fields
: Allows searching for tasks by title or description.
With this setup, you'll have full control over your TODO items from the Django admin interface. Click on add, create few todos from admin.
Missed Something? check this github code
Creating a Form to Input TODO Items
To allow users to input TODO items and save them to the database, we'll create a Django form.
This form will be tied to our TodoItem
model, making it easy to validate and save data.
Steps to create the form:
Create a forms.py file: If it doesn't already exist, create a
forms.py
file inside yourtodo
app directory.Define the form: Add the following code to
forms.py
from django import forms from .models import TodoItem class TodoItemForm(forms.ModelForm): class Meta: model = TodoItem # Excluded completed fields = ['title', 'description']
ModelForm
: We're usingModelForm
to automatically create a form based on theTodoItem
model.fields
: Specifies which fields to include in the form.widgets
: Customizes the form widgets, like setting placeholders and using a checkbox for thecompleted
field.
Missed Something? check this github code
Creating Views to Render the Form (CBV and FBV)
In this step, we'll create two types of views to handle the TODO form: a Class-Based View (CBV) and a Function-Based View (FBV).
Both will render the form and allow users to submit tasks.
Function-Based View (FBV)
First, let's create a function-based view to handle the form
- Open or create the
views.py
file in yourtodo
app directory. - Add the following code
from django.shortcuts import render, redirect from .forms import TodoItemForm def todo_create(request): if request.method == 'POST': form = TodoItemForm(request.POST) if form.is_valid(): form.save() return redirect('todo:todo_list') # Redirect to the list view after saving else: form = TodoItemForm() return render(request, 'todo/todo_form.html', {'form': form})
Class-Based View (CBV)
Now, let's create a class-based view for the same purpose
- Add the following code to
views.py
from django.views.generic.edit import CreateView from django.urls import reverse_lazy from .models import TodoItem class TodoCreateView(CreateView): model = TodoItem template_name = 'todo/todo_form.html' fields = ['title', 'description'] success_url = reverse_lazy('todo_list') # Redirect to the list view after saving
Setting Up the Template
Next, we need to create the template that both views will use to render the form
- Create a directory named
templates
inside yourtodo
app directory. - Inside
templates
, create another directory namedtodo
. - Create a file named
todo_form.html
inside thetodo
directory and add the following code
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Create TODO</title> </head> <body> <h2>Create a New TODO Item</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Save Task</button> </form> </body> </html>
URL Configuration
Finally, let's set up the URLs to access these views
- Lets create
urls.py
file inside app and then use it to register to mainurls.py
file - Add the following code.
from django.urls import path from .views import todo_create, TodoCreateView urlpatterns = [ path('create/', todo_create, name='todo_create'), # FBV path('create-cbv/', TodoCreateView.as_view(), name='todo_create_cbv'), # CBV ]
- Open the
urls.py
file in your project directory and register our app urls file to it.from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include(('todo.urls','todo'), namespace='todo')), ]
- FBV (Function-Based View): Handles form submission and rendering through a traditional function.
- CBV (Class-Based View): Utilizes Django's built-in
CreateView
for a more abstract and reusable approach. - Template: Both views use the same
todo_form.html
template to render the form.
This setup gives users flexibility in choosing between FBV and CBV while showcasing Django’s capabilities.
Missed Something? check this github code
Displaying the Created TODO Items
Now that users can create TODO items, let's create a view to display them in a list.
We'll set up a basic template that shows all TODO items, separated by an <hr>
tag for clarity.
Create the View to List TODO Items
In the views.py
file, add the following function-based view
from django.shortcuts import render from .models import TodoItem def todo_list(request): todos = TodoItem.objects.all() # Fetch all TODO items from the database return render(request, 'todo/todo_list.html', {'todos': todos})
If you want to use a class-based view instead, you can use Django's built-in ListView
from django.views.generic import ListView from .models import TodoItem class TodoListView(ListView): model = TodoItem template_name = 'todo/todo_list.html' context_object_name = 'todos' # Use 'todos' in the template to access the list of items
Set Up the Template to Display the TODO List
Create a new template file named todo_list.html
in the todo
directory inside templates
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TODO List</title> </head> <body> <h2>TODO List</h2> {% for todo in todos %} <h3>{{ todo.title }}</h3> <p>{{ todo.description }}</p> <p>Completed: {{ todo.completed }}</p> <hr> {% empty %} <p>No TODO items yet!</p> {% endfor %} </body> </html>
URL Configuration
Finally, let's set up the URLs to access these views:
- Open the
urls.py
file in your project directory (if it doesn't exist, create it). - Add the following code
from django.urls import path from .views import todo_list, TodoListView urlpatterns = [ path('', todo_list, name='todo_list'), # FBV for listing TODO items path('', TodoListView.as_view(), name='todo_list_cbv'), # CBV for listing TODO items ]
- Template: The
todo_list.html
template loops through the TODO items and displays each one with a title, description, and completion status. Each item is separated by an<hr>
tag. - FBV & CBV: We provided both function-based and class-based views to list the TODO items, allowing you to choose the approach that best fits your needs.
This setup will show all the TODO items in a simple, clean list format.
if you start your server with following comand you should see following page.
# inside TODO-Project\todoProject and keeping virtual env activated python manage.py runserver
Missed Something? check this github code
Creating an Update View for TODO Items
Now, we'll add functionality to update existing TODO items. We'll create both a function-based view (FBV) and a class-based view (CBV) for this purpose.
Then, we'll update the TODO list template to include an "Edit" link for each item.
Function-Based View (FBV) for Updating TODO Items
Add the following function-based view to views.py
from django.shortcuts import get_object_or_404 def todo_update(request, pk): todo = get_object_or_404(TodoItem, pk=pk) # Fetch the TODO item by its primary key if request.method == 'POST': form = TodoItemForm(request.POST, instance=todo) # Bind the form to the existing TODO item if form.is_valid(): form.save() return redirect('todo_list') # Redirect to the list view after updating else: form = TodoItemForm(instance=todo) # Prepopulate the form with the existing TODO item data return render(request, 'todo/todo_form.html', {'form': form})
Class-Based View (CBV) for Updating TODO Items
Add the following class-based view to views.py
from django.views.generic.edit import UpdateView class TodoUpdateView(UpdateView): model = TodoItem template_name = 'todo/todo_form.html' fields = ['title', 'description', 'completed'] success_url = reverse_lazy('todo:todo_list') # Redirect to the list view after updating
Template for Update Form
We'll use the same todo_form.html
template we created earlier for both creating and updating TODO items. No changes are needed in the template file.
Update URL Configuration
Update your urls.py
file to include the new views for updating TODO items
from django.urls import path from .views import todo_update, TodoUpdateView urlpatterns = [ path('update/<int:pk>/', todo_update, name='todo_update'), # FBV for updating TODO path('update-cbv/<int:pk>/', TodoUpdateView.as_view(), name='todo_update_cbv'), # CBV for updating TODO ]
Update TODO List Template for Edit Link
Finally, update the todo_list.html
template to include an "Edit" link for each TODO item
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TODO List</title> </head> <body> <h2>TODO List</h2> {% for todo in todos %} <h3>{{ todo.title }}</h3> <p>{{ todo.description }}</p> <p>Completed: {{ todo.completed }}</p> {% if not todo.completed %}<a href="{% url 'todo:todo_update' todo.pk %}">Edit</a> <!-- FBV Edit Link -->{% endif %} {% if not todo.completed %}<a href="{% url 'todo:todo_update_cbv' todo.pk %}">Edit (CBV)</a> <!-- CBV Edit Link -->{% endif %} <hr> {% empty %} <p>No TODO items yet!</p> {% endfor %} </body> </html>
- Update Form: We reused the
todo_form.html
template for both creating and updating TODO items. - FBV & CBV: Both function-based and class-based views were created to handle the update functionality.
- Edit Link: The TODO list template now includes "Edit" links that direct users to the appropriate update view.
With this setup, users can easily update their TODO items using either FBV or CBV, showcasing the flexibility of Django.
Missed Something? check this github code
Adding Bootstrap 5 to Project
we'll install the django-bootstrap-v5
package, integrate it into your project, and update your forms and HTML templates to use Bootstrap 5 for styling.
Install the Bootstrap 5 Package
First, install the django-bootstrap-v5
package using pip
pip install django-bootstrap-v5
Configure Django to Use django-bootstrap-v5
Next, add django_bootstrap_v5
to your INSTALLED_APPS
in your Django project's settings.py
INSTALLED_APPS = [ # other apps 'bootstrap5', ]
This package will now be available across your project, allowing you to use Bootstrap 5 components easily.
Update the Form Template
We'll modify the todo_form.html
template to apply Bootstrap 5 classes to the form elements.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Create or Update TODO</title> {% load bootstrap5 %} {% bootstrap_css %} </head> <body> <div class="container mt-5"> <h2>Create or Update a TODO Item</h2> <form method="post" class="mt-3"> {% csrf_token %} {% bootstrap_form form %} <button type="submit" class="btn btn-primary mt-3">Save Task</button> </form> </div> {% bootstrap_javascript %} </body> </html>
Update the TODO List Template
Now, update the todo_list.html
template to include Bootstrap styling.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TODO List</title> {% load bootstrap5 %} {% bootstrap_css %} </head> <body> <div class="container mt-5"> <h2 class="mb-4">TODO List</h2> <div id="todo-container"> {% for todo in todos %} <div class="card mb-3 draggable" draggable="true" id="todo-{{ todo.pk }}"> <div class="card-body"> <h5 class="card-title">{{ todo.title }}</h5> <p class="card-text">{{ todo.description }}</p> <p class="card-text"><small class="text-muted">Completed: {{ todo.completed }}</small></p> {% if not todo.completed %}<a href="{% url 'todo:todo_update' todo.pk %}" class="btn btn-warning">Edit</a> <!-- FBV Edit Link -->{% endif %} {% if not todo.completed %}<a href="{% url 'todo:todo_update_cbv' todo.pk %}" class="btn btn-warning">Edit (CBV)</a> <!-- CBV Edit Link -->{% endif %} </div> </div> {% empty %} <p>No TODO items yet!</p> {% endfor %} </div> </div> </body> </html>
With these updates, TODO app will have a more polished, modern look, thanks to Bootstrap 5.
Missed Something? check this github code
Creating a base.html
Template and Using Content Blocks
In this step, we'll create a base.html
template to hold the common HTML structure for your pages.
We'll then update the existing templates to extend base.html
and use content blocks for page-specific content.
Create base.html
First, create a new file named base.html
in your templates
directory. This file will contain the common structure, such as the <head>
section, navigation, and footer.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}TODO App{% endblock %}</title> {% load bootstrap5 %} {% bootstrap_css %} {% block extra_head %} <!-- Additional head content can be added here --> {% endblock %} </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="{% url 'todo:todo_list' %}">TODO App</a> <div class="collapse navbar-collapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="{% url 'todo:todo_create' %}">Add TODO</a> </li> </ul> </div> </nav> <div class="container mt-5"> {% block content %} <!-- Page-specific content goes here --> {% endblock %} </div> {% bootstrap_javascript %} {% block extra_scripts %} <!-- Additional scripts can be added here --> {% endblock %} </body> </html>
Implementing "Move Around TODO Item on Window" Feature
In this step, we'll add a feature that allows users to drag and move TODO items around on the screen. This will be done using a bit of JavaScript for the drag-and-drop functionality.
Update Existing Templates to Extend base.html
Now, update the todo_form.html
and todo_list.html
templates to extend base.html
and use content blocks.
todo_form.html
{% extends 'base.html' %} {% load bootstrap5 %} {% block title %}Create or Update TODO{% endblock %} {% block content %} <h2>Create or Update a TODO Item</h2> <form method="post" class="mt-3"> {% csrf_token %} {% bootstrap_form form %} <button type="submit" class="btn btn-primary mt-3">Save Task</button> </form> {% endblock %}
todo_list.html
{% extends 'base.html' %} {% block title %}TODO List{% endblock %} {% block content %} <h2 class="mb-4">TODO List</h2> <div id="todo-container"> {% for todo in todos %} <div class="card mb-3 draggable" draggable="true" id="todo-{{ todo.pk }}"> <div class="card-body"> <h5 class="card-title">{{ todo.title }}</h5> <p class="card-text">{{ todo.description }}</p> <p class="card-text"><small class="text-muted">Completed: {{ todo.completed }}</small></p> {% if not todo.completed %}<a href="{% url 'todo:todo_update' todo.pk %}" class="btn btn-warning">Edit</a> <!-- FBV Edit Link -->{% endif %} {% if not todo.completed %}<a href="{% url 'todo:todo_update_cbv' todo.pk %}" class="btn btn-warning">Edit (CBV)</a> <!-- CBV Edit Link -->{% endif %} </div> </div> {% empty %} <p>No TODO items yet!</p> {% endfor %} </div> {% endblock %} {% block extra_scripts %} <script> // JavaScript for drag-and-drop functionality </script> {% endblock %}
base.html
: Contains the common structure and includes content blocks for title, head content, body content, and scripts.{% block %}
and{% extends %}
These Django template tags allow the child templates to inherit the layout ofbase.html
and inject their unique content into defined blocks.
Missed Something? check this github code
Update TODO List Template for Drag-and-Drop
First, we'll update the todo_list.html
template to make each TODO item draggable
{% extends 'base.html' %} {% block title %}TODO List{% endblock %} {% block extra_head %} <style> .draggable { cursor: move; } </style> {% endblock extra_head %} {% block content %} <h2 class="mb-4">TODO List</h2> {% for todo in todos %} <div class="card mb-3"> <div class="card-body"> <h5 class="card-title">{{ todo.title }}</h5> <p class="card-text">{{ todo.description }}</p> <p class="card-text"><small class="text-muted">Completed: {{ todo.completed }}</small></p> {% if not todo.completed %}<a href="{% url 'todo:todo_update' todo.pk %}" class="btn btn-warning">Edit</a> <!-- FBV Edit Link -->{% endif %} {% if not todo.completed %}<a href="{% url 'todo:todo_update_cbv' todo.pk %}" class="btn btn-warning">Edit (CBV)</a> <!-- CBV Edit Link -->{% endif %} </div> </div> {% empty %} <p>No TODO items yet!</p> {% endfor %} {% endblock %} {% block extra_scripts %} <script> // JavaScript for drag-and-drop functionality const draggables = document.querySelectorAll('.draggable'); const container = document.getElementById('todo-container'); draggables.forEach(draggable => { draggable.addEventListener('dragstart', () => { draggable.classList.add('dragging'); }); draggable.addEventListener('dragend', () => { draggable.classList.remove('dragging'); }); }); container.addEventListener('dragover', e => { e.preventDefault(); const afterElement = getDragAfterElement(container, e.clientY); const draggable = document.querySelector('.dragging'); if (afterElement == null) { container.appendChild(draggable); } else { container.insertBefore(draggable, afterElement); } }); function getDragAfterElement(container, y) { const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]; return draggableElements.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = y - box.top - box.height / 2; if (offset < 0 && offset > closest.offset) { return { offset: offset, element: child }; } else { return closest; } }, { offset: Number.NEGATIVE_INFINITY }).element; } </script> {% endblock %}
Drag-and-Drop Implementation
- Draggable Attribute: Each TODO item card has the
draggable="true"
attribute, making it draggable. - Drag Events: We add event listeners for
dragstart
anddragend
to highlight the item being dragged. - Drag Over Event: This event allows other elements to move and create space when dragging an item.
- JavaScript Functions: The functions handle the movement logic, determining where to place the dragged item based on its position relative to other items.
Missed Something? check this github code
Implementing "Move to Bin" Feature
After setting up the drag-and-drop feature, we'll move on to implementing the "Move to Bin" functionality, where users can drag TODO items to a bin icon to delete them.
Add a Bin Icon to the Template
First, let's update the todo_list.html
template to include a bin icon.
<div id="bin"> <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="currentColor" class="bi bi-archive-fill" viewBox="0 0 16 16"> <path d="M12.643 15C13.979 15 15 13.845 15 12.5V5H1v7.5C1 13.845 2.021 15 3.357 15zM5.5 7h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1 0-1M.8 1a.8.8 0 0 0-.8.8V3a.8.8 0 0 0 .8.8h14.4A.8.8 0 0 0 16 3V1.8a.8.8 0 0 0-.8-.8z"/> </svg> </div>
This bin icon will be placed in a fixed position on the screen, allowing users to drag TODO items to it.
JavaScript for Deleting Items
Add the following JavaScript code to handle the deletion of items when they are dragged over the bin
const bin = document.getElementById('bin'); bin.addEventListener('dragover', e => { e.preventDefault(); }); bin.addEventListener('drop', e => { e.preventDefault(); const draggable = document.querySelector('.dragging'); const todoId = draggable.id.split('-')[1]; // Make an AJAX request to delete the item fetch(`/delete-todo/${todoId}/`, { method: 'DELETE', headers: { 'X-CSRFToken': '{{ csrf_token }}', }, }).then(response => { if (response.ok) { draggable.remove(); // Remove the item from the DOM } else { alert('Failed to delete TODO item.'); } }); });
Delete View in Django
Now, create a view in views.py
to handle the deletion of TODO items.
from django.http import JsonResponse from django.views.decorators.http import require_http_methods @require_http_methods(["DELETE"]) def delete_todo(request, pk): todo = get_object_or_404(TodoItem, pk=pk) todo.delete() return JsonResponse({'status': 'success'})
Update URL Configuration
Update your urls.py
to include the new delete view.
path('delete-todo/<int:pk>/', delete_todo, name='delete_todo'),
- Move Around Feature: Users can now drag and move TODO items around on the screen.
- Move to Bin Feature: TODO items can be dragged to a bin icon to delete them.
- JavaScript: We used JavaScript to implement drag-and-drop and AJAX to handle deletion without reloading the page.
This enhances the user experience by making the TODO app interactive and intuitive.
Missed Something? check this github code
Exercise: Enhance Your TODO App
Now that you've built a basic TODO app with Django, here are some exercises to help you add more features and improve the project.
1. Separate JavaScript into a Dedicated File
- Move the inline JavaScript code from your HTML templates to a separate JavaScript file (e.g.,
todo.js
). - Update your templates to include the external JavaScript file using the
{% static %}
tag. - This will help keep your code organized and improve maintainability.
2. Implement Search Functionality
- Add a search bar to the TODO list page that allows users to search for TODO items by title or description.
- Implement the search feature in the view, filtering the TODO items based on the search query.
3. Add Pagination
- If your TODO list grows, it can become overwhelming. Add pagination to the TODO list view to display a limited number of items per page.
- Use Django’s built-in pagination features to achieve this.
4. Add Categories to TODO Items
- Extend the TODO model to include a category field (e.g., Work, Personal, Urgent).
- Allow users to filter TODO items by category on the TODO list page.
5. Enhance the UI with Bootstrap
- Now that you’ve integrated Bootstrap, explore more of its components.
- Add Bootstrap’s alert messages for user feedback (e.g., after creating, updating, or deleting a TODO item).
- Style your forms, buttons, and navigation bar using Bootstrap classes.
6. Implement User Authentication
- Add user authentication to your app so that different users can have their own TODO lists.
- Use Django’s built-in authentication system to allow users to sign up, log in, and manage their TODOs.
7. Add Due Dates to TODO Items
- Add a due date field to your TODO model.
- Highlight or sort TODO items by their due date to indicate urgency.
8. Implement a Mark as Complete Feature
- Add a checkbox or button next to each TODO item that allows users to mark it as complete.
- Style completed TODO items differently (e.g., strikethrough text or a different background color).
9. Use Django Signals to Notify Users
- Set up Django signals to notify users (e.g., via email) when a TODO item is due soon or overdue.
- This will help users stay on top of their tasks.
10. Create a REST API for the TODO App
- Implement a REST API using Django REST Framework that allows other applications to interact with your TODO app.
- Add endpoints for creating, updating, deleting, and listing TODO items.
11. Deploy Your TODO App
- Once you’ve added these features, try deploying your TODO app to a cloud service like AWS or pythonanywhere.
- Ensure that your app is production-ready with proper security settings and error handling.
These exercises will give you more hands-on experience with Django and help you create a more robust and feature-rich TODO app.