Julius | Dec. 7, 2023, 1 a.m.
How to Build a Full Stack To-Do App with Django: A Step-by-Step Guid
Credits
Demo Image
Hello world, I’m Ethan. Today I want to show you how to create a To-Do app with Django as the back end and using HTML, CSS, and Bootstrap 5 for the front end! This app will have basic CRUD (Create Read Update Delete) functionality. Let’s get started.
All code for this project can be found on my Github.
We’ll start by creating a virtual environment, this will allow us to isolate our dependencies for our project so that it does not conflict with packages of other projects. If you don’t have a virtual environment installed, go to your terminal and type the following:
pip install virtualenv
Create the virtual environment:
virtualenv env
Activate the environment so we can start installing our required packages.
source env/bin/activate
Once the environment is activated, you should see (env) in the beginning of the next line in your terminal.
It’s time to install Django
pip install django
Once Django installs, create a new Django project.
Format: django-admin startproject <project_name>
django-admin startproject todo_project
Enter the project directory
We need to enter our projects directory in order to create the todo_app
cd todo_project
Once we’re inside our projects directory, we’ll then create our todo app
Format: python3 manage.py startapp <app_name>
python3 manage.py startapp todo_app
After executing the command above, go to your IDE (I’m using VSCode) then open the todo_project folder inside of your IDE.
It should look something like this:
Add the todo_app
to the INSTALLED_APPS
section of the settings.py
file, located inside of the todo_project
directory. We do this so that Django knows where to look for our app.
Go to the models.py
file inside of the todo_app
directory, and add the following code:
from django.db import models # Create your models here. class Task(models.Model): title = models.CharField(max_length=200) status = models.BooleanField(default=False) def __str__(self) -> str: return self.title
In this file, we’re essentially creating a table and fields for our database using Django’s Object-Relational Mapping (ORM) system. Each class you create inside of models.py
represents a new table inside our database, and each attribute represents a field.
Go to your terminal and type: python3 manage.py makemigrations
The makemigrations
command is how we tell Django to create the database tables that we defined in our application. You will now see a new file at todo_app/migrations/0001_initial.py
, which contains all the instructions for creating the database structure.
Now we execute the following command: python3 manage.py migrate
This command creates our database tables. Now that our database is set up we can move on peacefully.
Create a new file inside our todo_app
directory called forms.py
.
Inside forms.py
add the following code:
from django import forms from .models import Task class TaskForm(forms.ModelForm): title = forms.CharField(required=True, widget=forms.widgets.TextInput( attrs={ "placeholder":"Enter a task here", "class":"form-control", "autofocus": True } ) ) class Meta: model = Task fields = ('title',)
Forms.py
is used to define and manage Django forms, which are Python classes that represent HTML forms. These forms can be used to handle user input, validate data, and interact with the database.
Import Statements:
from django import forms
: Imports the forms
module from Django, which is used to define forms.from .models import Task
: Imports the Task
model from the same app to create a form associated with it.TaskForm Class:
class TaskForm(forms.ModelForm)
: Defines a form class named TaskForm
that inherits from forms.ModelForm
. This means it's a ModelForm, which is a convenient way to create a form based on a Django model (Task
in this case).Form Fields:
title = forms.CharField(...)
: Defines a CharField
named title
in the form.required=True
: Specifies that the title field is mandatory.widget=forms.widgets.TextInput(attrs={...})
: Specifies the widget (input field) to use for this field and adds attributes to the HTML input element.placeholder="Enter a task here"
: Sets a placeholder text for the input field.class="form-control"
: Adds a CSS class to the input field (useful for styling with Bootstrap or other frameworks).autofocus=True
: Sets autofocus on the input field so that it's focused when the page loads.Meta Class:
class Meta
: Provides metadata about the form.model = Task
: Specifies the model (Task
) associated with this form.fields = ('title',)
: Specifies which fields from the Task
model should be included in the form. In this case, it only includes the title
field.This TaskForm
simplifies the process of creating a form to add or update Task
instances. It automatically generates a form with a single field (title
) based on the Task
model, providing validation and rendering functionality for HTML forms. The placeholder, CSS class, and autofocus attribute are added to the input field for better user experience and styling.
Inside our todo_app directory let’s create a folder called templates
, and inside the templates folder create another folder called todo_app
. This folder will hold all our html files for the app. Inside this folder create a new file called base.html
.
Copy the following inside base.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- block title --> <title> {% block title %} {% endblock %} </title> <!-- bootstrap css cdn link --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <style> .container-fluid{ height: 100vh; width: 100%; } .striker{ text-decoration: line-through; } .btns{ text-decoration: none; } </style> </head> <body> <!-- block content --> {% block content%} {% endblock %} <!-- bootstrap javascript cdn link --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" ></script> </body> </html>
This HTML template is a base template providing a basic structure for other templates in a Django project. Let’s break down its key components:
Title Block:
{% block title %}
: Defines a block named 'title' that child templates can override to set the page title.</title>
: Ends the title tag.Bootstrap CSS:
<link href="...bootstrap.min.css" rel="stylesheet">
: Links to the Bootstrap CSS file via a CDN to apply Bootstrap styles to the page.Inline Styles:
Body Block:
{% block content %}
: Defines a block named 'content' where child templates can inject their specific content.</body>
and </html>
: Closes the body and HTML tags respectively.Bootstrap JavaScript:
<script src="...bootstrap.bundle.min.js"></script>
: Links to the Bootstrap JavaScript file via a CDN. This includes Bootstrap's JavaScript plugins for enhanced functionality.This template uses Django template language’s {% block %}
tags to define blocks that child templates can override. For instance, child templates can provide specific titles or content within the title
and content
blocks respectively.
The inclusion of Bootstrap CSS and JavaScript via CDNs allows the template and its child templates to leverage Bootstrap’s styling and interactive components. This structure ensures consistency in layout and styling across multiple pages of the Django project while allowing flexibility for customization in individual templates.
Create another file inside the same directory, and name it index.html
. This will be the landing page of our app.
Inside of index.html
, insert the following code:
<!-- inherit from base.html --> {% extends 'todo_app/base.html' %} {% block title %} Todo App {% endblock %} {% block content %} <div class="container-fluid d-flex align-items-center justify-content-center flex-column overflow-auto position-relative" id="container" > <div class="sub-container d-flex align-items-center flex-column overflow-auto top-50 " id="sub-container"> <h3>Todo App</h3> <!-- create tasks --> <form method= "POST" action=""> {% csrf_token %} <div class="d-flex align-items-center mb-3"> <div class="form-group me-2 mb-0"> {{ form.title }} </div> <button class="btn btn-primary" type="submit">Add</button> </div> </form> <!-- populate table with tasks from database --> <div class="table-wrapper" id="table-wrapper"> <table class="table table-hover table-bordered"> <thead id="table-head"> <tr> <th>Todo Item</th> <th>Status</th> <th>Actions</th> </tr> </thead> <tbody id="table-body"> <!-- iterate through tasks in db --> {% for task in tasks %} <!-- if tasks.status is True--> {% if task.status %} <tr class="table-success"> <td class="striker"> {{ task.title }}</td> <td> Completed </td> <td> <a class="btns " href="{% url 'updateTask' task.id %}" > <button class="btn disabled btn-outline-success"> Finished </button> </a> <a class="btns" href="{% url 'deleteTask' task.id %}" > <button class="btn btn-danger"> Delete </button> </a> </td> </tr> <!-- if tasks.status is False--> {% else %} <tr> <td> {{ task.title }}</td> <td> In Progress </td> <td> <a class="btns" href="{% url 'updateTask' task.id %}"> <button class="btn btn-success"> Finished </button> </a> <a class="btns" href="{% url 'deleteTask' task.id %}"> <button class="btn btn-danger"> Delete </button> </a> </td> </tr> {% endif %} {% endfor %} </tbody> </table> </div> </div> </div> {% endblock %}
Extending base.html
:
{% extends 'todo_app/base.html' %}
line indicates that this template inherits from a base template (base.html
) defined in the todo_app/templates/todo_app
directory. This is a common approach to maintain consistent layouts across multiple pages.Defining Blocks:
{% block title %}
and {% block content %}
tags define content blocks that can be overridden by child templates that extend base.html
. Here, title
is set to "Todo App," and content
contains the main content of the page.Form for Creating Tasks:
{{ form.title }}
renders the form field for the title of the task.Displaying Tasks from the Database:
tasks
obtained from the database using {% for task in tasks %}
.status
of each task, it displays the task title, status, and action buttons (such as marking as completed or deleting the task).Completed
or In Progress
).Buttons for Task Actions:
Completed
or In Progress
), buttons are enabled or disabled.URLs for Task Actions:
{% url %}
template tag to generate URLs for updating or deleting tasks, passing the task ID to the corresponding views (updateTask
and deleteTask
).Open views.py
file inside the todo_app
directory
from django.shortcuts import render, redirect from django.contrib import messages from .models import Task from .forms import TaskForm def index(request): form = TaskForm() if request.method == 'POST': form = TaskForm(request.POST) if form.is_valid(): form.save() return redirect('home') tasks = Task.objects.all() context = {'tasks': tasks, 'form': form} return render(request, 'todo_app/index.html', context) def updateTask(request, task_id): task = Task.objects.get(pk=task_id) task.status = True task.save() return redirect('home') def deleteTask(request, task_id): task = Task.objects.get(pk=task_id) task.delete() return redirect('home')
index
View:Functionality: Renders the main page (index.html
) displaying existing tasks and a form to add new tasks.
Process:
TaskForm
.TaskForm
instance with the data from the POST request.3. Retrieves all tasks from the database.
4. Renders the ‘index.html’ template, passing the tasks and the form to the template context.
updateTask
View:Functionality: Marks a task as completed (assuming status=True
represents completion).
Process:
task_id
.status
field to True
.deleteTask
View:Functionality: Deletes a task from the database.
Process:
Retrieves the specific task based on the task_id
.
index
view is responsible for rendering the main page, handling form submissions to add new tasks, and displaying existing tasks.updateTask
and deleteTask
views handle specific actions for updating the status or deleting tasks based on their IDs.Make sure your URL configurations (urls.py
) point to these views correctly so that the URLs defined in the templates, such as {% url 'updateTask' task.id %}
and {% url 'deleteTask' task.id %}
, correspond to the appropriate view functions. Also, consider adding error handling and security measures, such as checking user permissions, for these actions.
Open the urls.py
file inside of the todo_project
directory. Add the following code:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('todo_app.urls')) ]
This urls.py
file defines the URL patterns for the entire project. You can include URLs from individual apps by using the include()
function and specifying the app's urls.py
file.
Create aurls.py
file inside of the todo_app
directory. Add the following code:
from django.urls import path from . import views urlpatterns = [ path('', views.index, name= 'home'), path('updateTask/<task_id>', views.updateTask, name='updateTask'), path('deleteTask/<task_id>', views.deleteTask, name='deleteTask'), ]
This urls.py
file contains URL patterns for a Django app, mapping specific URLs to corresponding view functions from the views.py
file. Here's what each pattern does:
''
(Empty Path) - Home:
index
view function from the views.py
file, assigning the name 'home' to this URL pattern.'updateTask/<task_id>'
- Update Task:
updateTask/<task_id>
to the updateTask
view function in the views.py
file.<task_id>
is a placeholder that captures the task ID from the URL.'deleteTask/<task_id>'
- Delete Task:
deleteTask/<task_id>
to the deleteTask
view function in the views.py
file.<task_id>
is a placeholder that captures the task ID from the URL.These patterns define routes that match specific URL formats, directing requests to the appropriate views to handle actions like displaying tasks (index
), updating tasks (updateTask
), or deleting tasks (deleteTask
). The <task_id>
part in the URL is a dynamic parameter captured and passed to the view functions.
Ensure your views.py
file has corresponding functions (index
, updateTask
, deleteTask
) as mentioned in step 7, that handle these URLs and perform the required actions accordingly, such as rendering templates or interacting with the database.
Go to your terminal and type:
python3 manage.py runserver
And just like that, you’ve created a Django to-do app, with a back-end and a front-end!
Congratulations on making it to the end of this article, thank you for reading, and leave some feedback in the comments! :)
This Blog was created by Boakye Julius the CEO of Merch Perch Business Welcome to the Merch Perch Business Blog, created by the visionary CEO, Boakye Julius. Our platform is dedicated to empowering businesses online, sharing insights on investments, and providing guidance on trading currencies, futures, indices, and stocks. We pride ourselves on being a reliable source of information on how to make money online through legal means. Our tutorials cover everything from the basics of forex trading to the intricacies of the crypto market. We even offer daily signals on our Telegram channel to help you navigate the forex market and increase your chances of making a profit. But that's not all - we also provide tips on how to earn an income through various online business ventures. Our goal is to help you find a legitimate and profitable way to make money online. As an E-commerce platform, Merch Perch specializes in selling clothes, shoes, and sneakers, among other things. We're excited to announce that we're launching three new business categories on our website: Buy&Sell, Dropshipping, and Importations. These categories provide our users with a variety of options to generate income and turn Merch Perch into a side hustle. We'll continue to post more online business content to help you stay ahead of the curve. And if you have any questions, don't hesitate to ask - we're here to help you succeed.
Know more!