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.
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 où 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.
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.
@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:
@bp.route
associe l’URL/register
avec la fonction de vueregister
. Lorsque Flask reçoit une requête vers l’URL/auth/register
, elle appelle la vueregister
et génère sa réponse sur base de la valeur de retour.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.request.form
est un type spécial dedict
qui met en correspondance les clés et valeurs du formulaire. L’utilisateur fournit les valeurs pour les clésusername
etpassword
.Vérifier que les valeurs des champs
username
etpassword
ne sont pas vides.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 retourneNone
. Ensuite, la fonctionfetchall()
, qui elle retourne une liste avec tous les résultats, sera utilisée.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.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é.If validation fails, the error is shown to the user.
flash()
stores messages that can be retrieved when rendering the template.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 fonctionrender_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.
@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
:
On recherche d’abord l’utilisateur (user) et sa valeur est stockée dans une variable pour être réutilisée après.
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.session
est undict
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.
@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.
@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.
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).