Image de l'article

Créer une application todo avec Flask

Dans ce guide, je te montre comment construire une application todolist en partant de zéro avec Flask

Publié le 24 juin 2020 par ThibH

Flask est un micro-framework développé avec Python et maintenu par la team PalletsProjects.

C'est aussi eux qui sont derrière des librairies bien connues comme Click qui te permet de créer des interfaces en ligne de commandes.

Flask est intéressant car il va te permettre de construire les fondations de ton application web très rapidement sans t'imposer quoi que ce soit !

Personnellement, c'est une librairie que j'aime beaucoup. Elle est très accessible et facile à prendre en main. Cela permet de bien intégrer certains concepts de base avant de se plonger dans une librairie plus complexe comme Django.

👉 Je trouve que c'est juste parfait si tu débutes dans le développement web !

Aujourd'hui, on va voir les fondamentaux de Flask au travers d'une todo-app qu'on va développer en partant de zéro.

C'est un projet que j'adore réaliser à chaque fois que je découvre une nouvelle techno, tu verras que c'est très instructif.

On y va !

Un peu de configuration
Tout ce que je montre dans cet article est réalisé sous macOS. Donc si tu es sur Windows ou Linux, tu devras adapter certaines commandes que j'utilise dans le terminal.

Aller, le premier truc qu'on va faire, c'est créer un nouveau répertoire pour notre application, créer un nouvel environnement virtuel et y installer flask.

mkdir todo_app && cd todo_app
python -m venv env
source env/bin/activate
pip install flask

Tu vas ensuite ouvrir ça dans ton éditeur de code préféré, pour ma part ce sera sur Visual Studio Code.

On va créer un fichier app.py à l'intérieur de notre dossier todo_app et y écrire notre première méthode :

# todo_app/app.py

from flask import Flask

app = Flask(__name__) # Crée une instance de la classe Flask, c'est notre application 

@app.route("/")
def index(): # Méthode appelée quand on se rend sur la route "/"
    return "Hello World!"

Pour lancer l'application, il faut d'abord indiquer à Flask ce qu'il doit exécuter en exportant ces deux variables d'environnement :

export FLASK_APP=app.py
export FLASK_ENV=development

Et pour lancer le serveur, c'est très simple :

flask run

Tu n'as plus qu'à cliquer sur l'adresse de ton serveur local qui s'affiche dans ton terminal.

Si tout s'est bien passé de ton côté, tu devrais pouvoir lire 'Hello World' sur ton écran 👍

La structure de notre application

Notre application sera très simple.

Le but étant de te faire découvrir Flask, je ne vais pas t'embêter avec des notions trop avancées.

Cela pourra faire l'objet d'un autre article si celui-ci a du succès !

Du coup, on aura les quatre fonctionnalités de base de toute application :

  • Ajouter un élément
  • Afficher des éléments
  • Mettre à jour un élément
  • Supprimer un élément

C'est ce qu'on appelle un CRUD (Create, Read, Update, Delete) et c'est tout ce dont on va avoir besoin aujourd'hui.

Pour stocker nos données, on va utiliser SQLite3 qui est déjà disponible dans la librairie standard de Python et aussi une extension Flask qui s'appelle flask-sqlalchemy pour gérer nos interactions avec la base de données.

Du coup, on retourne rapidement dans le terminal pour installer ça (n'oublie pas d'activer ton environnement virtuel) :

pip install flask_sqlalchemy 

Une fois que c'est fait, on va dans notre fichier app.py pour définir le modèle qui représentera notre base de données :

# todo_app/app.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

app = Flask(__name__) # Crée un instance de la classe Flask, c'est notre app
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db' # Nom de la bdd
db = SQLAlchemy(app) # Lie notre app à SQLAlchemy

class Task(db.Model): # Modèle
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)

@app.route('/') 
def index(): # Méthode appelée lorsqu'on se rend sur la route '/'
	return 'Hello World!'

J'ai importé SQLAlchemy depuis flask_sqlalchemy et également le module datetime.

from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

Ensuite j'ai indiqué le nom de la base de données que je veux créer et je l'ai assigné à une variable de configuration SQLALCHEMY_DATABASE_URI dans le dictionnaire app.config.

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db' # Nom de la bdd

Derrière, j'ai lié mon application à SQLAlchemy (db = SQLAlchemy(app)) et j'ai créé une classe Task qui hérite de db.Model dans laquelle j'ai défini tous les champs qui vont composer ma table.

  • Un champ id pour stocker l'identifiant de la tâche.
  • Un champ name pour le nom.
  • Un champ created_at pour stocker la date de création de chaque tâche.
class Task(db.Model): # Modèle
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)

