Flask To-Do App Project

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!