Flask

Structure de base d’une application Web Python+Flask

Procédure d’installation

Exemple de structure de serveur Web utilisant Flask :

Le fichier Python principal

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 se trouver dans le sous-dossier template (modèles) du dossier du site.

<!DOCTYPE html >
<html>
   <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>
   </head>

<!-- Corps principal de la page -->
<body>
   <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
  • images

 

Routage

Lorsque l’utilisateur saisit une URL dans la barre d’adresse du navigateur, ce dernier envoie une requête au serveur Apache. 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.

Le lien entre l’URL de la requête et la fonction à exécuter est réalisé par un décorateur :

@app.route("/")
def index(): 
   return 'Page principale'

Dans cet exemple, l’URL « / » correspond à l’adresse IP du Raspberry, 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() :

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(pageHTML, date = date, liste = liste, dico = dico)

… le modèle pageHTML 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 :

 

Remarques :

  • On peut passer à render_template() toutes les variables locales de la fonction grâce à la fonction locals() :
render_template(pageHTML, **locals())

ne pas oublier les astérisques ** !!

 

Quelques fonctions utiles

Accès restreint

Flask peut prendre en charge la gestion des sessions utilisateur.

Les identifiants de connexion sont stockés dans un fichier users.txt , sous la forme :

identifiant,hash_du_mot_de_passe

Pour hacher le mot de passe, 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")

Remarque : 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 le 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 du Raspberry

@app.route('/shutdown')
def shutdown():
   if is_logged():
      shutdown_server()
      return 'Server shutting down...'
   else:
      return redirect(url_for('index'))

Redémarrage du Raspberry

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:])

 

Vous aimerez aussi...

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *