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 :
pip install Flask
index.py
dans le répertoire contenant votre projet. Voici le contenu de ce fichier :
Premier exemple
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()
python3 index.py
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éé :
|-- index.py
|-- templates
| |-- index.html
|-- static
| |-- style.css
Afficher une page 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>
body {
background-color:lightgrey;
}
h1 {
font-weight:bold;
font-style:italic;
}
# 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()
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
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)
<!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>
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
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)
<!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>
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;
}
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
{% for i in range(5) %}
<p>{{ i }}</p>
{% endfor %}
{% for valeur in tableau %}
<p>{{ valeur }}</p>
{% endfor %}
tableau
.
{% if valeur %}
<p>La valeur est vraie</p>
{% else %}
<p>La valeur est fausse</p>
{% endif %}
valeur
contient un booléen.
Ce mot clé permet de définir une variable :
{% 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
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)
<!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>
<!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>
<!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
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)
<!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>
<!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>
<!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 GET
ou 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
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)
<!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>
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
:
|-- 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
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)
<!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>
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)
);
}
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 (attributid
) - préfixe
.
: le sélecteur est le nom d'une classe (attributclass
)
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'attributclass="test"
.$("#test").hide()
: cache l'élément possédant l'attributid="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
<!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>
$(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 :
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
js
par exemple :
<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)
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
<!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>
$(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"]);
});
});
});
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;
}