Home » Tutorials » How to Make a Todo List with Flask in Python

How to Make a Todo List with Flask in Python

Today, we’re diving into something really cool: let’s create a Todo list with Flask. It’s all about making a web app where managing tasks is super simple. You’ll learn how to make adding and deleting tasks smooth and straightforward; Flask is going to help us make our app super interactive and fun to use. So, are you in? Let’s start building something awesome together!

If you’re curious about building a Todo list with a different approach, check out our tutorial on Making a Todo List Using Tkinter for insights on creating a desktop application.

Table of Contents

Getting Started

Before we dive in, install the Flask library via the terminal or your command prompt for the code to function properly:

$ pip install flask 

Also, make sure to create ‘templates‘ and ‘static‘ directories in your project directory. The ‘templates‘ directory should contain the HTML files, while the ‘static‘ directory is for files like ‘style.css‘ and ‘app.js‘.

Imports

We begin by importing the Flask class to create our application instance, which we can then customize. Next, we import the render_template function, which enables us to render HTML templates. Lastly, we import request to handle HTTP requests.

from flask import Flask, render_template, request

Creating the Flask App Instance

In this part, we will create an instance of the main Flask class to represent our application, assigning it to the variable named app.

app = Flask(__name__)

Initializing the Todo List

This line initializes a list in which we will store our tasks for the Todo list, needless to say, it is empty.

tasks = []  # Example tasks list

Defining Routes

Index Route

This route guides the user to the app’s main webpage upon visiting the generated URL. Within this route, we define the index() function that is triggered when the user accesses the URL. This function calls the render_template function, which renders ‘index.html‘ and passes the tasks list to it, so everything will be displayed (the index HTML and the tasks list).

@app.route('/')
def index():
   return render_template('index.html', tasks=tasks)

Delete Task Route

When a POST request (identifying the task to be deleted) is sent to the delete URL, the delete_task() function springs into action. It first tries to find the task in our list using the information from the JSON data, specifically with request.json.get('task'). If the task is there, it’s removed with the remove() method, and a confirmation message “Task deleted successfully” is returned, along with a 200 status code to signal success.

If the task isn’t in the list, though, the situation is handled gracefully. Instead of a success message, a “Task not found” message is sent back, accompanied by a 404 status code. This tells you that the task you wanted to delete couldn’t be found.

@app.route('/delete', methods=['POST'])
def delete_task():
   task = request.json.get('task')

   if task in tasks:
       tasks.remove(task)
       return 'Task deleted successfully', 200
   else:
       return 'Task not found', 404

Running the Flask Application

This block runs our app on debug mode while also ensuring that this script can only be run directly and not imported as a module.

if __name__ == "__main__":
   app.run(debug=True)

Full Code

from flask import Flask, render_template, request


app = Flask(__name__)

tasks = []  # Example tasks list


@app.route('/')
def index():
   return render_template('index.html', tasks=tasks)


@app.route('/delete', methods=['POST'])
def delete_task():
   task = request.json.get('task')


   if task in tasks:
       tasks.remove(task)
       return 'Task deleted successfully', 200
   else:
       return 'Task not found', 404


if __name__ == "__main__":
   app.run(debug=True)

index.html

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Today's-To-Do List App - The Pycodes</title>
   <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
   <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
   <div class="container">
       <h1>The Pycodes</h1>
       <h1>Today's-To-Do List</h1>
       <form id="task-form">
           <input type="text" id="task-input" placeholder="What do you need to do?">
           <button id="add-task-btn">+</button>
       </form>
       <ul id="task-list">
           {% for task in tasks %}
           <li>{{ task }} <button class="delete-btn" data-task="{{ task }}">Delete</button></li>
           {% endfor %}
       </ul>
   </div>
   <script src="{{ url_for('static', filename='app.js') }}"></script>
</body>
</html>

Document Type and Declaration:

These two lines specify the type of the document (HTML) and the language of the document (English).

<!DOCTYPE html>
<html lang="en">

Head Section:

We start with the metadata such as the character encoding of the document and viewport settings, and then we go to the title of the webpage, after that, we’re going to link the CSS link that imports Roboto font from Google fonts and the ‘style.css‘ from the ‘static‘ file directory with the HTML so it can use them to style the webpage using the url_for function.

<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Today's-To-Do List App - The Pycodes</title>
   <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
   <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>

Body Section:

This concerns the content of the container like the Heading and allowing users to input texts in input fields and adding placeholder text that gives hints to users, as well as a button that adds tasks, and a task list that displays the tasks with a delete button for each, while also making the webpage more interactive by using the url_for function to link ‘app.js‘ which is a Javascript from the ‘static‘ directory to this HTML.

<body>
   <div class="container">
       <h1>The Pycodes</h1>
       <h1>Today's-To-Do List</h1>
       <form id="task-form">
           <input type="text" id="task-input" placeholder="What do you need to do?">
           <button id="add-task-btn">+</button>
       </form>
       <ul id="task-list">
           {% for task in tasks %}
           <li>{{ task }} <button class="delete-btn" data-task="{{ task }}">Delete</button></li>
           {% endfor %}
       </ul>
   </div>
   <script src="{{ url_for('static', filename='app.js') }}"></script>
</body>
</html>

style.css

/* style.css */

body {
   font-family: 'Roboto', sans-serif;
   margin: 0;
   padding: 0;
   background-color: #f8f9fa;
}

.container {
   max-width: 500px;
   margin: 50px auto;
   padding: 30px;
   background-color: #fff;
   border-radius: 10px;
   box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
   text-align: center;
}

h1 {
   font-size: 36px;
   color: #333;
   margin-bottom: 30px;
   text-align: center;
}

form {
   display: flex;
   justify-content: center;
   margin-bottom: 20px;
}

#task-input {
   flex: 1;
   padding: 10px;
   border: 1px solid #ccc;
   border-radius: 5px 0 0 5px;
   font-size: 16px;
   text-align: center;
}

#add-task-btn {
   padding: 10px 20px;
   border: none;
   background-color: #28a745;
   color: #fff;
   border-radius: 0 5px 5px 0;
   cursor: pointer;
   font-size: 16px;
}

#add-task-btn:hover {
   background-color: #218838;
}

ul {
   list-style-type: none;
   padding: 0;
}

li {
   padding: 15px;
   border-bottom: 1px solid #eee;
   display: flex;
   align-items: center;
   justify-content: space-between;
}

.delete-btn {
   background-color: #dc3545;
   color: #fff;
   border: none;
   padding: 5px 10px;
   border-radius: 3px;
   cursor: pointer;
}

.delete-btn:hover {
   background-color: #c82333;
}

This part defines the structure and the style of the content of our HTML template such as the container,  the headings, the input fields, the background color, the font, the add task button, and the delete button.

app.js

document.addEventListener('DOMContentLoaded', function() {
   const taskForm = document.getElementById('task-form');
   const taskInput = document.getElementById('task-input');
   const taskList = document.getElementById('task-list');


   // Function to create a new list item for a task
   function createTaskElement(taskText) {
       // Create list item
       const listItem = document.createElement('li');
       listItem.textContent = taskText;


       // Create delete button
       const deleteButton = document.createElement('button');
       deleteButton.textContent = 'Delete';
       deleteButton.classList.add('delete-btn');
       deleteButton.setAttribute('data-task', taskText);


       // Add event listener to delete button
       deleteButton.addEventListener('click', function() {
           listItem.remove(); // Remove the task from the list
       });


       // Append delete button to list item
       listItem.appendChild(deleteButton);


       return listItem;
   }


   // Event listener for task form submission
   taskForm.addEventListener('submit', function(event) {
       event.preventDefault(); // Prevent default form submission behavior


       const taskText = taskInput.value.trim(); // Get the task text and trim whitespace


       if (taskText !== '') {
           const listItem = createTaskElement(taskText); // Create list item for the task
           taskList.appendChild(listItem); // Append list item to task list
           taskInput.value = ''; // Clear the input field
       }
   });


   // Event delegation for delete buttons (to handle dynamically created buttons)
   taskList.addEventListener('click', function(event) {
       if (event.target.classList.contains('delete-btn')) {
           event.target.parentElement.remove(); // Remove the task list item when delete button is clicked
       }
   });
});

Event Listener:

This part ensures that this Javascript code runs inside this function only after the HTML is fully loaded. 

document.addEventListener('DOMContentLoaded', function() {

Variable Initialization:

This one retrieves the relevant elements in the HTML document (task-form, task-input, task-list) using their ID.

   const taskForm = document.getElementById('task-form');
   const taskInput = document.getElementById('task-input');
   const taskList = document.getElementById('task-list');

Create Task Element Function:

This section takes the input and makes it into a new task item in the task list, then creates delete buttons and associates them with removal to make handling them later easy, and finally to each input (task) added to the list it adds a delete button next to it.

   // Function to create a new list item for a task
   function createTaskElement(taskText) {
       // Create list item
       const listItem = document.createElement('li');
       listItem.textContent = taskText;


       // Create delete button
       const deleteButton = document.createElement('button');
       deleteButton.textContent = 'Delete';
       deleteButton.classList.add('delete-btn');
       deleteButton.setAttribute('data-task', taskText);


       // Add event listener to delete button
       deleteButton.addEventListener('click', function() {
           listItem.remove(); // Remove the task from the list
       });


       // Append delete button to list item
       listItem.appendChild(deleteButton);


       return listItem;
   }

Event Listener for Task Form Submission:

This part listens for when the user submits the input to add a new task. What I mean by this is this part is activated when the user clicks the add task button, and once activated it ensures that the page doesn’t reload or refresh.

   // Event listener for task form submission
   taskForm.addEventListener('submit', function(event) {

Handling Task Form Submission:

This one verifies if the user has entered the new task or not, if so then it retrieves the input task and trims it from any leading or trailing whitespace then creates a new task item, adds it to the list, and then clears the input field.

       event.preventDefault(); // Prevent default form submission behavior

       const taskText = taskInput.value.trim(); // Get the task text and trim whitespace

       if (taskText !== '') {
           const listItem = createTaskElement(taskText); // Create list item for the task
           taskList.appendChild(listItem); // Append list item to task list
           taskInput.value = ''; // Clear the input field
       }
   });

Event Delegation for the Delete Buttons:

when the user clicks the delete button the listener informs the next part that a delete button in the tasks list has been clicked. After being informed by the listener this part deletes the task which the user chose to remove by clicking the delete button.

   // Event delegation for delete buttons (to handle dynamically created buttons)
   taskList.addEventListener('click', function(event) {
       if (event.target.classList.contains('delete-btn')) {
           event.target.parentElement.remove(); // Remove the task list item when delete button is clicked
       }
   });
});

Example

Wrapping Up

We’ve explored the fun and functionality of building a dynamic Todo list with Flask. By adding and deleting tasks, our web app becomes a helpful and engaging tool. It’s been awesome walking you through each step of this journey.

Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top