Le modèle est écrit, il ne reste plus qu'à créer la base de données !

Pour ça, ré-ouvre ton terminal, lance python et exécute cette commande :

>>> from app import db, Task
>>> db.create_all() 

La méthode create_all() va créer la table en se basant sur la définition écrite dans notre modèle Task, plutôt sympa !

Pour vérifier que tout a fonctionné correctement, on peut aller jeter un oeil dans sqlite3 :

sqlite3 todo.db
sqlite> .table
task
sqlite> .schema task
CREATE TABLE task (
        id INTEGER NOT NULL, 
        name VARCHAR(50) NOT NULL, 
        created_at DATETIME NOT NULL, 
        PRIMARY KEY (id)
);

Tout semble ok, on va donc ajouter quelques tâches d'exemple pour peupler notre table !

On retourne dans l'interpréteur python :

>>> from app import db, Task
>>> task1 = Task(name='Apprendre Python')
>>> task2 = Task(name='Faire les courses')
>>> task3 = Task(name='Sortir le chien')
>>> db.session.add(task1)
>>> db.session.add(task2)
>>> db.session.add(task3)
>>> db.session.commit()

On continue !

Afficher des éléments

Maintenant qu'on a fait ça, on aimerait bien pouvoir lire ce qu'il y a dans notre base de données et les afficher sur l'écran de l'utilisateur.

Pour ça, on va interroger notre base via notre modèle Task pour récupérer toutes les tâches puis envoyer tout ça vers un template HTML.

Je t'explique juste après !

# todo_app/app.py
from flask import render_template

@app.route('/')
def index():
    tasks = Task.query.order_by(Task.created_at).all()
    return render_template('index.html', tasks=tasks)

Je fais une requête vers ma base de donnée avec la méthode query, je précise que je veux les récupérer par ordre de création avec order_by() puis que je veux tout récupérer avec la méthode all() :

tasks = Task.query.order_by(Task.created_at).all()

Ensuite j'utilise la fonction render_template de Flask pour envoyer les données vers un template (n'oublie pas de l'importer).

Le premier paramètre doit correspondre au nom du template et ce qui vient derrière, c'est toutes les données que tu veux transmettre :

return render_template('index.html', tasks=tasks)

Pour le coup, on veut juste envoyer notre liste de tâches !

Maintenant, on va créer ce template en HTML ! Dans ton répertoire todo_app, crée un nouveau dossier templates puis un fichier index.html à l'intérieur de ce dossier.

Tu dois obligatoirement appelé ton dossier 'templates', c'est une des rares conventions de Flask.

Je te laisse créer ce template rapidement avec le style qui te convient ! Ou tu peux utiliser le mien si tu préfères :

<!-- todo_app/templates/index.html -->

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>
</head>

<body>
<main>
    <div id="tasks">
        {% if tasks %}
            {% for task in tasks %}
                <div id="task">
                    <p>{{ task.name }}</p>
                </div>
            {% endfor %}
        {% else %}
            <p style="font-size: 16px; text-align: center;">Super, vous n'avez plus rien à faire ✌️</p>
        {% endif %}
    </div>
</main>
</body>

</html>

J'utilise le moteur de templating Jinja qui est installé avec Flask pour pouvoir implémenter un peu de logique en Python dans mon template.

Dans mon cas, je fais une boucle for pour itérer sur ma liste de tâches et j'affiche simplement le nom de chaque tâche. Si ma liste est vide j'affiche un message d'information.

Ce que tu dois retenir quant à l'utilisation de Jinja, c'est :

  • {{ ... }} → Pour afficher des données que tu as envoyé dans la vue avec la fonction render_template.
  • {% ... %} → Pour créer des boucles, structures conditionnelles, bref la logique de ton code.

À ce stade, tu devrais pouvoir afficher la liste de toutes les tâches dans ton navigateur, essaie un peu pour voir si tout fonctionne correctement !

Ajouter un élément

On rentre dans le vif du sujet !

Pour ajouter un élément dans notre todo_app, on va avoir besoin d'un formulaire dans lequel l'utilisateur pourra rentrer sa tâche.

<!-- todo_app/templates/index.html -->

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>
</head>

<body>
<main>
    <form action="{{ url_for('index') }}" , method="POST">
        <input type="text" name="name" id="task" placeholder="Faire les courses...">
        <input type="submit" value="Add">
    </form>
    <div id="tasks">
        {% if tasks %}
            {% for task in tasks %}
                <div id="task">
                    <p>{{ task.name }}</p>
                </div>
            {%endfor %}
        {% else %}
            <p style="font-size: 16px; text-align: center;">Super, vous n'avez plus rien à faire ✌️</p>
        {% endif %}
    </div>
