Plans et vues

Une fonction « vue » est le code que vous écrivez pour répondre à des requêtes faites à votre application. Flask utilise des patterns pour faire correspondre l’URL d’une requête entrante avec la vue qui doit la traiter. La vue retourne les données que Flask convertit en une réponse. Flask peut aussi travailler dans l’autre sens et générer une URL correspondant à une vue en fonction de son nom et de ses arguments.

Créer un plan (blueprint)

Une classe Blueprint est une façon d’organiser un groupe de vues liées et du code associé. Plutôt que d’enregistrer les vues et le code associé directement avec une application, elles sont enregistrées à un plan. Ensuite, le plan est enregistré dans l’application dès qu’il est disponible dans la fonction qui contient l’usine à applications.

Flaskr contiendra deux plans, un pour les fonctions d’authentification et l’autre pour les fonctions liées aux posts sur le blog. Le code pour chaque plan sera placé dans un module séparé. Puisque le blog nécessite une authentification, vous allez d’abord écrire le plan qui y est relatif.

flaskr/auth.py
import functools

from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash

from flaskr.db import get_db

bp = Blueprint('auth', __name__, url_prefix='/auth')

Ceci créée une classe :class:”Blueprint` dénommée ` “auth”. Comme l'objet application, le plan doit connaître l'endroit il a été défini. C'est pour cela que ``__name__ est passé comme deuxième argument. L``url_prefix`` est ajouté au début de tous les URLs associés à ce plan.

Importez and enregistrez le plan depuis l’usine à applications en utilisant app.register_blueprint(). Placez ensuite le nouveau code à la fin de la fonction usine à applications avant de retourner l’application.

flaskr/__init__.py
def create_app():
    app = ...
    # existing code omitted

    from . import auth
    app.register_blueprint(auth.bp)

    return app

Le plan authentification devra avoir des vues permettant d’enregistrer de nouveaux utilisateurs ainsi que de se connecter et déconnecter.

La première vue: Register

Lorsque l’utilisateur visite l’URL /auth/register, la vue register va retourner du HTML contenant un formulaire à remplir. Lorsque l’utilisateur soumet le formulaire, son contenu est validé et soit celui-ci est réaffiché avec un message d’erreur ou le nouvel utilisateur est créé et l’on renvoie vers la page de login.

Pour le moment, vous allez uniquement écrire le code relatif à la vue. Vous verrez sur les page suivante comment utiliser des templates pour générer le formulaire HTML.

flaskr/auth.py
@bp.route('/register', methods=('GET', 'POST'))
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None

        if not username:
            error = 'Username is required.'
        elif not password:
            error = 'Password is required.'
        elif db.execute(
            'SELECT id FROM user WHERE username = ?', (username,)
        ).fetchone() is not None:
            error = 'User {} is already registered.'.format(username)

        if error is None:
            db.execute(
                'INSERT INTO user (username, password) VALUES (?, ?)',
                (username, generate_password_hash(password))
            )
            db.commit()
            return redirect(url_for('auth.login'))

        flash(error)

    return render_template('auth/register.html')

Voici ce que la fonction register fait:

  1. @bp.route associe l’URL /register avec la fonction de vue register. Lorsque Flask reçoit une requête vers l’URL /auth/register, elle appelle la vue register et génère sa réponse sur base de la valeur de retour.

  2. Si l’utilisateur a soumis un formulaire, request.method aura comme valeur 'POST'. Dans ce cas, il faut valider l’information encodée par l’utilisateur.

  3. request.form est un type spécial de dict qui met en correspondance les clés et valeurs du formulaire. L’utilisateur fournit les valeurs pour les clés username et password.

  4. Vérifier que les valeurs des champs username et password ne sont pas vides.

  5. Validate that username is not already registered by querying the database and checking if a result is returned. db.execute takes a SQL query with ? placeholders for any user input, and a tuple of values to replace the placeholders with. The database library will take care of escaping the values so you are not vulnerable to a SQL injection attack.

    fetchone() retourne une ligne de la requête. Si la requếte n’a pas retourné de résultat, elle retourne None. Ensuite, la fonction fetchall(), qui elle retourne une liste avec tous les résultats, sera utilisée.

  6. Si la vérification réussit, l’information relative au nouveau utilisateur est ajoutée à la base de données. Pour des raisons de sécurité, les mots de passe ne doivent jamais être stockés en clair`dans la base de données. La fonction :func:`~werkzeug.security.generate_password_hash est utilisée pour calculer un hash du mot de passe et c’est ce hash qui est stocké. Comme cette requête modifie des données, il ne faut pas oublier d’appeler db.commit() pour sauver les modifications.

  7. Après avoir stocké l’information relative à l’utilisateur, celui-ci est redirigé vers la page de login. La fonction url_for() génère l’URL de la vue de login sur base du nom. Ceci est préférable à une écriture directe de l’URL car cela vous permettra de changer l’URL ultérieurement sans devoir changer tout le code qui y est lié. redirect() génère une réponse de redirection pour l’URL généré.

  8. If validation fails, the error is shown to the user. flash() stores messages that can be retrieved when rendering the template.

  9. Lorsque l’utilisateur clique initialement sur auth/register, ou si il y a une erreur de validation, une page HTML contenant le formulaire d’enregistrement doit être présentée. La fonction render_template() va générer un template contenant le HTML, que vous écrirez dans la prochain section du tutoriel.

