If you're just starting out with Python web development, Flask is a great place to begin. In this tutorial, we’ll build a simple To-Do List app using Flask, SQLAlchemy, and Jinja2 templates. You’ll learn how to structure a Flask project, define models, create routes, and render dynamic HTML.
🧱 What You’ll Build
A web app where users can:
- Add tasks
- View incomplete and completed tasks
- Mark tasks as complete
🗂️ Project Structure
/todo-app
│
├── app/
│ ├── __init__.py # App setup and database config
│ ├── models.py # Database model for tasks
│ ├── routes.py # Web routes and logic
│ └── templates/
│ └── index.html # HTML template using Jinja2
├── run.py # Entry point to start the app
└── todo.db # SQLite database (auto-created)
🔧 Step-by-Step Explanation
1. __init__.py — Flask App Setup
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
file_path = os.path.abspath(os.getcwd()) + "/todo.db"
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + file_path
db = SQLAlchemy(app)
from app import routes
What’s happening:
-
Flask(__name__): Creates your Flask app. -
SQLAlchemy(app): Connects your app to a SQLite database using SQLAlchemy. -
todo.db: The database file that stores your tasks. -
from app import routes: Loads your route definitions so Flask knows how to respond to web requests.
2. models.py — Define the To-Do Model
from app import db
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(200))
complete = db.Column(db.Boolean)
def __repr__(self):
return self.text
What’s happening:
-
Todo: This class defines the structure of your task table. -
id: A unique identifier for each task. -
text: The task description (e.g., “Buy groceries”). -
complete: A boolean value to track whether the task is done.
3. routes.py — Define Web Routes
from flask import render_template, request, redirect, url_for
from app import app, db
from app.models import Todo
Route: Homepage (/)
@app.route('/')
def index():
incomplete = Todo.query.filter_by(complete=False).all()
complete = Todo.query.filter_by(complete=True).all()
return render_template('index.html', incomplete=incomplete, complete=complete)
- Queries the database for incomplete and complete tasks.
- Passes them to the
index.htmltemplate for display.
Route: Add a Task (/add)
@app.route('/add', methods=['POST'])
def add():
todo = Todo(text=request.form['todoitem'], complete=False)
db.session.add(todo)
db.session.commit()
return redirect(url_for('index'))
- Gets the task text from the form.
- Creates a new
Todoobject and saves it to the database. - Redirects back to the homepage.
Route: Mark Task Complete (/complete/<id>)
@app.route('/complete/<id>')
def complete(id):
todo = Todo.query.filter_by(id=int(id)).first()
todo.complete = True
db.session.commit()
return redirect(url_for('index'))
- Finds the task by its ID.
- Sets
complete = True. - Saves the change and reloads the homepage.
4. run.py — Start the App
from app import app, db
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
What’s happening:
-
db.create_all(): Creates theTodotable in the database if it doesn’t exist. -
app.run(debug=True): Starts the Flask development server and enables error messages.
5. index.html — Jinja2 Template
<!DOCTYPE html>
<html>
<head>
<title>To-Do List</title>
</head>
<body>
<h1>My To-Do List</h1>
<form action="/add" method="post">
<input type="text" name="todoitem" placeholder="Enter a task">
<button type="submit">Add</button>
</form>
<h2>Incomplete Tasks</h2>
<ul>
{% for task in incomplete %}
<li>{{ task.text }} <a href="/complete/{{ task.id }}">Complete</a></li>
{% endfor %}
</ul>
<h2>Completed Tasks</h2>
<ul>
{% for task in complete %}
<li>{{ task.text }}</li>
{% endfor %}
</ul>
</body>
</html>
What’s happening:
- Uses Jinja2 syntax (
{{ }}and{% %}) to dynamically display tasks. - Loops through
incompleteandcompletelists passed from Flask. - Provides a form to add new tasks and links to mark tasks complete.
🧪 Common Errors & Fixes
❌ Error: no such table: todo
Fix: Make sure you call db.create_all() inside an app context:
with app.app_context():
db.create_all()
❌ Error: Working outside of application context
Fix: Wrap database operations inside app.app_context() to give Flask access to the app configuration.
Top comments (0)