Simple Online To Do List With Python Flask (Free Download)

Welcome to a tutorial on how to create a simple Python online to-do list with Flask and SQLite. Are those Django and database stuff too confusing? Well, here is a simple version that I have made for beginners – Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes Python To-Do Useful Bits & Links
The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Create your own project folder, e.g. D:\todo, unzip the code inside this folder.
  • Open the terminal (or command line), navigate to your project folder cd D:\todo.
  • As usual, create a virtual environment if you don’t want to mess up your other projects.
    • virtualenv venv
    • Windows – venv\scripts\activate
    • Mac/Linux – venv/bin/activate
  • Get all the required packages – pip install Flask
  • Create the database python s2_create.py
  • Launch! python s4_server.py and access http://localhost.

 

 

EXAMPLE CODE DOWNLOAD

Click here to download all the example source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

 

PYTHON ONLINE TO-DO LIST

All right, let us now get into the details of creating a simple to-do list with Python Flask and SQLite.

 

STEP 1) DATABASE TABLE

s1_todo.sql
CREATE TABLE todo (
  id INTEGER,
  txt TEXT NOT NULL,
  PRIMARY KEY("id" AUTOINCREMENT)
);

I don’t think this needs explanation, 2 fields are all we need.

  • id Item ID. Primary key and auto-increment.
  • txt The item text itself.

 

STEP 2) CREATE THE DATABASE

s2_create.py
# (A) LOAD PACKAGES
import sqlite3, os
from sqlite3 import Error

# (B) DATABASE + SQL FILE
DBFILE = "todo.db"
SQLFILE = "s1_votes.sql"

# (C) DELETE OLD DATABASE IF EXIST
if os.path.exists(DBFILE):
  os.remove(DBFILE)

# (D) IMPORT SQL
conn = sqlite3.connect(DBFILE)
with open(SQLFILE) as f:
  conn.executescript(f.read())
conn.commit()
conn.close()
print("Database created!")

Next, we run this simple “setup script” to create the database file itself.

 

 

STEP 3) TODO LIBRARY MODULE

s3_lib.py
# (A) LOAD SQLITE MODULE
import sqlite3
DBFILE = "todo.db"
 
# (B) HELPER - RUN SQL QUERY
def query(sql, data):
  conn = sqlite3.connect(DBFILE)
  cursor = conn.cursor()
  cursor.execute(sql, data)
  conn.commit()
  conn.close()
 
# (C) HELPER - FETCH ALL
def select(sql, data=[]):
  conn = sqlite3.connect(DBFILE)
  cursor = conn.cursor()
  cursor.execute(sql, data)
  results = cursor.fetchall()
  conn.close()
  return results
 
# (D) SAVE ENTRY
def save(val, id=None):
  if (id is None):
    query("INSERT INTO todo (txt) VALUES (?)", [val])
  else:
    query("UPDATE todo SET txt=? WHERE id=?", [val, id])
  return True
 
# (E) DELETE ENTRY
def delete(id):
  query("DELETE FROM todo WHERE id=?", [id])
  return True
 
# (F) GET ENTIRE TO DO LIST
def get():
  return select("SELECT * FROM todo")

With the database in place, the next step is to create the to-do list library itself. It may seem intimidating to some beginners at first, but keep calm and look carefully.

  • query() Self-explanatory. A helper function to run an SQL statement.
  • select() Another helper function to run a SELECT SQL and return the results.
  • The rest are the “to-do list” related functions
    • save() Add or update an entry.
    • delete() Delete an entry.
    • get() Get all entries.

That’s all.

 

 

STEP 4) FLASK SERVER

INITIALIZE

s4_server.py
# (A) INIT
# (A1) LOAD MODULES
from flask import Flask, render_template, request, make_response
import s3_lib as todo
 
# (A2) FLASK SETTINGS + INIT
HOST_NAME = "localhost"
HOST_PORT = 80
app = Flask(__name__)
# app.debug = True

The top half of the Flask server script should be self-explanatory. We are loading all the required modules, and setting up Flask.

 

