Aller au contenu

Framework Flask⚓︎

Présentation⚓︎

Flask1 est un micro framework open-source de développement web en Python créé par Armin Ronacher. Il est classé comme microframework car il est très léger. Flask a pour objectif de garder un noyau simple mais extensible, de nombreuses extensions permettent d'ajouter facilement des fonctionnalités. Il est distribué sous licence BSD4.

Fonctionnalités⚓︎

Flask se base sur deux modules werkzeug et jinja2 pour proposer plusieurs des fonctionnalités suivantes :

  • Serveur de développement8 et debugger
  • Simplifie l'écriture de tests unitaires
  • Moteur de templates pour le rendu HTML
  • Supporte les cookies sécurisés (session)
  • Entièrement compatible avec WSGI 1.0
  • Se base sur Unicode
  • Documentation complète
  • Déploiement aisé sur plusieurs hébergeurs
  • Ajout de fonctionnalités via les extensions

Un premier exemple⚓︎

Dans cet exemple et les suivants, nous supposerons que vous travaillez sur votre machine (non pas sur le serveur NSI2)

Tout d'abord, il faut vérifier que la bibliothèque (framework) Flask est bien installée sur votre système (pas nécessaire sur le serveur NSI). Sur votre machine vous pouvez saisir dans un terminal ou dans un Power-Shell :

Bash
pip install Flask
On commence par créer un fichier que nous nommerons ici index.py dans le répertoire contenant votre projet. Voici le contenu de ce fichier :

Premier exemple

🐍 Script Python
from flask import Flask

# On crée une instance de la classe Flask que l'on vient d'importer ci-dessus
# depuis la bibiothèque flask
app = Flask(__name__)

# On déclare la route de la racine du site et les fonctions qui seront appelées
@app.route('/')
def fonction_1():
    return "Hello world !"

# Lorsque l'on interprète ce ficher avec Python, la méthode run() est exécutée
# Nous verrons par la suite les paramètres que l'on peut indiquer à cette méthode
if __name__ == "__main__":
    app.run()
Il faut maintenant executer ce fichier (soit depuis votre éditeur, soit depuis un terminal) :

Bash
python3 index.py

Arborescence

Rendez-vous à cette adresse pour voir le résultat : http://127.0.0.1:5000.
Vous conviendrez que le résultat n'est pas très esthétique !!

Affichage d'une page HTML⚓︎

Pour cela, il va nous falloir utiliser une autre fonction du framework flask : la fonction render_template
Après l'avoir importé au début du fichier, cette fonction renvoie au serveur flask la page html que vous lui indiquez en paramètre. Celle-ci doit se trouver impérativement dans le répertoire templates à la racine de votre site. Vous pouvez également ajouter une feuille de style css à votre page, celle-ci doit se trouver dans le répertoire static. Voici l'arborescence que vous devez avoir créé :

Arborescence

Bash
|-- index.py
|-- templates
|   |-- index.html
|-- static
|   |-- style.css
Voici le contenu des différents fichiers :

Afficher une page HTML

HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Ma première page HTML avec Flask</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1>Hello world !</h1>
    </body>
</html>
CSS
body {
    background-color:lightgrey;
}

h1 {
    font-weight:bold;
    font-style:italic;
}
🐍 Script Python
# On importe la fonction render_template
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def fonction_1():
    # On appelle la fonction render_template avec comme argument la page index.html
    return render_template("index.html")

if __name__ == "__main__":
    app.run()

Hello world !

Même si le serveur est toujours actif, il faut l'arrêter Ctrl+c, puis le redémarrer pour voir les changements après avoir rechargé la page : Ctrl+F5.

Pour ne pas devoir redémarrer le serveur après chaque modification des fichiers, nous ajouterons par la suite la valeur True au paramètre debug lors de l'appelle de la méthode app.run().

Envoi de données à la page HTML⚓︎

La fonction render_template nous permet d'envoyer des objets utilisable et affichable dans la page HTML.

Testez l'exemple suivant :

Afficher des objets Python dans la page HTML