Login

Cette vue s’organise de façon similaire à la vue register ci-dessus.

flaskr/auth.py
@bp.route('/login', methods=('GET', 'POST'))
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None
        user = db.execute(
            'SELECT * FROM user WHERE username = ?', (username,)
        ).fetchone()

        if user is None:
            error = 'Incorrect username.'
        elif not check_password_hash(user['password'], password):
            error = 'Incorrect password.'

        if error is None:
            session.clear()
            session['user_id'] = user['id']
            return redirect(url_for('index'))

        flash(error)

    return render_template('auth/login.html')

Il y a cependant quelques différences par rapport à la vue register:

  1. On recherche d’abord l’utilisateur (user) et sa valeur est stockée dans une variable pour être réutilisée après.

  2. la fonction check_password_hash() calcule un hash du mot de passe soumis de la même façon que le hash stocké et compare les deux de façon sûre. Si ils correspondent, le mot de passe est considéré comme valide.

  3. session est un dict qui stocke des données d’une requête à l’autre. Si la validation du mot de passe réussit, l”id de l’utilisateur est stocké dans une session. La donnée est stockée dans un cookie qui est envoyé au navigateur. Celui-ci le renverra pour les requêtes suivantes. Flask signe cette donnée de façon sûre afin de garantir qu’elle n’a pas été modifiée par un pirate.

Maintenant que l”id de l’utilisateur est stocké dans la :data:`session, celui-ci sera disponible pour les requêtes suivantes. A début de chaque requête, l’application vérifiera si l’utilisateur est authentifié et si c’est le l’information correspondante sera chargée et mise à disposition des autres vues.

flaskr/auth.py
@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
        g.user = get_db().execute(
            'SELECT * FROM user WHERE id = ?', (user_id,)
        ).fetchone()

bp.before_app_request() enregistre une fonction qui s’exécute avant la fonction « vue », quelle que soit l’URL demandée. load_logged_in_user vérifie si l”id de l’utilisateur est stocké dans la session et récupère son information dans la base de données et la stocke dans :data`g.user <g>` qui persiste pendant la durée de la requête. Si il n’y a pas d”id d’utilisateur ou si cet id n’existe pas, g.user est mis à la valeur None.

Logout

Pour terminer la connexion, il suffit de retirer l”id de l’utilisateur de la session. De cette façon, load_logged_in_user ne chargera pas les informations relatives à l’utilisateur lors de prochaines requêtes.

flaskr/auth.py
@bp.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))

Besoins d’authentification dans d’autres vues

L’utilisateur doit être authentifié pour pouvoir créer, éditer ou effacer des messages de blog. Un décorateur peut être utilisé pour vérifier cela pour chaque vue à laquelle il s’applique.

flaskr/auth.py
def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view

Ce décorateur retourne une nouvelle fonction vue qui englobe la vue originale à laquelle elle est appliquée. Cette nouvelle fonction vérifie si un utilisateur est chargé et sinon redirige vers la page de login. Si un utilisateur est chargé, la vue orginale est appelée et son traitement continue normalement. Vous utiliserez ce décorateur lorsque vous écrirez les vues du blog.

Endpoints and URLs

La fonction url_for() génère l’URL associé à une vue sur base d’un nom et d’arguments. Le nom associé à une vue est aussi appelé le point final (endpoint en anglais). Par défaut, il est identique au nom de la fonction vue.

Par exemple, la vue hello() wui a été ajoutée précédemment à l’usine à applications a comme nom hello et peut être associée avec url_for('hello'). Si elle avait pris un argument, ce que vous verrez par après, elle aurait été associée en utilisant url_for(“hello”, who=”World”)`.

En utilisant un plan, le nom du plan est préfixé au nom de la fonction. Pour cette raison, le point final de la fonction login qui vous avez écrite précédemment est auth.login puisque vous l’avez ajoutée au plan 'auth'.

Continuez en lisant le document Modèles (Templates en anglais).