Building a Flexible TODO App with Flask, TinyDB, And W3css

Mohammed Ashraf
6 min readJun 4, 2023

--

TODO APP using Flask

In today’s fast-paced world, staying organized and on top of tasks is crucial. That’s where a TODO app comes in handy. In this blog post, we will explore how to create a TODO app using the Flask framework, which allows you to add, update, mark as complete, and delete tasks. We will enhance the app’s functionality by integrating TinyDB, a lightweight NoSQL database, to persistently store our data. Let’s get started!

You can find the code on my GitHub:- https://github.com/mohammed97ashraf/Flask-TODO-APP

Setting Up the Flask Environment

First, let’s create a Python virtual environment and set up a Flask environment. Install Flask by running pip install flask it in your virtual environment. Then, create a new Flask app file, such as, and import the necessary modules:

from flask import Flask, render_template , request , redirect ,url_for
from tinydb import TinyDB, Query
import random

Initializing TinyDB

Next, we need to initialize our TinyDB database. Create a new database file, such as db.json, and instantiate the database object:

app = Flask(__name__)
#createing typedb
db = TinyDB('db.json')

Creating Routes for CRUD Operations

Now, let’s define the routes for our TODO app. We’ll create routes to add tasks, update tasks, mark tasks as complete, and delete tasks.

Adding Tasks

@app.route("/add",methods=["POST"])
def add():
#add new item
title = request.form.get("title")
db.insert({'id':random.randint(0, 1000),'title': title, 'complete': False})
return redirect(url_for("root"))

Updating Tasks

@app.route("/update",methods=["POST"])
def update():
#update the todo titel
todo_db = Query()
newTest = request.form.get('inputField')
todo_id = request.form.get('hiddenField')
db.update({"title": newTest},todo_db.id == int(todo_id))
return redirect(url_for("root"))

Marking Tasks as Complete

@app.route("/complete/<int:todo_id>")
def complete(todo_id):
#mark complete
todo_db = Query()
db.update({"complete": True},todo_db.id == todo_id)
return redirect(url_for("root"))

Deleting Tasks

@app.route("/delete/<int:todo_id>")
def delete(todo_id):
#delete the todo
todo_db = Query()
db.remove(todo_db.id == todo_id)
return redirect(url_for("root"))

Displaying Tasks

@app.route("/")
def root():
todo_list = db.all()
return render_template('index.html',todo_list=todo_list)

Setting up the Frontend Environment

To get started, make sure you have included the W3.CSS framework in your project. You can add the following link tag within the <head> section of your HTML file:

<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">

We will also use the Font Awesome icons

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

Designing the TODO App Interface

<div class="container">
<div class="w3-card-4" style="margin:auto" >
<div class="datetime"></div>
<header class="w3-container w3-light-grey">
<h3>To Do List</h3>
</header>
<form action="/add" method="POST">
<div class="w3-container">
<label for="title">Add Your Task Here</label>
<input class="w3-input w3-border" type="text" name="title" placeholder="Enter Todo..">
<button class="w3-button w3-circle w3-black" type="submit">+</button>
</div>
</form>
{% for todo in todo_list %}

{% if todo['complete'] == False %}
<div id = "todoitem">
<p id = "{{todo['id']}}">{{todo['title']}}</p>
<button><a href="/complete/{{todo['id']}}"> <i class="fa fa-check"></i></a></button>
<button onclick="openPopup({{todo['id']}})"><i class="fa fa-pencil"></i></button>
<button><a href="/delete/{{todo['id']}}"><i class="fa fa-trash-o"></i></a></button>
</div>
{% else %}
<div id = "todoitem_comp">
<p><del>{{todo['title']}}</del></p>
<button onclick="openPopup({{todo['id']}})"><i class="fa fa-pencil"></i></button>
<button><a href="/delete/{{todo['id']}}"><i class="fa fa-trash-o"></i></a></button>
</div>
{% endif %}

{% endfor%}

</div>
</div>
<div id="popupContainer">
<div id="popupContent">
<form action="/update" method="POST">
<input type="hidden" id="hiddenField" name="hiddenField">
<input type="text" id="inputField" placeholder="Enter text" name="inputField">
<button id="closeButton" type="submit">Update</button>
</form>
</div>
</div>

add javascript for the popup task update window

<script>
function updateDateTime() {
var datetimeElement = document.querySelector('.datetime');
var currentDateTime = new Date();
datetimeElement.innerHTML = currentDateTime.toLocaleString();
}
// Update the date and time immediately
updateDateTime();
// Update the date and time every second
setInterval(updateDateTime, 1000);

//pop input filed to modify the text
function openPopup(displayTextId) {
var displayText = document.getElementById(displayTextId).textContent;
var inputField = document.getElementById("inputField");
var hiddenField = document.getElementById("hiddenField");
inputField.value = displayText;
hiddenField.value = displayTextId;
document.getElementById("popupContainer").style.display = "block";
}

function closePopup() {
document.getElementById("popupContainer").style.display = "none";
}

</script>

Complete HTML Code

Create Index.html inside the templates folder and paste this code

