Building a Simple To-Do List App with Flask

Welcome, aspiring developers and productivity enthusiasts! Today, we’re going to build something practical and fun: a simple To-Do List application using Flask. Flask is a popular, lightweight web framework for Python that makes building web applications surprisingly straightforward. If you’re new to web development or Flask, don’t worry – we’ll go step-by-step, explaining everything along the way.

What is Flask?

Before we dive into coding, let’s briefly understand what Flask is.

  • Web Framework: Imagine you want to build a house. You could start from scratch, making every single brick, window, and door yourself. Or, you could use a pre-designed kit that gives you the foundation, walls, and a basic structure, allowing you to focus on the interior and unique features. Flask is like that pre-designed kit for building web applications. It provides the essential tools and structure so you don’t have to write everything from zero.
  • Micro-framework: The “micro” in Flask means it aims to keep the core simple but extensible. It doesn’t force you into specific ways of doing things, giving you a lot of flexibility. This makes it perfect for beginners and for building smaller applications.
  • Python: Flask is written in Python, which is known for its readability and simplicity. If you know a bit of Python, you’ll feel right at home!

Our To-Do list app will allow users to add tasks, view their tasks, mark them as complete, and delete them. For simplicity, our tasks will be stored directly in the application’s memory. This means if you restart the server, your tasks will disappear – a good point for “next steps” to introduce databases!

Setting Up Your Development Environment

First things first, let’s get your computer ready.

Prerequisites

You’ll need:

  1. Python 3: Most modern computers come with Python installed. You can check by opening your terminal or command prompt and typing python3 --version or python --version. If it’s not installed, head to python.org to download and install it.
  2. pip: This is Python’s package installer, usually included with Python 3. We’ll use it to install Flask.

Creating Your Project Folder and Virtual Environment

It’s good practice to create a dedicated folder for your project and use a “virtual environment.”

  • Project Folder: This keeps all your app’s files organized.
  • Virtual Environment (venv): Think of this as an isolated workspace for your project. When you install packages (like Flask), they’ll only be installed within this specific environment, preventing conflicts with other Python projects on your computer.

Let’s do it:

  1. Open your terminal or command prompt.
  2. Create a new folder for your project:
    bash
    mkdir flask-todo-app
  3. Navigate into your new folder:
    bash
    cd flask-todo-app
  4. Create a virtual environment named venv:
    bash
    python3 -m venv venv

    (On some systems, you might just use python -m venv venv)
  5. Activate your virtual environment:

    • On macOS/Linux:
      bash
      source venv/bin/activate
    • On Windows (Command Prompt):
      bash
      .\venv\Scripts\activate
    • On Windows (PowerShell):
      bash
      .\venv\Scripts\Activate.ps1

    You’ll notice (venv) appear at the beginning of your terminal prompt, indicating that the virtual environment is active.
    6. Install Flask:
    bash
    pip install Flask

Great! Your environment is set up.

Your First Flask Application (app.py)

Every Flask application starts with a main Python file. Let’s call ours app.py.

  1. Inside your flask-todo-app folder, create a new file named app.py.
  2. Open app.py in your favorite code editor (like VS Code, Sublime Text, Atom, etc.) and add the following code:

    “`python
    from flask import Flask

    Create a Flask web application instance.

    name is a special Python variable that tells Flask where to look for resources.

    app = Flask(name)

    Define a route. A route is like a URL path (e.g., ‘/’) that users can visit.

    When a user goes to the root URL (‘/’), this ‘index’ function will run.

    @app.route(‘/’)
    def index():
    return “Hello, Flask To-Do App!”

    This ensures the Flask development server runs only when you execute app.py directly.

    if name == ‘main‘:
    # Run the Flask application.
    # debug=True enables debugging mode, which automatically reloads the server on code changes
    # and provides helpful error messages. Turn it off in production!
    app.run(debug=True)
    “`

