Python Flask
Structure de base d’une application Web Python+Flask
Dans la technologie client-serveur, utilisée pour le World Wide Web, le navigateur Web envoie au serveur des requêtes relatives à des pages Web. Le serveur répond aux demandes en envoyant les pages au navigateur Web. Le navigateur affiche alors les pages à l’utilisateur.
Les applications Web utilisent cette technique pour mettre en œuvre leur interface graphique. Celle-ci est composée de pages créées de toutes pièces par le logiciel lors de chaque requête. Chaque hyperlien contenu dans la page provoque l’envoi d’une nouvelle requête, qui donnera en résultat une nouvelle page.
À la différence d’un site web statique où les pages sont des fichiers préalablement enregistrés !
Les pages Web contiennent divers widgets tels des boutons poussoirs, des icônes et des zones de texte, permettant la manipulation de l’application.

Comme toute application Web, les ressources se trouvent sur un serveur. Elles comprennent :
- du contenu :
- des pages (ou éléments de page) au format HTML+CSS
- des médias (images, vidéo, …)
- du code :
- Python+Flask exécuté sur le serveur
- Javascript exécuté sur le navigateur du client
Sur le serveur (la machine) doivent donc être installés :
- un serveur HTTP (le logiciel) : Apache, NGNIX, …
- Python avec les bibliothèques nécessaires (Flask notamment)
- une interface WSGI : un programme qui fait l’interface entre le serveur HTTP et les programmes Python.
Environnement virtuel
Sur le serveur, afin de ne pas perturber l’installation principale de Python, il est conseillé de travailler dans un environnement virtuel.
Installation d’un environnement virtuel
Un environnement virtuel est associé à un dossier de travail, et s’appuie sur une installation de Python existante.
Voir VSCodium
Installation des paquets nécessaires
sudo apt install python3-venv python3-pip
Création d’un environnement virtuel
Se placer dans le dossier dans lequel on souhaite travailler (celui qui contiendra les fichiers Python). Exemple avec le dossier monprojet :
mkdir monprojet cd monprojet
Le prompt du terminal doit être :
pi@raspberrypinsi1:~/monprojet $
Créer l’environnement virtuel (par exemple nommé env):
python -m venv env
Patienter …
Activation de l’environnement virtuel
. env/bin/activate
L’environnement actif apparaît au début du prompt du terminal :
(env) pi@raspberrypinsi1:~/monprojet $
Installation de Flask
Lorsque l’environnement virtuel est activé, il faut installer la bibliothèque Flask :
pip install flask
Structure d’une application Flask
Sur le serveur, la structure usuelle des fichiers sur un serveur basé sur Flask est la suivante :
- logs
- erreurs.log
- …
- scripts
- user.py
- …
- static
- img
- css
- js
- templates
- base.html
- page_1.html
- …
- index.py
Le fichier Python principal
Souvent appelé index.py, ce fichier est exécuté par un import depuis le fichier script wsgi. Il doit impérativement instancier un objet Flask(__name__), stocké dans une variable (ici app) pour être utilisé par le script wsgi.
Il doit se trouver à la racine du dossier du site.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Le Framework (générateur de page HTML)
from flask import *
# Application Flask
app = Flask(__name__) # le nom app est utilisé par le fichier wsgi
# Page HTML de base
pageHTML = "index.html"
############################################################################################
@app.route("/")
def index():
return render_template(pageHTML)
# Script de débuggage (n'est pas exécuté quand le module est importé par wsgi)
if __name__ == "__main__":
app.run(host='0.0.0.0', debug = True)
Les fichiers HTML
Il est conseillé de créer des fichiers HTML qui serviront de base au contenu.
Les fichiers HTML doivent impérativement se trouver dans le sous-dossier des modèles, par défaut appelé
templates.
Voici la structure minimale d’une page HTML :
<!DOCTYPE html >
<html lang="fr">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/> <!-- Encodage de la page -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mon site Web</title> <!-- Le titre qui apparaît sur les onglets du navigateur -->
<link/> <!-- Les fzuilles de style, intégrées ou importées -->
<script></script> <!-- Les codes Javascript, intégrés ou importés -->
</head>
<body> <!-- Corps principal de la page -->
<p> Bonjour tout le monde ! </p>
</body>
</html>
Remarque : la ligne
<meta name="viewport" content="width=device-width, initial-scale=1.0">permet d’adapter correctement les dimensions de la page aux écrans de smartphone.Toutes les explications ici : https://www.alsacreations.com/article/lire/1490-comprendre-le-viewport-dans-le-web-mobile.html
Les autres fichiers
Tous les autres fichiers nécessaires à l’élaboration des pages Web doivent se trouver dans le sous-dossier static du dossier du site :
- feuilles de style css
- scripts Javascript
- média (images, vidéo, …)
- …
Routage
Lorsque l’utilisateur saisit une URL dans la barre d’adresse du navigateur, ce dernier envoie une requête au serveur HTTP. Grâce à l’interface WSGI, cette requête se traduit par l’exécution d’une fonction du script principal Python, qui retourne le contenu de la page.
Cette fonction est appelée fonction de routage.
Le lien entre l’URL de la requête et la fonction de routage à exécuter est réalisé par un décorateur :
@app.route("/") # URL relatif à l'adresse IP/non d'hôte du serveur
def index():
return 'Page principale'
Dans cet exemple, l’URL « / » correspond à l’adresse IP ou le nom d’hôte du serveur (la machine), sans rien d’autre :
![]()
@app.route("/ma_page")
def page():
return 'Autre page'
![]()
On peut également utiliser des URL avec des « variables » :
@app.route('/user/<username>')
def show_user_profile(username):
return 'User %s' % username
@app.route('/page/<int:num_page>')
def afficher_page(num_page):
return 'Page n°%s' %num_page
Rendu des modèles
Les modèles (templates) sont des fichiers HTML qui peuvent être complétés dynamiquement par Flask. Le rendu est effectué par la fonction render_template().
Les modèles doivent se situer dans le dossier
templates!
Un exemple
Avec la fonction suivante …
import datetime
@app.route("/")
def index():
now = datetime.datetime.now()
date = "%s-%s-%s" %(now.year, now.month, now.day)
liste = ['a', 'b', 'c']
dico = {'Nom' : 'PAGE', 'Prénom' : 'Jimmy'}
return render_template("maPage.html", date = date, liste = liste, dico = dico)
… le modèle est défini dans un fichier maPage.html ci-dessous …
<!DOCTYPE html >
<html>
<head>
<title>Mon site Web</title>
</head>
<body>
<h1>Bonjour tout le monde !</h1>
<p>Nous sommes le {{ date }}</p>
{% if liste|length > 0 %}
<ul>
{% for i in liste %}
<li>{{ i }}</li>
{% endfor %}
</ul>
{% else %}
<p> la liste est vide !</p>
{% endif %}
<dl>
{% for cle, valeur in dico.iteritems() %}
<dt>{{ cle}} :</dt>
<dd>{{ valeur }}</dd>
{% endfor %}
</dl>
</body>
</html>
… s’affichera ainsi :

