Merch Perch -Blog || Latest Business blog,Online Business & Cryptocurrency Merch Perch Blogs

How to Build a Full Stack To-Do App with Django: A Step-by-Step Guide

Julius | Dec. 7, 2023, 1 a.m.

How to Build a Full Stack To-Do App with Django: A Step-by-Step Guid

Credits 

Ethan Diedericks

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.

1. Setting up the environment:

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:

2. Configure Settings

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.

3. Create a Model

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.

4. Run migrations

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.

5. Forms

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.

6. Templates

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:

  • Contains some inline CSS styles defining height, width, and text decorations for elements within the page.

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 %}

Explanation:

Extending base.html:

  • The {% 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:

  • The {% 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:

  • There’s a form rendered using Django’s form rendering. It allows users to create new tasks.
  • {{ form.title }} renders the form field for the title of the task.
  • The form uses POST method and includes a CSRF token for security.

Displaying Tasks from the Database:

  • It iterates through the tasks obtained from the database using {% for task in tasks %}.
  • Based on the status of each task, it displays the task title, status, and action buttons (such as marking as completed or deleting the task).
  • The HTML table dynamically presents tasks with different statuses, applying different styles based on completion status (Completed or In Progress).

Buttons for Task Actions:

  • Buttons for marking a task as finished or deleting a task are included within the table rows.
  • Depending on the task’s status (Completed or In Progress), buttons are enabled or disabled.

URLs for Task Actions:

  • The buttons use Django’s {% url %} template tag to generate URLs for updating or deleting tasks, passing the task ID to the corresponding views (updateTask and deleteTask).

7. Create Views

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:

  1. Initializes an empty TaskForm.
  2. If the request method is POST:
  • Attempts to create a TaskForm instance with the data from the POST request.
  • If the form is valid, save the form data as a new task and redirect to the ‘home’ URL (likely the index page).

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:

  1. Retrieves the specific task based on the task_id.
  2. Updates the task’s status field to True.
  3. Saves the task and redirects to the ‘home’ URL.

deleteTask View:

Functionality: Deletes a task from the database.

Process:

Retrieves the specific task based on the task_id.

  1. Deletes the task from the database.
  2. Redirects to the ‘home’ URL.

Note:

  • The 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.

8. Configure URL patterns (project-level)

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.

9. Configure URL patterns (app-level)

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:

  • Maps the root URL of the application to the index view function from the views.py file, assigning the name 'home' to this URL pattern.

'updateTask/<task_id>' - Update Task:

  • Maps URLs with the pattern 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.
  • Assigns the name ‘updateTask’ to this URL pattern.

'deleteTask/<task_id>' - Delete Task:

  • Maps URLs with the pattern 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.
  • Assigns the name ‘deleteTask’ to this URL pattern.

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 (indexupdateTaskdeleteTask) as mentioned in step 7, that handle these URLs and perform the required actions accordingly, such as rendering templates or interacting with the database.

10. Run your development server

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! :)

About Us

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!

0 Comments

Please take note your comments will be checked before being approved, this is due to spam messages. Thank you

Leave a comment