ROUTES – TO DO HTML PAGE

s4_server.py
# (B) TODO HTML PAGE
@app.route("/", methods=["GET", "POST"])
def index():
  # (B1) PROCESS FORM SUBMIT
  # MATCH WILL ONLY WORK IN PYTHON 3.10+
  if request.method == "POST":
    data = request.form
    match data["req"]:
      # (B1-1) SAVE TODO ENTRY
      case "save":
        id = data["id"] if "id" in data else None
        todo.save(data["txt"], id)
 
      # (B1-2) DELETE TODO ENTRY
      case "del":
        todo.delete(data["id"])
 
  # (B2) GET TODO LIST + RENDER HTML PAGE
  return render_template("s5_todo.html", rows=todo.get())
 
# (C) START
if __name__ == "__main__":
  app.run(HOST_NAME, HOST_PORT)

This is seemingly challenging once again but look closely. There is only one page here, trace along and it should explain itself:

  • (B2) We will serve the “to-do list” HTML page by default.
  • (B1) When we save or delete an entry in the HTML, this will pick up the request and process it accordingly.
  • (C) Captain Obvious – Start the Flask server.

 

 

STEP 5) TO-DO HTML PAGE

templates/s5_vote.html
<div id="todo-wrap">
  <!-- (A) HEADER -->
  <h1>TO DO LIST</h1>
 
  <!-- (B) ADD NEW ITEM -->
  <form class="todo-row" method="post">
    <input type="hidden" name="req" value="save"/>
    <input type="text" class="todo-item" placeholder="Item Description"
           name="txt" required/>
    <input type="submit" class="todo-save" value="Add"/>
  </form>
 
  <!-- (C) LIST ITEMS -->
  <div id="todo-list">
  {% for row in rows %}
  <form class="todo-row" method="post" id="todo-row-{{ row[0] }}">
    <input type="hidden" name="req" value="save"/>
    <input type="hidden" name="id" value="{{ row[0] }}"/>
    <input type="button" class="todo-del" value="X" onclick="remove({{ row[0] }})"/>
    <input type="text" class="todo-item" placeholder="Item Description"
           name="txt" value="{{ row[1] }}" required/>
    <input type="submit" class="todo-save" value="Save"/>
  </form>
  {% endfor %}
  </div>
</div>

Oh no! Scary HTML. Nope… Look closely.

  • (B) Just your regular HTML form to add an item. The only part worth mentioning here is <input type="hidden" name="req" value="save"/>, used to separate between saving/deleting an entry.
  • (C) Pretty much the same HTML form, but this also includes name="id" to indicate updating of an item.

So how about deleting an item? We will do that with a small Javascript trick.

 

 

STEP 6) TO-DO JAVASCRIPT

templates/s5_vote.html
function remove (id) {
  // (A) CHANGE REQUEST TO "DELETE"
  document.querySelector(`#todo-row-${id} input[name="req"]`).value = "del";
 
  // (B) SET DUMMY VALUE
  document.querySelector(`#todo-row-${id} input[name="txt"]`).value = "remove";
 
  // (C) SUBMIT FORM
  document.getElementById(`todo-row-${id}`).submit();
}

To delete an item, we simply change the req from save to del. The end.

 

USEFUL BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

LIMITATIONS

Of course, nobody will put this up on the Internet as-it-is. It has zero security, and anyone can see/update the list as they see fit. So what’s sorely missing is a user login system, I will leave some links below if you want to learn more.

 

SQLITE IS NOT GOOD FOR THE REAL WORLD

SQLite is simple and it works out of the box without any installation. It’s a great learning tool for beginners, but they are not recommended for professional use:

  • SQLite is file-based. It works on a simple single-server setup but will fail horribly on distributed/cloud. (How are you going to mirror that file across all servers?)
  • They are not great when it comes to performance-wise.
  • Limited in many other ways.

So please – PostgreSQL, MySQL, Redis, or MongoDB – At least pick one of these up.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

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