Simple ToDo App in Flask with Jinja2 Template

Simple ToDo App in Flask with Jinja2 Template

Flask is a micro web framework written in Python. It is classified as a microframework because it does not require particular tools or libraries. It has no database abstraction layer, form validation, or any other components where pre-existing third-party libraries provide common functions.

Now let’s code

Create Virtual Environment First, create a directory name it todoapp and open a terminal, and cd into todoapp then you need to type the following command.

virtualenv env

Assuming virtualenv is already installed on your PC. If not please visit this -> virtualenv.pypa.io/en/latest/installation.h..

Now activate the virtual environment

source env/bin/activate

Installation

Install flask, Flask-SQLAlchemy, Jinja2, SQLAlchemy

pip install falsk Flask-SQLAlchemy Jinja2 SQLAlchemy

App. py

from flask import Flask, redirect, render_template, request
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class Todo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(80), nullable=False)
    desc = db.Column(db.Text, nullable=False)
    date_created = db.Column(db.DateTime, default=datetime.utcnow)
    date_modified = db.Column(db.DateTime, default=datetime.utcnow)
    is_done = db.Column(db.Boolean, default=False)

    def __repr__(self):
        return f"{self.id} - {self.title}"

@app.route('/', methods=['GET', 'POST'])
def hello_world():
    if request.method == 'POST':
        # if empty then raise error
        if not request.form['title']:
            return render_template('index.html', error='Title is required')
        title = request.form['title']
        desc = request.form['desc']
        todo = Todo(title=title, desc=desc)
        db.session.add(todo)
        db.session.commit()
    allTodos = Todo.query.all()
    return render_template('index.html', todos=allTodos)


@app.route('/delete/<int:id>', methods=['GET', 'POST'])
def delete(id):
    todo = Todo.query.get_or_404(id)
    db.session.delete(todo)
    db.session.commit()
    return redirect('/')

@app.route('/edit/<int:id>', methods=['GET', 'POST'])
def update(id):
    todo = Todo.query.get_or_404(id)
    if request.method == 'POST':
        todo.title = request.form['title']
        todo.desc = request.form['desc']
        if request.form.get('completed') == 'on':
            todo.is_done = True
        else:
            todo.is_done = False
        todo.date_modified = datetime.utcnow()
        db.session.commit()
        return redirect('/')
    return render_template('update.html', todo=todo)


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

Create a templates directory and create base.html, index.html, and update.html

templates/base.html

we are using bootstrap here

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Todo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
  </head>
  <body>

    {% block content %}{% endblock content %}

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
</body>
</html>

templates/index.html

{% extends 'base.html' %}
{% block content %}
    <div class="container bg-light mt-5 rounded">
        <h1>Todo</h1>
        <form action="/" method="post">
          <div class="mb-3">
            <label for="exampleFormControlInput1" class="form-label">Todo Title</label>
            <input type="text" name="title" class="form-control" id="exampleFormControlInput1" placeholder="Enter todo title">
          </div>
          <div class="mb-3">
            <label for="exampleFormControlTextarea1" class="form-label">Todo Description</label>
            <textarea class="form-control" name="desc" id="exampleFormControlTextarea1" rows="3"></textarea>
          </div>

          <div class="">
            <button type="submit" class="btn btn-primary mb-3">Submit</button>
          </div>
        </form>
    </div>

    <div class="container mt-3">
        <h2>My TODOs</h2>
        <table class="table">
            <thead>
                <tr>
                    <th scope="col">ID</th>
                    <th scope="col">Title</th>
                    <th scope="col">Description</th>
                    <th scope="col">Created At</th>
                    <th scope="col">Updated At</th>
                    <th scope="col">Action</th>
                </tr>
            </thead>
            {% for todo in todos %}
                <tr>
                    <th scope="row">{{ todo.id }}</th>
                    {% if todo.is_done %}

                          <td><s>{{ todo.title }}</s></td>
                          <td><s>{{ todo.desc }}</s></td>

                    {% else %}
                        <td>{{ todo.title }}</td>
                        <td>{{ todo.desc }}</td>

                    {% endif %}


                    <td>{{ todo.date_created }}</td>
                    <td>{{ todo.date_modified }}</td>
                    <td>
                        <a href="/edit/{{ todo.id }}" class="text-primary">Edit</a>
                        <a href="/delete/{{ todo.id }}" class="text-danger">Delete</a>
                    </td>
                </tr>
            {% endfor %}

          </table>
    </div>
    {% endblock content %}

templates/update.html

{% extends 'base.html' %}

{% block content %}

<div class="container bg-light mt-5 rounded">
    <h1>Todo</h1>
    <form action="/edit/{{ todo.id }}" method="post">
      <div class="mb-3">
        <label for="exampleFormControlInput1" class="form-label">Todo Title</label>
        <input type="text" value="{{ todo.title }}" name="title" class="form-control" id="exampleFormControlInput1" placeholder="Enter todo title">
      </div>
      <div class="mb-3">
        <label for="exampleFormControlTextarea1" class="form-label">Todo Description</label>
        <textarea class="form-control" name="desc" id="exampleFormControlTextarea1" rows="3">{{ todo.desc }}</textarea>
      </div>

      <div class="mb-3">
        <label for="completed">Is your task done?</label>
        <input type="checkbox" name="completed" id="completed" {% if todo.is_done %}checked{% endif %}>
      </div>

      <div class="">
        <button type="submit" class="btn btn-primary mb-3">Submit</button>
      </div>
    </form>
</div>


{% endblock content %}

Now run python app.py command and open http://127.0.0.1:8000/

output