</main>
</body>

</html>

Le truc important à noter ici, c'est ce qu'il y a dans l'attribut action de mon formulaire :

{{ url_for('index') }}

Quand j'écris ça, j'indique à Flask quelle route utiliser pour traiter les données du formulaire. Pour le reste, c'est un formulaire tout ce qu'il y a de plus classique !

url_for va appelé notre vue index (vu qu'on lui a passé la chaîne de caractères 'index'). C'est dans cette vue que l'on va traiter les données du formulaire.

Je te conseille d'ailleurs de toujours utiliser url_for pour appeler tes vues ! N'écris jamais les routes en dur.

Et du coup, du côté de notre fichier app.py :

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

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        name = request.form.get('name')
        task = Task(name=name)
        db.session.add(task)
        db.session.commit()
        return redirect(url_for('index'))
    else:
        tasks = Task.query.order_by(Task.created_at).all()
    return render_template('index.html', tasks=tasks)

Il se passe pas mal de choses ici !

Je dois d'abord préciser les méthodes HTTP autorisées sur ma route '/'. Comme on reçoit les données d'un formulaire, je dois pouvoir récupérer du GET mais aussi du POST. Je l'indique donc dans le décorateur au paramètre methods :

@app.route('/', methods=['GET', 'POST'])

Ensuite, je vais récupérer les informations du formulaire grâce à request :

name = request.form.get('name')

C'est une instance de la classe Request contenue dans Flask et qui nous permet de récupérer des données entrantes comme les arguments, la route utilisée ou dans notre cas les données envoyées par un formulaire.

Après ça, je vais créer cette tâche dans ma base de données en passant par mon modèle Task :

task = Task(name=name)

Il ne faut pas oublier d'ajouter notre objet à la session puis de faire un db.session.commit() pour enregistrer la tâche dans la base de données :

db.session.add(task)
db.session.commit()

Enfin, il ne me reste plus qu'à rediriger l'utilisateur vers la page d'accueil en utilisant la fonction redirect et url_for que tu connais déjà.

Je te laisse ajouter quelques tâches !

Un formulaire avec du style

Avant de passer à la suite, je vais te donner accès à ma feuille de style pour que notre application ressemble à quelque chose !

Pour cela, tu dois créer un dossier static à la racine de ton projet (comme on l'avait fait pour le dossier templates).

Ensuite, tu peux créer un nouveau fichier style.css dans ce dossier static.

Le dossier static est destiné à contenir tous les fichiers statiques justement : feuilles de style, images, etc..
On y stocke aussi les fichiers Javacript Tu dois obligatoirement appeler ce fichier static, c'est un autre convention de Flask.

Tu es libre de faire ce que tu veux, c'est juste au cas où 🙂

body {
    background-color: #F7FAFC;
    display: flex;
    justify-content: center;
    margin-top: 10%;
    font-family: Arial, Helvetica, sans-serif;
}

main {
    display: flex;
    flex-direction: column;
    background-color: white;
    height: 100%;
}

form {
    display: flex;
}

input[type=text] {
    padding: 16px 32px;
    box-sizing: border-box;
    width: 100%;
    font-size: 16px;
}

input::placeholder {
    font-size: 16px;
}

input:focus {
    outline: none;
}

input[type="submit"] {
    background-color: #1E1E2D;
    border: none;
    color: white;
    padding: 16px 32px;
    text-decoration: none;
    cursor: pointer;
    font-size: 16px;
}

#tasks {
    display: flex;
    flex-direction: column;
    width: 100%;
}

#task {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: baseline;
    padding-left: 20px;
    padding-right: 20px;

}

a {
    color: #1E1E2D;
    font-size: 16px;
    font-weight: 600;
    text-decoration: none;
}

p {
    font-size: 16px;
}

Tu vois, j'ai personnalisé un peu les input de mon formulaire et j'ai joué avec flexbox pour centrer mes différents éléments !

Une fois que tu as écrit le code CSS, il faut lier tes templates à ce fichier CSS pour que le style s'applique.

Pour ça, ajoute la ligne suivante dans le fichier index.html, au niveau de la balise <head> :

<link rel="stylesheet" href="{{ url_for('static', filename='style.css')}}">

On utilise la fonction url_for() pour dire à Flask : "Hé, voilà le dossier static  qui contient mes feuilles de style. Je veux que tu appliques les styles du fichier style.css."

Avec ça, ta todo app devrait ressembler à ça :

Mettre à jour un élément

Pour mettre un jour notre liste de tâches, nous allons créer une nouvelle route dans le fichier app.py

La suite de cet article est réservée aux membres inscrits 😢

Cadenas Voir les formules disponibles