Jinja, le moteur de template
Pour « rendre » les pages (c’est à dire fournir au navigateur du client du code HTML), Flask utilise le moteur Jinja.
Le fichier modèle, au format HTML, contient des éléments non-HTML, placé entre accolades {{ ... }} :
{{ maVariable }} sera remplacé par une représentation textuelle de la variable maVariable
Jinja prévoit également l’utilisation de déclarations (statements), placées entre {% ... %} :
{% set maVariable = 42 %}pour faire des affectations,{% for e in liste %} ... {% endfor %}pour des boucles,{% if not fini %} ... {% endif %}pour des structures conditionnelles,- …
Remarque : les déclarations ressemblent fortement à du Python !
Toute la documentation sur les rendus : Template Designer Documentation
Du code Python au rendu HTML
Sur le modèle, il est possible de déclarer des variables :
% set maVariable = 42 %}
Mais il est possible de passer des variables Python au moteur de rendu :
Par exemple avec l’instruction :
render_template("maPage.html", date = date, liste = liste, dico = dico)
on pourra disposer des variables date , liste et dico dans le modèle, comme par exemple dans cette structure répétitive :
{% if liste|length > 0 %}
<ul>
{% for i in liste %}
<li>{{ i }}</li>
{% endfor %}
</ul>
{% else %}
<p> la liste est vide !</p>
{% endif %}
Cet extrait de modèle permet de générer une liste non ordonnée <ul> avec les éléments de la liste liste si elle n’est pas vide, ou un simple paragraphe <p> dans le cas contraire.
Remarque : on peut passer à
render_template()toutes les variables locales de la fonction grâce à la fonctionlocals():render_template(pageHTML, **locals())ne pas oublier les astérisques
**!!
les noms des variables sont identiques dans le modèle et dans la fonction
Programmation modulaire
Source : https://flask.palletsprojects.com/en/stable/blueprints/
Quelques fonctions utiles
Accès restreint
Flask peut prendre en charge la gestion des sessions utilisateur.
Les identifiants de connexion peuvent être stockés dans un fichier users.txt, sous la forme :
identifiant, hash_du_mot_de_passe
Pour hacher le mot de passe (en obtenir une empreinte), on peut utiliser la fonction suivante :
import hashlib
def hash_mdp(mdp):
return hashlib.sha256(str(mdp).encode('utf-8')).hexdigest()
Remarque : la fonction de hachage permet d’obtenir une empreinte du mot de passe. Chaque mot de passe possède une unique empreinte, et il est impossible de retrouver le mot de passe en connaissant l’empreinte.
On peut récupérer la liste des utilisateurs sous forme d’un dictionnaire avec la fonction suivante :
import os
def get_users(path_dir):
users = {}
with open(os.path.join(path_dir, "users.txt")) as f:
for l in f:
u, m = l.split(',')
users[u] = m
return users
Du coté de Flask, on utilise l’objet session, qui permet de stocker dans le navigateur, sous forme d’un cookie signé, et au contenu crypté, les données de l’utilisateur connecté.
Il faut importer l’objet session puis créer une clef de cryptage (secrète, sinon on pourrait modifier le cookie !)
from flask import session
app.secret_key = 'z\x82sIL8\xgg\x16\xf3\xb1Q\x84\xc0E\x8f\r\r{\xd2\x81H<:f'
Pour générer une clef secrète, on peut utiliser le code suivant, en ligne de commande :
python -c 'import os; print(os.urandom(16))'
Il faut bien sûr prévoir une page de connexion, (par exemple login.html), contenant un formulaire :
<form method="post">
<p><input type=text name=id>
<p><input type=password name=mdp>
<p><input type=submit value='Se connecter'>
</form>
Et créer le routage vers cette page :
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST': # L'utilisateur a saisi un identifiant
identifiant= request.form.get('id') # Récupération de l'identifiant sur le formulaire
users = get_users("") # Récupération des identifiants des utilisateurs enregistrés
if identifiantin users.keys(): # L'identifiant correspond à un utilisateur enregistré
mdp = request.form.get('mdp') # Récupération du mot de passe sur le formulaire
mdp_chiffre = hash_mdp(mdp) # Hachage du mot de passe
if users[identifiant] == mdp_chiffre: # Le mot de passe correspond
session['username'] = identifiant # Inscription de l'utilisateur
return redirect(url_for('index')) # Retour à la page d'accueil
else: # L'utilisateur demande l'accès à la page de connexion
return render_template("login.html")
Rappel : l’emploi de la méthode POST pour envoyer les données saisies dans le formulaire permet d’éviter qu’elles apparaissent dans l’URL.
Et il faut aussi prévoir une fonction de déconnexion :
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('index'))
Par la suite, on pourra savoir si un utilisateur est connecté (et lequel !) en utilisant une fonction de ce type :
def is_logged(): return 'username' in session
Actions diverses
Il est parfois nécessaire de réaliser des opérations à distance sur la machine (Raspberry, …).
Il est fortement conseillé de réserver ces actions aux utilisateurs « autorisés » !
Extinction du serveur
@app.route('/shutdown_server')
def shutdown_server():
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
Extinction de la machine
@app.route('/shutdown')
def shutdown():
if is_logged():
shutdown_server()
return 'Server shutting down...'
else:
return redirect(url_for('index'))
Redémarrage de la machine
import os
@app.route('/reboot')
def reboot_system():
if is_logged():
os.system("sudo reboot")
return 'Rebooting ...'
else:
return redirect(url_for('index'))
Gestion des robots
Il existe un protocole d’exclusion des robots, appelé robot.txt,
Le fichier robots.txt, à placer la racine d’un site web, contient une liste de ressources du site qui ne sont pas censées être explorées par les moteurs de recherches.
@app.route('/robots.txt')
@app.route('/sitemap.xml')
def static_from_root():
return send_from_directory(app.static_folder, request.path[1:])