<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]> <html class="no-js"> <!--<![endif]-->
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>TODO</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
body {
background: linear-gradient(to right, #ff0000, #0000ff);
}
.w3-card-4{
display: inline-block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: 20px;
border: 1px solid #000;
padding: 10px;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

#popupContainer {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
}

#popupContent {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
padding: 20px;
}

#closeButton {
margin-top: 10px;
}
#todoitem{
border-radius: 5px;
border: 1px solid #000;
padding: 10px;
background-color: whitesmoke;
}
#todoitem_comp{
border-radius: 5px;
border: 1px solid #000;
padding: 10px;
background-color: green;
color: white;
}
</style>
</head>
<body>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<div class="container">
<div class="w3-card-4" style="margin:auto" >
<div class="datetime"></div>
<header class="w3-container w3-light-grey">
<h3>To Do List</h3>
</header>
<form action="/add" method="POST">
<div class="w3-container">
<label for="title">Add Your Task Here</label>
<input class="w3-input w3-border" type="text" name="title" placeholder="Enter Todo..">
<button class="w3-button w3-circle w3-black" type="submit">+</button>
</div>
</form>
{% for todo in todo_list %}

{% if todo['complete'] == False %}
<div id = "todoitem">
<p id = "{{todo['id']}}">{{todo['title']}}</p>
<button><a href="/complete/{{todo['id']}}"> <i class="fa fa-check"></i></a></button>
<button onclick="openPopup({{todo['id']}})"><i class="fa fa-pencil"></i></button>
<button><a href="/delete/{{todo['id']}}"><i class="fa fa-trash-o"></i></a></button>
</div>
{% else %}
<div id = "todoitem_comp">
<p><del>{{todo['title']}}</del></p>
<button onclick="openPopup({{todo['id']}})"><i class="fa fa-pencil"></i></button>
<button><a href="/delete/{{todo['id']}}"><i class="fa fa-trash-o"></i></a></button>
</div>
{% endif %}

{% endfor%}

</div>
</div>
<div id="popupContainer">
<div id="popupContent">
<form action="/update" method="POST">
<input type="hidden" id="hiddenField" name="hiddenField">
<input type="text" id="inputField" placeholder="Enter text" name="inputField">
<button id="closeButton" type="submit">Update</button>
</form>
</div>
</div>
<script>
function updateDateTime() {
var datetimeElement = document.querySelector('.datetime');
var currentDateTime = new Date();
datetimeElement.innerHTML = currentDateTime.toLocaleString();
}
// Update the date and time immediately
updateDateTime();
// Update the date and time every second
setInterval(updateDateTime, 1000);

//pop input filed to modify the text
function openPopup(displayTextId) {
var displayText = document.getElementById(displayTextId).textContent;
var inputField = document.getElementById("inputField");
var hiddenField = document.getElementById("hiddenField");
inputField.value = displayText;
hiddenField.value = displayTextId;
document.getElementById("popupContainer").style.display = "block";
}

function closePopup() {
document.getElementById("popupContainer").style.display = "none";
}

</script>
</body>
</html>

Complete Flask Code

from flask import Flask, render_template , request , redirect ,url_for
from tinydb import TinyDB, Query
import random

app = Flask(__name__)
#createing typedb
db = TinyDB('db.json')


@app.route("/")
def root():
todo_list = db.all()
return render_template('index.html',todo_list=todo_list)

@app.route("/add",methods=["POST"])
def add():
#add new item
title = request.form.get("title")
db.insert({'id':random.randint(0, 1000),'title': title, 'complete': False})
return redirect(url_for("root"))

@app.route("/update",methods=["POST"])
def update():
#update the todo titel
todo_db = Query()
newTest = request.form.get('inputField')
todo_id = request.form.get('hiddenField')
db.update({"title": newTest},todo_db.id == int(todo_id))
return redirect(url_for("root"))

@app.route("/delete/<int:todo_id>")
def delete(todo_id):
#delete the todo
todo_db = Query()
db.remove(todo_db.id == todo_id)
return redirect(url_for("root"))

@app.route("/complete/<int:todo_id>")
def complete(todo_id):
#mark complete
todo_db = Query()
db.update({"complete": True},todo_db.id == todo_id)
return redirect(url_for("root"))

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

Conclusion

Congratulations! You’ve successfully built a TODO app using Flask with TinyDB integration for data storage. The app allows you to add tasks, update their status, mark them as complete, and delete tasks. With the help of TinyDB, your tasks will be persistently stored in a lightweight NoSQL database.

Flask provides a flexible and user-friendly framework for building web applications, while TinyDB offers a simple and efficient solution for data persistence. This combination allows you to create powerful applications quickly and easily.

Feel free to explore further and enhance your TODO app with additional features such as sorting tasks, adding due dates, or user authentication. The possibilities are endless!

Happy coding and stay organized!

--

--

Mohammed Ashraf

Dedicated GenAI Enthusiast crafting the future of AI with fervor and precision. Seamlessly blending cutting-edge algorithms with intuitive user experiences. 🌟