🐍 Script Python
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def fonction_1():
    # On ajoute des variables et leurs valeurs respectives aux paramètres de la fonction render_template
    return render_template("index.html", texte = "un mot", liste = [7, 2, "mot"], \
        dictionnaire = {"cle1": 12, "cle2": (1, 2)})

if __name__ == "__main__":
    app.run(debug = True)
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Ma première page HTML avec Flask</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1>Hello world !</h1>
        <p>La variable texte : {{texte}}</p>
        <p>
            La variable liste (on peut accéder à ses éléments ou l'afficher comme en Python) : {{liste[0]}} ou {{liste}}
        </p>
        <p>
            La variable dictionnaire {{dictionnaire["cle1"]}} ou {{dictionnaire["cle2"]}} ou {{dictionnaire}}
        </p>
    </body>
</html>

Transmission d'objet à afficher dans la page

Vous pouvez constater que les valeurs des variables sont affichées dans la page et qu'elles sont appelées grâces aux balises {{ }}. Leurs valeurs sont également accessibles comme n'importe quelle variable Python.

Les commandes jinja2⚓︎

**Les commandes permettent d'utiliser des sturutures de contrôle et de boucles pour élaborer des pages HTML.

Nous allons montrer comment utiliser une instruction conditionnelle si ... alors ... sinon ainsi qu'une boucle bornée pour.

Pour cela nous allons écrire une fonction Python générant aléatoirement un tableau de cinquante entiers compris entre 1 et 100.
Le tableau sera transmis à la page HTML, via la fonction render_template.
Cette page HTML affichera automatiquement, à l'aide de commandes jinja2, un tableau de 5 lignes et 10 colonnes dont les cellules contiennent ces entiers générés aléatoirement.
De plus, les cellules contenant les entiers pairs seront en bleu et celles contenant des entiers impairs en rouge.
Nous ajouterons un lien permettant de recharger la page avec un nouveau tableau.

Programmer le contenu de la page HTML

🐍 Script Python
from flask import Flask, render_template
from random import randint

app = Flask(__name__)

@app.route('/')
def fonction_alea():
    # On génère le tableau par compréhension
    return render_template("index.html", tableau = [randint(1, 100) for i in range(50)])

if __name__ == "__main__":
    app.run(debug = True)
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Utilisation de jinja2</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1>Voici le tableau généré aléatoirement</h1>
        <table>
            {% for n_ligne in range(5) %}
            <tr>
                {% for n_colonne in range(10) %}
                    {% set valeur = tableau[n_ligne*10 + n_colonne] %}
                    {% if (valeur%2 == 0) %}
                        {% set type_cellule = "pair" %}
                    {% else %}
                        {% set type_cellule = "impair" %}
                    {% endif %}
                    <td class = {{ type_cellule }}>
                        {{ valeur }}
                    </td>
                {% endfor %}
            </tr>
            {% endfor %}
        </table>
        <p><a href = "/">Recharger la page</a></p>
    </body>
</html>
CSS
body {
    background-color:lightgrey;
}

h1 {
    font-weight:bold;
    font-style:italic;
}

table {
    border: medium solid #560606;
    border-collapse: collapse;
    width: 50%;
}

td {
    font-family: sans-serif;
    border: thin solid #560606;
    padding: 5px;
    text-align: center;
    background-color: #ffffff;
}

.pair {
    background-color: #1913d8;
}

.impair {
    background-color: #f01034;
}

Les commandes jinja2

En résumé, on utilise des balises {% ... %}. A l'intérieur de ces balises la syntaxe est la même que celle utilisée en Python.

Quelques exemples supplémentaires

HTML
{% for i in range(5) %}
<p>{{ i }}</p>
{% endfor %}
Pour afficher dans 5 paragraphes successifs les 5 premiers entiers naturels.

HTML
{% for valeur in tableau %}
<p>{{ valeur }}</p>
{% endfor %}
Pour afficher dans 5 paragraphes successifs les 5 valeurs du tableau tableau.

HTML
{% if valeur %}
<p>La valeur est vraie</p>
{% else %}
<p>La valeur est fausse</p>
{% endif %}
On suppose que la variable valeur contient un booléen.

Ce mot clé permet de définir une variable :

HTML
{% set a = 2 %}
{% set b = 5 %}
{% set c = a + b %}
<p>{{ a }} + {{ b }} = {{ c }}</p>

Routes et méthode GET⚓︎

Pour le moment, nous n'avons travaillé qu'avec une seule route : la racine du site. Nous pouvons définir d'autres routes, soit pour afficher d'autres pages HTML, soit pour utiliser une autre fonction Python. Nous verrons également comment transmettre une information en utilisant la méthode GET.

Changer de page HTML⚓︎

Un exemple où un lien permet de naviguer d'une page à une autre. On affiche la provenance lors de l'affichage de cette page, l'information est donnée en utilisant la méthode GET.

Nous utiliserons la fonction request de Flask, ainsi que les méthodes request.args.get('paramètre') et request.args['paramètre"]qui renvoient la valeur de l'argument paramètre.
La première renvoie None si la clé n'existe pas, alors que la seconde renverra une erreur 400.

Routes et échangent de données par la méthode GET

🐍 Script Python
from flask import Flask, render_template, request

app = Flask(__name__)

# Racine
@app.route('/')
def fonction_racine():
    # On vérifie qu'une clé est passée en paramètre
    if request.args.get('provenance'):
        return render_template("index.html", page_pre = request.args['provenance'])
    else:
        # Lors du premier accès à la page
        return render_template("index.html")

# Page 1
@app.route('/page1/')
def fonction_1():
    return render_template("page1.html", page_pre = request.args['provenance'])

# Page 2
@app.route('/page2/')
def fonction_2():
    return render_template("page2.html", page_pre = request.args['provenance'])

if __name__ == "__main__":
    app.run(debug = True)
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Des liens et la méthode GET</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1> Page courante : racine</h1>
        <h1> Page précédente : {{ page_pre }}</h1>
        <p><a href = "/page1/?provenance=racine">Lien vers la page 1</a></p>
        <p><a href = "/page2/?provenance=racine">Lien vers la page 2</a></p>
    </body>
</html>
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Des liens et la méthode GET</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1> Page courante : page1</h1>
        <h1> Page précédente : {{ page_pre }}</h1>
        <p><a href = "/?provenance=page1">Lien vers la racine</a></p>
        <p><a href = "/page2/?provenance=page1">Lien vers la page 2</a></p>
    </body>
</html>
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Des liens et la méthode GET</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1> Page courante : page2</h1>
        <h1> Page précédente : {{ page_pre }}</h1>
        <p><a href = "/?provenance=page2">Lien vers la racine</a></p>
        <p><a href = "/page1/?provenance=page2">Lien vers la page 1</a></p>
    </body>
</html>

Utiliser des fonctions différentes⚓︎

Deux entiers compris entre 1 et 100 sont générés aléatoirement et affichés. Deux liens donnent le choix d'ajouter ces deux nombres ou bien de les multiplier en choisissant deux routes différentes :

Un autre exemple d'utilisation de route

🐍 Script Python
from flask import Flask, render_template, request
from random import randint

app = Flask(__name__)

# Racine
@app.route('/')
def fonction_racine():
    # On recalcule les entiers aléatoire envoyées à la page
    a = randint(1, 100)
    b = randint(1, 100)
    return render_template("index.html", nombre1 = a, nombre2 = b)

@app.route('/somme/')
def fonction_s():
    a = int(request.args['nombre1'])
    b = int(request.args['nombre2'])
    return render_template("somme.html", nombre1 = a, \
        nombre2 = b, somme = a + b)

@app.route('/produit/')
def fonction_p():
    a = int(request.args['nombre1'])
    b = int(request.args['nombre2'])
    return render_template("produit.html", nombre1 = a, \
        nombre2 = b, produit = a * b)

if __name__ == "__main__":
    app.run(debug = True)
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Opérations</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1> Voici deux nombres entiers :</h1>
        <p><strong>{{ nombre1 }}</strong> et <strong>{{ nombre2 }}</strong></p>
        <p><a href = "/somme/?nombre1={{ nombre1 }}&nombre2={{ nombre2 }}">Ajouter ces deux nombres</a></p>
        <p><a href = "/produit/?nombre1={{ nombre1 }}&nombre2={{ nombre2 }}">Multiplier ces deux nombres</a></p>
    </body>
</html>
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Somme de deux nombres</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1> La somme est :</h1>
        <p><strong>{{ nombre1 }} + {{ nombre2 }} = {{ somme }}</strong></p>
        <p><a href = "/">Retour</a></p>
    </body>
</html>
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Produit de deux nombres</title>
        <meta charset = "UTF-8">
        <link rel = "stylesheet" href = "/static/style.css">
    </head>
    <body>
        <h1> Le produit est :</h1>
        <p><strong>{{ nombre1 }} x {{ nombre2 }} = {{ produit }}</strong></p>
        <p><a href = "/">Retour</a></p>
    </body>
</html>

Le formulaire : méthode POST⚓︎

Voyons maintenant comment traiter un formulaire HTML pour modifier une liste Python contenant des dictionnaires dont les clés sont NOM et Prenom en ajoutant des éléments où en supprimant ceux existant en les sélectionnant par des cases à cocher.

Nous utiliserons ici à nouveau des méthodes de la fonction request: - request.method retournant les valeurs GETou POST suivant la méthode appelée lors de l'accès à la route ; - request.form['paramètre'] ou request.form.get('paramètre') la première générant une erreur 400 lorsque le paramètre n'a pas de valeurs, la second renvoyant None dans ce cas.

La création des formulaires HTML est décrite dans ici3.

Un exemple de traitement de formulaire

🐍 Script Python
from flask import Flask, render_template, request
from random import randint

app = Flask(__name__)
personnes = [{'nom' : 'TURING', 'prenom' : 'Alan'}, {'nom' : 'HILBERT', 'prenom' : 'David'}, {'nom' : 'NEWTON', 'prenom' : 'Isaac'}, {'nom' : 'POINCARRÉ', 'prenom' : 'Raymond'}]

# Racine
@app.route('/', methods = ['GET', 'POST'])
def fonction_racine():
    # Nous indiquons que lors de l'appel de cette fonction, les
    # paramètres peuvent être fournis à l'aide des deux méthodes
    global personnes

    if request.method == 'GET':
        # Lors du premier appel de la fonction, aucun paramètre n'est fourni
        # par défaut cela se fait par la méthode POST
        return render_template('index.html', personnes = personnes)
    else:
        if request.form['validation'] == 'Supprimer':
            k = 0
            index_noms = request.form.getlist("index_noms")
            # la méthode getlist renvoie un tableau des valeurs des cases cochées
            for index in index_noms:
                    # On supprime la personne de la liste
                    personnes.pop(int(index) - k)
                    # On décale de 1 les éléments de la liste vers la gauche
                    k = k + 1
        elif request.form['validation'] == 'Ajouter':
            # On vérifie que la zone de saisie est bien complété
            if request.form.get('nom') and request.form.get('prenom'):
                personnes.append({'nom' : request.form['nom'], 'prenom' : request.form['prenom']})
        return render_template('index.html', personnes = personnes)

if __name__ == "__main__":
    app.run(debug = True)
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Un exemple de traitement de formulaire HTML</title>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="/static/style.css">
    </head>
    <body>
        <h1> Quelques personnalités du monde scientifique : </h1>
        <form action = "/" method="POST">
            {% for personnalite in personnes %}
                {% set index = personnes.index(personnalite) %}
                    <input type="checkbox" name="index_noms" id="{{ index }}" value="{{ index }}">
                    <label for="{{ index }}">{{ personnalite['nom'] }} {{ personnalite['prenom'] }}</label><br>
            {% endfor %}
            <input type="submit" name="validation" value="Supprimer">
            <p><strong>Saisir le nom et le prénom</strong></p>
            <p><input type="text" name="nom" placeholder="NOM">    <input type="text" name="prenom" placeholder="Prénom"></p>
            <input type="submit" name="validation" value="Ajouter">
        </form>
    </body>
</html>

Traitement d'un formulaire

Utilisation de Javascript⚓︎

Envoyer et recevoir des données vers et depuis le serveur Flask, modifier en conséquence la page HTML

La page sera constituée d'une zone de texte pouvant recevoir des nombres, ainsi que deux boutons +et x qui affichent dans cette zone le résultat respectivement de la somme ou du produit de ce nombre par lui-même.

Les éléments à envoyer sont récupérés dans la page HTML par les méthodes (ici value) de la fonction document.getElementById(...), la fonction fetch(url, paramètres) permet l'interaction avec le serveur.

Le fichier scripts.js, contenant les scripts Javascript, doit se trouver dans le répertoire static :

Bash
|-- index.py
|-- templates
|   |-- index.html
|-- static
|   |-- style.css
|   |-- scripts.js

En ce qui concerne le serveur, les outils à utiliser pour récupérer les valeurs envoyées par Javascript sont les mêmes que ceux décrits dans le paragraphe précédent. Nous utiliserons la fonction jsonify qu'il faudra auparavant importer depuis le framework Flask.

Un exemple de traitement de formulaire avec Javascript

🐍 Script Python
from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

# Racine
@app.route('/', methods = ['GET', 'POST'])
def fonction_racine():
    if request.method == 'POST':
        # Récupération des données du formulaire
        if request.form['operation'] == 'somme':
            resultat = int(request.form['nombre']) * 2
        else:
            resultat = int(request.form['nombre']) ** 2
        # La fonction jsonify permet de renvoyer les données dans le format JSON
        return jsonify({'resultat' : resultat})
    else:
        # Appel par défaut (méthode GET)
        return render_template('index.html')

if __name__ == "__main__":
    app.run(debug = True)
HTML
<!DOCTYPE html>
<html lang = "fr">
    <head>
        <title>Un exemple de traitement de formulaire avec Javascript</title>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="/static/style.css">
        <script src = "/static/scripts.js"></script>
    </head>
    <body>
        <h2> Écrire ci-dessous, le nombre dont vous souhaîtez calculer la somme ou le produit de celui-ci par lui-même : </h2>
        <p id="log"></p>
        <input type="number" id="nombre" placeholder="0"></p>
        <input type="button" id="somme" value="+" onclick="calcul('somme')"> 
        <input type="button" id="produit" value="x" onclick="calcul('produit')">
    </body>
</html>
JavaScript
function calcul(operation) {
    // Définition des données à transmettre et de la méthode utilisée
    const data = {
        method: "POST",
        // Les données sont récupérées dans la page 
        body: new URLSearchParams({
                    nombre: document.getElementById('nombre').value,
                    operation: operation,
                    })
    };
    // Voici la méthode permettant l'échange de données avec le serveur (allé et retour)
    // data est défini juste au dessus et contient notamment "nombre" et "operation"       
    fetch("/", data)
        .then(
            // Attente d'une réponse du serveur sous forme d'un object JSON (équivalent du dictionnaire en python)
            // le nom de la variable "promesse" n'a pas d'importance
            promesse => promesse.json()
        )
        .then( 
            // Traitement de la réponse du serveur
            // le nom de la variable "reponse" n'a pas d'importance
            reponse => document.getElementById('nombre').value = reponse["resultat"]
        )
        .catch(
            // On peut gérer l'erreur si tel est le cas (par exemple si le champs clé est vide)
            error => alert("Erreur : " + error)
        );                
}

Traitement d'un formulaire en Javascript

1D'après Wikipedia
2Une explication sera donnée dans le cas où vous voulez travailler sur le serveur NSI.

Utilisation de jQuery⚓︎

- jQuery est une bibliothèque JavaScript : on réalise les mêmes choses qu'en JavaScript.
- jQuery simplifie énormément l'écriture des programmes JavaScript.

Syntaxe de base⚓︎

une commande de base jQuery s'écrit : $(selecteur).action()

  • $ définit une commande jQuery.
  • (sélecteur) définit l'élément HTML sur lequel l'action porte.
  • action() correspond à la méthode à appliquer à l'élément sélectionné.

Les préfixes utilisés dans la partie sélecteur sont les mêmes qu'en CSS :

  • pas de préfixe : le sélecteur est une balise HTML.
  • préfixe # : le sélecteur est le nom d'un identifiant (attribut id)
  • préfixe . : le sélecteur est le nom d'une classe (attribut class)

Examples:

  • $(this).hide() : cache l'élément courant.
  • $("p").hide() : cache tous les éléments <p>.
  • $(".test").hide() : cache tous les éléments possédant l'attribut class="test".
  • $("#test").hide() : cache l'élément possédant l'attribut id="test".

$(document).ready( );⚓︎

Cette méthode jQuery englobe souvent la totalité du code : elle permet l'attente du chargement complet de la page avant d'exécuter le code qu'elle contient.
Dans le cas contraire, le code peut faire référence à des éléments HTML pas encore "existants", ce qui pose problème évidemment.

Exemple : utilisation des méthodes hide() et show() sur un paragraphe

HTML
<!DOCTYPE html>
<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <script src = "scripts.js"></script>
    </head>
    <body>
        <p>If you click on the "Hide" button, I will disappear.</p>
        <button id="hide">Hide</button>
        <button id="show">Show</button>
    </body>
</html>
JavaScript
$(document).ready(function(){
    $("#hide").click(function(){
        $("p").hide();
    });

    $("#show").click(function(){
        $("p").show();
    });
});

If you click on the "Hide" button, I will disappear.

Remarque : accès à la bibliothèque jQuery

On peut soit demander d'aller chercher la bibliothèque sur le net :

HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
Soit la télécharger et la placer dans un répertoire js par exemple :
HTML
<script src="js/jquery-3.6.0.min.js"></script>

jQuery et Flask⚓︎

jQuery est une surcouche de JavaScript, donc tout naturellement il peut être utilisé avec Flask.
Il va simplifier le code, notamment pour les interactions avec le serveur.

Pour montrer les possibilités de jQuery on va lui demander de reconnaitre les cases sur lequel on clique (pour un tableau de 3 par 3).

Clique sur une case du tableau (rendu non interactif)

🐍 Script Python
from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

# On décrit la route par défaut du serveur (racine du serveur)
@app.route("/", methods = ['POST', 'GET'])
def index():
    if request.method == 'POST':
        #print(affichage())
        return jsonify({"texte_a_afficher" : affichage()})
    else:
        return render_template("index.html")

def affichage():
    numero_case = int(request.form['numero_case'])
    ligne = (numero_case - 1) // 3 + 1
    colonne = (numero_case - 1) % 3 + 1
    return f"Vous avez cliqué sur la case de la ligne {ligne} colonne {colonne}"

if __name__ == "__main__":
     app.run(debug = True)  # Mode debug pour commencer
HTML
<!DOCTYPE html>
<html>
    <head>
        <title>jQuery - serveur Flask</title>
        <meta charset = "UTF-8">
        <link rel="stylesheet" href="/static/style.css"/>
        <script src = "/static/jquery-3.6.0.min.js"></script>
        <script src = "/static/scripts.js"></script>
    </head>
    <body>
        <h1>Interaction tableau avec JQuery - serveur Flask </h1>
        <br>
        <div>
            <table id = "tableau">
                <tr>
                    <td id = "1"> 1 </td>
                    <td id = "2"> 2 </td>
                    <td id = "3"> 3 </td>
                </tr>
                <tr>
                    <td id = "4"> 4 </td>
                    <td id = "5"> 5 </td>
                    <td id = "6"> 6 </td>
                </tr>
                <tr>
                    <td id = "7"> 7 </td>
                    <td id = "8"> 8 </td>
                    <td id = "9"> 9 </td>
                </tr>
            </table>
        </div>
        <h2 id = "texte_a_afficher">Cliquer sur une case du tableau</h2>
    </body>
</html>
JavaScript
$(document).ready(function() {

    $("td").click(function() {
        $.post( "/", {numero_case : $(this).attr('id')}, function(err, req, resp) {
            $("#texte_a_afficher").html(resp["responseJSON"]["texte_a_afficher"]);
            alert(resp["responseJSON"]["texte_a_afficher"]);
        });
    });

});
CSS
body { background-color : lightgray; }

h1, h2 {
    text-align: center;
    font-weight: bold;
}

div { text-align: center; }

table {
    margin-left: auto;
    margin-right:auto;
    border:1px solid #8BA674;
}

tr { border:1px solid #8BA674; }

td {
    font-size : 2em;
    border:1px solid #8BA674;
}

doc_flask_morpion