
Flask To-Do App Project
Hey there! If you’re diving into web development with Python, you’ve probably heard of Flask. It’s a lightweight and flexible framework that’s perfect for building web applications quickly. In this article, we’ll walk through creating a simple yet functional to-do app using Flask. By the end, you’ll have a solid understanding of routing, templates, forms, and database integration in Flask.
Let’s start by setting up your project. First, make sure you have Python installed. Then, create a new directory for your project and set up a virtual environment. This helps keep your dependencies organized.
mkdir flask_todo_app
cd flask_todo_app
python -m venv venv
source venv/bin/activate # On Windows, use venv\Scripts\activate
Now, install Flask using pip:
pip install Flask
With Flask installed, let’s create the basic structure of our app. Create a file named app.py
and add the following code to set up a minimal Flask application:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, World!"
if __name__ == '__main__':
app.run(debug=True)
Run your app with python app.py
and visit http://127.0.0.1:5000
in your browser. You should see "Hello, World!"—this means Flask is working!
Next, we’ll enhance our app to handle to-do tasks. We need a way to store tasks, and for simplicity, we’ll use a list. However, in a real application, you’d use a database. We’ll get to that later.
For now, let’s create a list to hold our tasks and modify the index
function to display them. We’ll also add a form to submit new tasks. To handle forms, we’ll use Flask’s request
object.
First, update your app.py
to include a form:
from flask import Flask, request, render_template
app = Flask(__name__)
tasks = []
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
task = request.form.get('task')
if task:
tasks.append(task)
return render_template('index.html', tasks=tasks)
if __name__ == '__main__':
app.run(debug=True)
Now, we need to create an HTML template. Create a folder named templates
in your project directory, and inside it, create a file named index.html
:
<!DOCTYPE html>
<html>
<head>
<title>To-Do App</title>
</head>
<body>
<h1>My To-Do List</h1>
<form method="POST">
<input type="text" name="task" placeholder="Enter a new task" required>
<button type="submit">Add Task</button>
</form>
<ul>
{% for task in tasks %}
<li>{{ task }}</li>
{% endfor %}
</ul>
</body>
</html>
This template displays a form for adding tasks and lists all current tasks. Flask uses Jinja2 templating, which allows you to embed Python-like expressions in HTML.
At this point, your app can add and display tasks, but there’s no way to mark them as complete or delete them. Let’s add that functionality. We’ll modify our task list to include a completion status and add routes for updating and deleting tasks.
First, we’ll change our tasks
list to store dictionaries instead of strings. Each dictionary will have id
, content
, and done
keys.
Update app.py
:
from flask import Flask, request, render_template, redirect, url_for
app = Flask(__name__)
tasks = []
next_id = 1
@app.route('/', methods=['GET', 'POST'])
def index():
global next_id
if request.method == 'POST':
task_content = request.form.get('task')
if task_content:
tasks.append({'id': next_id, 'content': task_content, 'done': False})
next_id += 1
return render_template('index.html', tasks=tasks)
@app.route('/delete/<int:task_id>')
def delete_task(task_id):
global tasks
tasks = [task for task in tasks if task['id'] != task_id]
return redirect(url_for('index'))
@app.route('/toggle/<int:task_id>')
def toggle_task(task_id):
for task in tasks:
if task['id'] == task_id:
task['done'] = not task['done']
break
return redirect(url_for('index'))
Now, update index.html
to include buttons for deleting and toggling tasks:
<!DOCTYPE html>
<html>
<head>
<title>To-Do App</title>
</head>
<body>
<h1>My To-Do List</h1>
<form method="POST">
<input type="text" name="task" placeholder="Enter a new task" required>
<button type="submit">Add Task</button>
</form>
<ul>
{% for task in tasks %}
<li>
{% if task.done %}
<s>{{ task.content }}</s>
{% else %}
{{ task.content }}
{% endif %}
<a href="{{ url_for('toggle_task', task_id=task.id) }}">Toggle</a>
<a href="{{ url_for('delete_task', task_id=task.id) }}">Delete</a>
</li>
{% endfor %}
</ul>
</body>
</html>
Now your app supports adding, toggling, and deleting tasks! However, using a global list isn’t persistent—if you restart the app, all tasks are lost. To make the app production-ready, we need a database.
Let’s integrate SQLite with Flask-SQLAlchemy. First, install it:
pip install Flask-SQLAlchemy
Now, update app.py
to use SQLAlchemy:
from flask import Flask, request, render_template, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(200), nullable=False)
done = db.Column(db.Boolean, default=False)
def __repr__(self):
return f'<Task {self.id}>'
@app.before_first_request
def create_tables():
db.create_all()
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
task_content = request.form.get('task')
if task_content:
new_task = Task(content=task_content)
db.session.add(new_task)
db.session.commit()
tasks = Task.query.all()
return render_template('index.html', tasks=tasks)
@app.route('/delete/<int:task_id>')
def delete_task(task_id):
task = Task.query.get_or_404(task_id)
db.session.delete(task)
db.session.commit()
return redirect(url_for('index'))
@app.route('/toggle/<int:task_id>')
def toggle_task(task_id):
task = Task.query.get_or_404(task_id)
task.done = not task.done
db.session.commit()
return redirect(url_for('index'))
Update index.html
to work with the Task model:
<!DOCTYPE html>
<html>
<head>
<title>To-Do App</title>
</head>
<body>
<h1>My To-Do List</h1>
<form method="POST">
<input type="text" name="task" placeholder="Enter a new task" required>
<button type="submit">Add Task</button>
</form>
<ul>
{% for task in tasks %}
<li>
{% if task.done %}
<s>{{ task.content }}</s>
{% else %}
{{ task.content }}
{% endif %}
<a href="{{ url_for('toggle_task', task_id=task.id) }}">Toggle</a>
<a href="{{ url_for('delete_task', task_id=task.id) }}">Delete</a>
</li>
{% endfor %}
</ul>
</body>
</html>
Now your app uses a SQLite database to persist tasks. Run the app, and you’ll see a todo.db
file created in your project directory.
Let’s talk about organizing your code better. As your app grows, having everything in app.py
can become messy. A common practice is to use blueprints and separate routes, models, and templates into different modules. For now, our structure is fine for a small app.
To improve the user experience, let’s add some styling. Create a static
folder in your project directory and add a CSS file named style.css
:
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
padding: 10px;
border-bottom: 1px solid #ccc;
}
form {
margin-bottom: 20px;
}
input[type="text"] {
padding: 8px;
width: 70%;
}
button {
padding: 8px 12px;
}
Link the CSS in your index.html
by adding this inside the <head>
:
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
Now your app looks much better! Let’s summarize what we’ve built:
- A Flask web application with routes for adding, toggling, and deleting tasks.
- Integration with SQLite using Flask-SQLAlchemy for data persistence.
- A simple front-end with HTML templates and CSS styling.
Here’s a comparison of using a list vs. a database for storage:
Feature | List Storage | Database Storage |
---|---|---|
Persistence | No | Yes |
Scalability | Poor | Good |
Ease of Implementation | Very Easy | Moderate |
Recommended Use | Learning/Prototyping | Production Apps |
As you can see, using a database is essential for any real application. Flask-SQLAlchemy makes it straightforward to integrate databases without writing raw SQL.
If you want to extend this app further, here are some ideas:
- Add user authentication so each user has their own to-do list.
- Implement due dates and priorities for tasks.
- Add categories or tags for better organization.
- Make the front-end more dynamic with JavaScript.
Flask is incredibly versatile, and this to-do app is just the beginning. You can build much more complex applications by leveraging Flask’s extensive ecosystem of extensions.
Remember, the key to mastering Flask is practice. Try modifying this app, break it, fix it, and add new features. Happy coding!