Flask¶
Flask est un framework web beaucoup plus évolué que Bottle.
Il reste compact mais comporte beaucoup plus de possibilité de bottle (a priori on ré-invente moins la roue)
Il prend en charge nativement les templates Jinja2
Il possède de plus un ensemble de plugin qui permet rapidement de lui rajouter ce qui lui manque
Premier exemple¶
Commençons par un simple “hello word”...
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
Pour tester ce code, il suffit juste de lancer la commande suivante. Et d’ouvrir le navigateur sur l’URL qui sera affichée dans la console.
$ python app.py
* Running on http://127.0.0.1:5000/
Pour l’instant si on compare Flask et Bottle cela ne fait pas beaucoup de différence. Et heureusement puisqu’ils respectent tous les deux le PEP de python sur les framework web.
Debugger¶
Flask possède un debugger intégré très performant puisqu’il permet de lancer via un naviguateur web une console python (accessible via chaque ligne du résultat du debugger)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
# une erreur ici
a = 1/0
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True)
On peut aussi activer le debug via l’utilisation une variable globale
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask
# Flask configuration
DEBUG = True
app = Flask(__name__)
app.config.from_object(__name__)
@app.route("/")
def hello():
# une erreur ici
a = 1/0
return "Hello World!"
if __name__ == "__main__":
app.run()
Note
On peut utiliser n’importe quelle variable en majuscule de l’objet utilisé dans from_object. On peut ainsi passer le path d’un fichier log ou celui d’une base de donnée On récupère les variables via le code
app.config['NAME_OF_VARIABLE']
Logger¶
Flask possède de façon native un handler de logger ... on peut donc facilement rajouter des loggers
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
from flask import Flask
# Flask configuration
DEBUG = True
SERVER_NAME = '0.0.0.0:5001'
LEVEL = logging.INFO
app = Flask(__name__)
app.config.from_object(__name__)
@app.route('/')
def hello():
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
app.logger.info('Info')
return "Hello World!"
if __name__ == '__main__':
handler = logging.StreamHandler()
handler.setLevel(app.config['LEVEL'])
app.logger.addHandler(handler)
app.run()
Warning
si on passe le DEBUG a False on ne voit que les errors et les warnings plus les infos
Ajout de fonction pre et post¶
Comme pour bottle on peut décorer les appels ... mais Flask possède des décorateurs pré-défini
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
from flask import Flask
# Flask configuration
DEBUG = True
SERVER_NAME = '0.0.0.0:5001'
LEVEL = logging.INFO
app = Flask(__name__)
app.config.from_object(__name__)
@app.before_request
def before_request():
app.logger.warning('before call')
@app.teardown_request
def teardown_request(exception):
app.logger.warning('after call')
@app.route('/')
def hello():
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
app.logger.info('Info')
return "Hello World!"
if __name__ == '__main__':
handler = logging.StreamHandler()
handler.setLevel(app.config['LEVEL'])
app.logger.addHandler(handler)
app.run()
Filtrage des formulaires et des réponses¶
@app.route('/contact', methods=['GET', 'POST'])
def contact():
if request.method == 'GET':
# afficher le formulaire
else:
# traiter les données reçues
# afficher : "Merci de m'avoir laissé un message !"
Les sessions¶
Les sessions sont donc un moyen simplifié et plus sécurisé de retenir des informations pour vos visiteurs. Les sessions utilisent deux choses distinctes :
- Un cookie placé chez le client, pour identifier sa session.
- Des données stockées sur le serveur, reliées à cette même session.
Pour fonctionner, nous aurons besoin d’importer l’objet session :
from flask import session
Cet objet s’utilise ensuite de la même manière qu’un dictionnaire Python. L’avantage est qu’on peut lire et écrire dedans, on ne passe donc plus par les objets requête et réponse. Voilà l’équivalent du code avec les cookies transposé dans une version utilisant session à la place :
@app.route('/')
def index():
if 'pseudo' in session:
return "C'est un plaisir de se revoir, {pseudo} !".format(pseudo=session['pseudo'])
else:
session['pseudo'] = 'Luc'
return "Bonjour, c'est votre première visite ?"
Le défaut des sessions est qu’on ne peut pas choisir de période d’expiration différente pour chaque variable qu’on stocke dans la session.
Par défaut, la session sera effacée quand le visiteur fermera son navigateur. Si on veut qu’elle dure plus longtemps, il faut placer session.permanent = True après sa création. Elle vivra alors un mois.
Pour changer cette durée, il faut modifier la configuration de l’objet app :
app.config['PERMANENT_SESSION_LIFETIME'] = 3600 # la session dure une heure
On peut stocker n’importe quoi dans session : entiers, chaînes, listes, objets, dictionnaires, etc.
Cependant, dans le cas des listes et des dictionnaires (et de tout type mutable plus généralement), il faut notifier la session lorsqu’on les modifie, en positionnant à True l’attribut modified :
session['mon_panier'].append('appareil photo') # mon_panier est une liste
session.modified = True
Pour finir, lorsqu’on désire supprimer une variable de la session, on utilise la méthode pop() de la session :
session.pop('pseudo', None)
Note
Le deuxième paramètre (None) est là uniquement pour éviter une erreur s’il n’y a pas de variable ‘pseudo’ dans la session.
Les templates¶
#! /usr/bin/python
# -*- coding:utf-8 -*-
from flask import Flask
app = Flask(__name__)
@app.route('/')
def accueil():
mots = ["bonjour", "à", "toi,", "visiteur."]
return render_template('accueil.html', titre="Bienvenue !", mots=mots)
if __name__ == '__main__':
app.run(debug=True)
Note
penser dans les template a ne pas utiliser d’adresse en dur mais d’utiliser url_for()
<link href="{{ url_for('static', filename='mon_style.css') }}" rel="stylesheet" type="text/css" />
Création d’un site statique¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from flask import Flask
import flask
# Flask configuration
DEBUG = True
PATH_HTML = 'C:/Users/f.aoustin/Google Drive/mydoc/build/html'
PATH_INDEX = 'index.html'
app = Flask(__name__)
app.config.from_object(__name__)
@app.route("/")
def index():
return flask.send_from_directory(app.config['PATH_HTML'],app.config['PATH_INDEX'])
@app.route("/<path:path>")
def static_web(path):
return flask.send_from_directory(app.config['PATH_HTML'],path)
if __name__ == "__main__":
app.run()
Plugins¶
flask-login¶
Généralement lors de la création d’une applicatino web une des premiere fonctionnalité est la gestion de l’accès à cette dernière.
Flask possède un plugin qui gère cela pour nous: flask-login
pip install flask
pip install flask-login
on va par la suite créer cette arborescence
dir
|__ myapp.py
|__ myapp.cfg
|__ templates
| |__ login.html
|__ statics
|__ css
|__ fonts
|__ js
| |__ myjs.js
|__ less
|__ scss
|__ admin.html
|__ protected.html
On utilisera les modules font-awesomes et nos modules propres de design (cf autre billet: design web)
admin.html
<html>
<head>
<link href="css/font-awesome.min.css" rel="stylesheet">
<link href="css/mycss.css" rel="stylesheet">
<link href="css/mytheme.css" rel="stylesheet">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="js/myjs.js"></script>
</head>
test admin
<a href="/logout">logout</a>
<a href="/">index</a>
</body>
</html>
protected.html
<html>
<head>
<link href="css/font-awesome.min.css" rel="stylesheet">
<link href="css/mycss.css" rel="stylesheet">
<link href="css/mytheme.css" rel="stylesheet">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="js/myjs.js"></script>
</head>
test index
<a href="/logout">logout</a>
<a href="/admin">admin</a>
</body>
</html>
myjs.js
$().ready(function() {
$('.tdauto').css({
width: ($(window).width() - 250)/2 + 'px'
});
$("input[type=checkbox]").click(function () {
$(this).toggleClass("checked");
$.ajax({
type: "POST",
url: "http://192.168.1.18:5001/update",
dataType: "xml",
data: "id=" + $(this).attr("id"),
async: true,
global: false,
success: function(xml) {
/* todo */
},
beforeSend: function() {
/* todo */
},
complete: function() {
/* todo */
}
});
});
// hide block expand
$("div.list>input.checked").each(function() {
$("div.expand>span[id='"+$(this).parent().attr("view")+"']").append(" <strong class='list-selected'>"+$(this).next().text()+"</strong>");
}
);
$("input[type=radio]").click(function () {
$('input:radio[name='+$(this).attr("name")+']').removeClass("checked");
$(this).toggleClass("checked");
/*$('input:radio[name='+$(this).attr("name")+']').toggleClass("checked");*/
$.ajax({
type: "POST",
url: "http://192.168.1.18:5001/update",
dataType: "xml",
data: "id=" + $(this).attr("id")+ "&val=" + $(this).val(),
async: true,
global: false,
success: function(xml) {
/* todo */
},
beforeSend: function() {
/* todo */
},
complete: function() {
/* todo */
}
});
/* manage list */
if ( $( this ).hasClass( "list" ) ) {
$(this).parent().css("display","none");
$("div.expand>span[id='"+$(this).parent().attr("view")+"']").toggleClass("expand");
$("div.expand>span[id='"+$(this).parent().attr("view")+"']>strong").remove();
$("div.expand>span[id='"+$(this).parent().attr("view")+"']").append(" <strong class='list-selected'>"+$(this).next().text()+"</strong>");
};
});
$('input[type=text].online').on('input', function() {
$.ajax({
type: "POST",
url: "http://192.168.1.18:5001/update",
dataType: "xml",
data: "id=" + $(this).attr("id") + "&val=" + $(this).val(),
async: true,
global: false,
success: function(xml) {
/* todo */
},
beforeSend: function() {
/* todo */
},
complete: function() {
/* todo */
}
});
});
$('input.validform').keypress(function(event){
if(event.keyCode == 13){
$(this).parents("form")[0].submit();
}
});
// hide block expand
$("div.expand>span").each(function() {
$("[view='"+$(this).attr("id")+"']").css("display","none");
}
);
$("div.expand>span").click(function () {
$(this).toggleClass("expand");
if ( $( this ).hasClass( "expand" ) ) {
$("[view='"+$(this).attr("id")+"']").css("display","inline-block");
} else {
$("[view='"+$(this).attr("id")+"']").css("display","none");
}
});
$('input[type=range]').on('input change', function() {
$.ajax({
type: "POST",
url: "http://192.168.1.18:5001/update",
dataType: "xml",
data: "id=" + $(this).attr("id") + "&val=" + $(this).val(),
async: true,
global: false,
success: function(xml) {
/* todo */
},
beforeSend: function() {
/* todo */
},
complete: function() {
/* todo */
}
});
});
$("button").click(function () {
$.ajax({
type: "POST",
url: "http://192.168.1.18:5001/update",
dataType: "xml",
data: "id=" + $(this).attr("id"),
async: true,
global: false,
success: function(xml) {
/* todo */
},
beforeSend: function() {
/* todo */
},
complete: function() {
/* todo */
}
});
});
});
login.html
<html>
<head>
<link href="css/font-awesome.min.css" rel="stylesheet">
<link href="css/mycss.css" rel="stylesheet">
<link href="css/mytheme.css" rel="stylesheet">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="js/myjs.js"></script>
</head>
<body>
<table class="center">
<tr/>
<tr>
<td class="tdauto"></td>
<td>
<form id="myformlogin" action="login" method="post">
<div class="login">
<div class="input-prepend">
<span class="add-on"><i class="fa fa-user"></i></span>
<input id="username" name="username" type="text" placeholder="username" class="online"/>
</div>
<div class="input-prepend">
<span class="add-on"><i class="fa fa-wrench"></i></span>
<input id="password" name="password" class="validform" type="password" placeholder="password"/>
</div>
{% for warning in warnings %}
<div class="warning" ><i class="fa fa-2x fa-warning"></i> <span>{{ warning }}</span></div>
{% endfor %}
{% for info in infos %}
<div class="info" ><i class="fa fa-2x fa-info-circle"></i> <span>{{ info }}</span></div>
{% endfor %}
</div>
</form>
</td>
<td/>
</tr>
<tr/>
</table>
</body>
</html>
myapp.cfg
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
DEBUG = True
PATH_STATICS = './statics'
SECRET_KEY = 'secret_key'
PORT = 5001
HOST = '0.0.0.0'
LEVEL = logging.DEBUG
USERS = [
{ 'id' : 1,
'username' : 'admin',
'password' : 'keyadmin',
'groups' : ['admin','user']
},
{ 'id' : 2,
'username' : 'test',
'password' : 'keytest',
'groups' : ['user']
}
]
myapp.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
from flask import Flask
import flask
from flask.ext.login import LoginManager
from flask.ext.login import login_user , logout_user , current_user , login_required
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
app.config.from_pyfile('myapp.cfg')
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
class User():
""" class of User, for login"""
def __init__(self, username):
self.username = username
def check_password(self, password):
if self.username in [i['username'] for i in app.config['USERS']]:
if [ i['password'] for i in app.config['USERS'] if i['username'] == self.username][0] == password:
return True
return False
def in_groups(self, group):
if group in [ i['groups'] for i in app.config['USERS'] if i['username'] == self.username][0]:
return True
return False
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return [ i['id'] for i in app.config['USERS'] if i['username'] == self.username][0]
def __repr__(self):
return '<User %r>' % (self.username)
class checkGroup(object):
def __init__(self, group):
self.group = group
def __call__(self, fn):
def wrapped_f(*args, **kwargs):
if current_user.in_groups('admin') :
return fn(*args, **kwargs)
return unauthorized()
return wrapped_f
@app.route('/')
@login_required
def index():
app.logger.debug('load protected.html by %s ' % current_user.username)
return flask.send_from_directory(app.config['PATH_STATICS'],'protected.html')
@app.route('/admin')
@login_required
@checkGroup('admin')
def admin():
app.logger.debug('load admin.html by %s ' % current_user.username)
return flask.send_from_directory(app.config['PATH_STATICS'],'admin.html')
@app.route('/login',methods=['GET','POST'])
def login():
app.logger.debug('login method: %s' % flask.request.method)
if flask.request.method == 'GET':
return flask.render_template('login.html', warnings=[], infos=[])
username = flask.request.form['username']
app.logger.debug(username)
password = flask.request.form['password']
registered_user = User(username)
if registered_user.check_password(password):
login_user(registered_user, remember = True)
app.logger.debug('Logged in successfully')
return flask.redirect(flask.request.url_root)
return flask.render_template('login.html', warnings=['username and/or password are wrong',], infos=[])
#return flask.abort(401) # 401 Unauthorized
@app.route('/logout')
def logout():
logout_user()
return flask.redirect(flask.url_for('login'))
@login_manager.unauthorized_handler
def unauthorized():
return "non autoriser bouh !!!"
@login_manager.user_loader
def load_user(id):
app.logger.debug('id %s' % id)
return User([i['username'] for i in app.config['USERS'] if i['id']== id][0])
@app.route("/<path:path>")
def static_web(path):
app.logger.debug('load static %s' % path)
return flask.send_from_directory(app.config['PATH_STATICS'],path)
if __name__ == "__main__":
handler = logging.StreamHandler()
handler.setLevel(app.config['LEVEL'])
app.logger.addHandler(handler)
app.run(host = app.config['HOST'],
port = app.config['PORT'])
# TODO: https://www.openshift.com/blogs/use-flask-login-to-add-user-authentication-to-your-python-application
# TODO: https://github.com/shekhargulati/flask-login-openshift-quickstart/blob/master/wsgi/todoapp.py
on peut au lieu d’enregistrer le mot de passe en dure dans un fichier le hasher puis par la suite contrôler que le hash sauvegardé correspond au password fournit
4 >>from werkzeug.security import generate_password_hash, check_password_hash
5 >>hash = generate_password_hash('test')
6 >>print(hash)
pbkdf2:sha1:1000$FaKJFp6q$eee385f5f75cf3e7da560b51580c0c9b565e0cb5
7 >>print check_password_hash(hash,'test')
True
8 >>print check_password_hash(hash,'test2')
False
9 >>
Flask-WeasyPrint¶
Il est parfois utile d’avoir un outil simple pour générer du pdf à la voler.
Le plugin Flask-WeasyPrint est fait pour vous
pip install flask
pip install Flask-WeasyPrint
on va par la suite créer cette arborescence
dir
|__ myapp.py
|__ myapp.cfg
|__ templates
| |__ hello.html
|__ statics
|__ css
| |__ styles.css
|__ img
|__ hello.png
myapp.cfg
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
DEBUG = True
PATH_STATICS = './statics'
SECRET_KEY = 'secret_key'
PORT = 5001
HOST = '0.0.0.0'
myapp.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, render_template, url_for, send_from_directory
from flask_weasyprint import HTML, render_pdf
app = Flask(__name__)
app.config.from_pyfile('myapp.cfg')
@app.route('/hello/', defaults={'name': 'World'})
@app.route('/hello/<name>/')
def hello_html(name):
return render_template('hello.html', name=name)
@app.route('/hello_<name>.pdf')
def hello_pdf(name):
# Make a PDF from another view
return render_pdf(url_for('hello_html', name=name))
#return render_pdf(HTML(string=html)) other possibilite generate HTML
@app.route("/<path:path>")
def static_web(path):
return send_from_directory(app.config['PATH_STATICS'],path)
if __name__ == "__main__":
app.run(host = app.config['HOST'],
port = app.config['PORT'])
hello.html
<!doctype html>
<title>Hello</title>
<link rel=stylesheet href="/css/style.css}" />
<p>Hello, {{ name }}!</p>
<img src="/img/hello.png"/>
<nav><a href="{{ url_for('hello_pdf', name=name) }}">Get as PDF</a></nav>
style.css
body { font: 2em Fontin, serif }
nav { font-size: .7em }
@page { size: A5; margin: 1cm }
@media print {
nav { display: none }
}
on peut tester sur http://127.0.0.1:5001/hello
Flask-Mail¶
On peut avoir besoin dans une application d’envoyer des mails
pip install flask
pip install Flask-Mail
on va par la suite créer cette arborescence
dir
|__ myapp.py
|__ myapp.cfg
|__ templates
| |__ mail.html
|__ statics
|__ img
|__ hello.png
myapp.cfg
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
DEBUG = True
PATH_STATICS = './statics'
SECRET_KEY = 'secret_key'
PORT = 5001
HOST = '0.0.0.0'
MAIL_SERVER = 'srvxxxx1'
myapp.py
from flask import Flask, send_from_directory
from flask_mail import Mail, Message
mail = Mail()
app = Flask(__name__)
app.config.from_pyfile('myapp.cfg')
mail.init_app(app)
@app.route("/mail")
def send_mail():
msg = Message("Hello",
sender="informatiquepz@myprop-group.com",
recipients=["f.aoustin@myprop-group.com"])
msg.body = "testing"
with app.open_resource("%s/img/hello.png" % app.config['PATH_STATICS']) as fp:
msg.attach("hello.png", "image/png", fp.read())
#msg.html = "<b>testing</b>"
mail.send(msg)
return send_from_directory(app.config['PATH_STATICS'],'mail.html')
if __name__ == "__main__":
app.run(host = app.config['HOST'],
port = app.config['PORT'])
mail.html
<html>
send mail
</html>
on peut tester sur http://127.0.0.1:5001/mail
Flask-WTF¶
Cette extension permet de gérer simplement des formulaires.
pip install flask
pip install Flask-WTF
Nous allons contruire une application possédant un formulaire de contact avec 4 champs:
- nom
- sujet
- message
Tout les champs sont obligatoires et nous validerons le format du mail
Notre application aura l’arborescence suivante
dir
|__ myapp.py
|__ myapp.cfg
|__ templates
| |__ contact.html
| |__ home.html
| |__ layout.html
|__ statics
|__ css
|__ main.css
contact.html
{% extends "layout.html" %}
{% block content %}
<h2>Contact</h2>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
<form action="{{ url_for('contact') }}" method=post>
{{ form.hidden_tag() }}
{{ form.name.label }}
{{ form.name }}
{% for message in form.name.errors %}
<div class="flash">{{ message }}</div>
{% endfor %}
{{ form.email.label }}
{{ form.email }}
{% for message in form.email.errors %}
<div class="flash">{{ message }}</div>
{% endfor %}
{{ form.subject.label }}
{{ form.subject }}
{% for message in form.subject.errors %}
<div class="flash">{{ message }}</div>
{% endfor %}
{{ form.message.label }}
{{ form.message }}
{% for message in form.message.errors %}
<div class="flash">{{ message }}</div>
{% endfor %}
{{ form.submit }}
</form>
{% endblock %}
layout.html
{% extends "layout.html" %}
{% block content %}
home
{% endblock %}
home.html
<!DOCTYPE html>
<html>
<head>
<title>Flask</title>
<strong><link rel="stylesheet" href="css/main.css"></strong>
</head>
<body>
<header>
<div class="container">
<h1 class="logo">Flask App</h1>
<nav>
<ul class="menu">
<li><a href="{{ url_for('home') }}">Home</a></li>
<li><a href="{{ url_for('contact') }}">Contact</a></li>
</ul>
</nav>
</div>
</header>
<div class="container">
{% block content %}
{% endblock %}
</div>
</body>
</html>
Note
on utilise içi les templates avec des notions d’extends
myapp.cfg
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
DEBUG = True
PATH_STATICS = './statics'
SECRET_KEY = 'secret_key'
PORT = 5001
HOST = '0.0.0.0'
MAIL_SERVER = 'srvxxxx1'
myapp.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
from flask import Flask
import flask
from flask_wtf import Form
from wtforms import TextField, TextAreaField, SubmitField, validators
app = Flask(__name__)
app.config.from_pyfile('myapp.cfg')
class RegistrationForm(Form):
username = TextField('Username', [validators.Length(min=4, max=25)])
email = TextField('Email Address', [validators.Length(min=6, max=35)])
name = TextField("Name", [validators.Required("Please enter your name.")])
subject = TextField("Subject", [validators.Required("Please enter a subject.")])
message = TextAreaField("Message", [validators.Required("Please enter a message.")])
datefield = DateField('datefield')
datetimefield = DateTimeField('datetimefield')
decimalfield = DecimalField('decimalfield')
filefield = FileField('filefield')
floatfield = FloatField('floatfield')
integerfield = IntegerField('integerfield')
radiofield = RadioField('radiofield', choices=[('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')])
selectfield = SelectField('selectfield', choices=[('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')])
password = PasswordField('New Password', [
validators.Required(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
accept_tos = BooleanField('I accept the TOS', [validators.Required()])
submit = SubmitField("Send")
@app.route('/home')
def home():
app.logger.debug(flask.render_template('home.html'))
return flask.render_template('home.html')
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if flask.request.method == 'POST':
if form.validate() == False:
app.logger.error('All fields are required.')
flask.flash('All fields are required.')
return flask.render_template('contact.html', form=form)
else:
app.logger.debug("name : %s" % form.name.data)
app.logger.debug("email : %s" % form.email.data)
app.logger.debug("subject : %s" % form.subject.data)
app.logger.debug("message : %s" % form.message.data)
return 'Form posted.'
elif flask.request.method == 'GET':
return flask.render_template('contact.html', form=form)
@app.route("/<path:path>")
def static_web(path):
app.logger.debug('load static %s' % path)
return flask.send_from_directory(app.config['PATH_STATICS'],path)
if __name__ == "__main__":
handler = logging.StreamHandler()
handler.setLevel(app.config['LEVEL'])
app.logger.addHandler(handler)
app.run(host = app.config['HOST'],
port = app.config['PORT'])
On peut tester sur http://127.0.0.1:5001/contact
Il est important de noter que nous gérons les erreurs champs par champs au niveau du contact.html mais aussi au niveau gloabl grâce à la fonction flash.
On peut aussi facilement rajouter un form de gestion du login
from wtforms import TextField, TextAreaField, SubmitField, validators, PasswordField, BooleanField
class RegistrationForm(Form):
username = TextField('Username', [validators.Length(min=4, max=25)])
email = TextField('Email Address', [validators.Length(min=6, max=35)])
password = PasswordField('New Password', [
validators.Required(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
accept_tos = BooleanField('I accept the TOS', [validators.Required()])
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm(flask.request.form)
if flask.request.method == 'POST' and form.validate():
flash('Thanks for registering')
return redirect(url_for('login'))
return flask.render_template('register.html', form=form)
Et pour register.html afin de simplifier son écriture on peut créer une fonction de template
register.html
{% from "_formhelpers.html" import render_field %}
{% extends "layout.html" %}
{% block content %}
<form method=post action="/register">
{% for field in form %}
{% if field.widget.__class__.__name__ in ('TextInput','PasswordInput','CheckboxInput','TextArea', 'ListWidget', 'Select') %}
{{ render_field(field, class='wtf') }}
{% endif %}
{% endfor %}
{{ form.submit }}
</form>
{% endblock %}
_formhelpers.html
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
Concernant les éléments qu’on peut uiliser via le module wtform il y a une excellente doc https://wtforms.readthedocs.org/en/latest
Il existe plusieurs validateurs:
- validators.DataRequired(message=None)
- validators.Email(message=None)
- validators.EqualTo(fieldname, message=None)
- validators.InputRequired(message=None)
- validators.IPAddress(ipv4=True, ipv6=False, message=None)
- validators.Length(min=-1, max=-1, message=None)
- validators.MacAddress(message=None)
- validators.NumberRange(min=None, max=None, message=None)
- validators.Optional(strip_whitespace=True)
- validators.Regexp(regex, flags=0, message=None)
- validators.URL(require_tld=True, message=None)
- validators.UUID(message=None)
- validators.AnyOf(values, message=None, values_formatter=None)
- validators.NoneOf(values, message=None, values_formatter=None)
Il est possible de créer facilement des nouveaux validateurs
def my_length_check(form, field):
if len(field.data) > 50:
raise ValidationError('Field must be less than 50 characters')
class MyForm(Form):
name = StringField('Name', [InputRequired(), my_length_check])