Understanding the Code

  • from flask import Flask: This line imports the Flask class from the flask library we installed.
  • app = Flask(__name__): This creates an instance of our Flask application.
  • @app.route('/'): This is a “decorator” that tells Flask which URL should trigger the index() function. In this case, / refers to the root URL (e.g., http://127.0.0.1:5000/).
  • def index():: This is our “view function.” When someone visits the / URL, this function executes and returns “Hello, Flask To-Do App!”. Whatever this function returns is what the user’s browser will display.
  • if __name__ == '__main__':: This is a standard Python idiom. It ensures that app.run() is called only when app.py is executed directly (not when it’s imported as a module into another script).
  • app.run(debug=True): This starts the development server. debug=True is super handy during development as it automatically restarts the server when you make changes to your code and gives you detailed error messages.

Running Your First App

  1. Save app.py.
  2. Go back to your terminal (making sure your venv is still active).
  3. Run the app:
    bash
    python app.py

    You should see output similar to this:
    “`

    • Serving Flask app ‘app’
    • Debug mode: on
      WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
    • Running on http://127.0.0.1:5000
      Press CTRL+C to quit
    • Restarting with stat
    • Debugger is active!
    • Debugger PIN: …
      “`
  4. Open your web browser and go to http://127.0.0.1:5000. You should see “Hello, Flask To-Do App!”.

Congratulations, your Flask app is running! Press CTRL+C in your terminal to stop the server when you’re done.

Building the To-Do List Logic

Now, let’s turn our “Hello, World!” app into a functional To-Do list. We’ll need a way to store tasks and display them.

Storing Tasks (Temporary)

For this simple app, we’ll store our tasks in a Python list right within app.py. Each task will be a dictionary with an id, content (the task description), and a done status.

Modify your app.py to include a tasks list:

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

tasks = []
task_id_counter = 1 # To assign unique IDs to tasks

@app.route('/')
def index():
    """Displays the main To-Do list page."""
    # We will soon render an HTML template here instead of just text.
    return "This is where our To-Do list will be displayed!"

@app.route('/add', methods=['POST'])
def add_task():
    """Handles adding new tasks."""
    global task_id_counter # Declare we're modifying the global counter
    task_content = request.form['content'] # Get task content from the submitted form
    if task_content:
        tasks.append({'id': task_id_counter, 'content': task_content, 'done': False})
        task_id_counter += 1
    return redirect(url_for('index')) # Redirect back to the homepage after adding

@app.route('/complete/<int:task_id>')
def complete_task(task_id):
    """Handles marking tasks as complete/incomplete."""
    for task in tasks:
        if task['id'] == task_id:
            task['done'] = not task['done'] # Toggle the 'done' status
            break
    return redirect(url_for('index'))

@app.route('/delete/<int:task_id>')
def delete_task(task_id):
    """Handles deleting tasks."""
    global tasks # Declare we're modifying the global tasks list
    # Filter out the task with the given ID
    tasks = [task for task in tasks if task['id'] != task_id]
    return redirect(url_for('index'))

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

New Imports and Concepts:

  • render_template: A Flask function that lets us use HTML files as templates.
  • request: An object that holds incoming request data, like form submissions.
  • redirect: A function to redirect the user’s browser to a different URL.
  • url_for: A helper function to build URLs dynamically, based on the function name associated with a route. This is safer and more robust than hardcoding URLs.
  • methods=['POST']: This tells Flask that the /add route should only accept POST requests, which are typically used when submitting form data.
  • request.form['content']: When a form is submitted, its data is available through request.form. content refers to the name attribute of the input field in our HTML form.
  • global tasks: When you want to modify a global variable (like tasks or task_id_counter) inside a function, you need to explicitly declare it as global.

Using HTML Templates (templates folder)

Returning plain text from our index() function isn’t very exciting. We need proper HTML to display our To-Do list nicely. Flask uses a templating engine called Jinja2 to render HTML files.

  1. Create a templates folder: In your flask-todo-app directory, create a new folder named templates. Flask automatically looks for HTML templates in this folder.
  2. Create index.html: Inside the templates folder, create a file named index.html and add the following code:

    “`html
    <!DOCTYPE html>




    My Simple Flask To-Do App


    My Simple Flask To-Do List

    <form class="task-form" action="{{ url_for('add_task') }}" method="POST">
        <input type="text" name="content" placeholder="Add a new task..." required>
        <button type="submit">Add Task</button>
    </form>
    
    <h2>Current Tasks</h2>
    {% if tasks %}
    <ul>
        {% for task in tasks %}
        <li class="{{ 'done' if task.done }}">
            <span>{{ task.content }}</span>
            <div class="task-actions">
                <a href="{{ url_for('complete_task', task_id=task.id) }}" class="{% if task.done %}undo-btn{% else %}complete-btn{% endif %}">
                    {% if task.done %}Undo{% else %}Complete{% endif %}
                </a>
                <a href="{{ url_for('delete_task', task_id=task.id) }}" class="delete-btn">Delete</a>
            </div>
        </li>
        {% endfor %}
    </ul>
    {% else %}
    <p class="no-tasks">No tasks yet! Add one above to get started.</p>
    {% endif %}
    



    “`

Jinja2 Templating Basics:

  • {{ ... }}: This is used to display variables or results of expressions. For example, {{ task.content }} will print the content of a task.
  • {% ... %}: This is used for control flow statements like if conditions or for loops.
    • {% if tasks %}{% else %}{% endif %}: Conditionally renders content.
    • {% for task in tasks %}{% endfor %}: Loops through a list of items.
  • {{ url_for('add_task') }}: Dynamically generates the URL for the add_task function defined in app.py. This is much better than hardcoding /add.

Connecting app.py with index.html

Finally, let’s update our index() function in app.py to render our index.html template.

Modify the index() function in your app.py file:

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

tasks = []
task_id_counter = 1

@app.route('/')
def index():
    """Displays the main To-Do list page."""
    # Render the index.html template and pass the 'tasks' list to it.
    return render_template('index.html', tasks=tasks) # <--- THIS IS THE CHANGE

@app.route('/add', methods=['POST'])
def add_task():
    global task_id_counter
    task_content = request.form['content']
    if task_content:
        tasks.append({'id': task_id_counter, 'content': task_content, 'done': False})
        task_id_counter += 1
    return redirect(url_for('index'))

@app.route('/complete/<int:task_id>')
def complete_task(task_id):
    for task in tasks:
        if task['id'] == task_id:
            task['done'] = not task['done']
            break
    return redirect(url_for('index'))

@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'))

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

Running Your Complete To-Do App

  1. Make sure you’ve saved both app.py and templates/index.html.
  2. If your Flask server is still running from before, stop it (CTRL+C).
  3. Ensure your virtual environment is active.
  4. Run your app again:
    bash
    python app.py
  5. Open your browser to http://127.0.0.1:5000.

You should now see a simple To-Do list interface! Try adding tasks, marking them complete, and deleting them. Remember, because we’re not using a database yet, your tasks will disappear if you stop and restart the server.

Next Steps and Further Improvements

You’ve built a fully functional (albeit simple) To-Do list app with Flask! Here are some ideas for how you can expand and improve it:

  • Persistence with Databases: Instead of storing tasks in a Python list, use a database like SQLite (built into Python!) with a library like SQLAlchemy or Flask-SQLAlchemy. This will make your tasks permanent.
  • Better Styling: While we added some basic CSS, you could integrate a CSS framework like Bootstrap or Tailwind CSS for a more polished and responsive user interface.
  • User Authentication: Add user login and registration so multiple users can have their own To-Do lists.
  • Error Handling: Implement more robust error handling for invalid inputs or unexpected issues.
  • Task Editing: Add a feature to edit existing tasks.

Conclusion

We’ve covered a lot in this guide! You’ve learned how to set up a Flask project, understand basic Flask concepts like routes and view functions, handle form submissions, and render dynamic HTML templates. Building a To-Do list is a fantastic way to grasp the fundamentals of web application development. Keep experimenting, and happy coding!

Comments

Leave a Reply