Fort heureusement, il existe de nos jours de nombreuses béquilles qui aident les déficients de l'ergonomie que nous sommes et, parmi elles, le module python WTForms et le framework Bootstrap qui nous permettrons respectivement de générer des formulaires propres et de produire une interface de belle facture.
1. Au début était le moche
Nous démarrerons notre expérience par la création d'une application Flask basique: Un formulaire de trois champs dont le résultat sera inscrit dans un fichier plat après validation.
Le code Flask est le suivant :
$ cat basic.py
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route(‘/’, methods=[‘GET’, ‘POST’])
def basicform():
if request.method == ‘POST’:
with open(‘/tmp/test.txt’, ‘w’) as f:
for k in request.form:
f.write(‘{0}: {1}\n’.format(k, request.form[k]))
return render_template(‘basic.html’)
if __name__ == ‘__main__’:
app.run(debug=True)
Et le template associé :
$ cat templates/basic.html
<!doctype html>
<title>Bienvenue, visiteur du futur !</title>
<div class="content">
<h2>Identification</h2>
<hr>
<form method="POST" action="/">
<label>Utilisateur</label><br />
<input type="text" name="user" id="user"><br />
<label>Mot de passe</label><br />
<input type="password" name="passwd" id="passwd"><br />
<select name="active" id="active">
<option value="no">Non</option>
<option value="yes">Oui</option>
</select><br />
<input type="submit" name="action" value="register">
</form>
</div>
Sans surprise, ce duo produit l'interface suivante :
Fig. 1 : Moche
C'est laid, c'est sur, mais surtout, que de caractères tapés pour un résultat aussi sommaire… Alors que nous utilisons un framework pour générer notre GUI, n'y a-t-il pas de moyen plus efficace pour produire ces sempiternels formulaires ?
2. C'est quoi ce !@#!@#
WTForms est un module python permettant de grandement factoriser la création de formulaires. En couplant la puissance du moteur de template Jinja2, nativement intégré à Flask et la souplesse de WTForms, il devient simplissime de générer des formulaires avec un minimum d'efforts.
Il suffit pour cela d'importer le type de champs souhaité, puis de faire hériter son formulaire de l'objet Form et d'y lister les différents types de données. Plus fort, WTForms permet sans beaucoup plus de travail d'intégrer des validateurs, afin de vérifier que le contenu des champs correspond bien à l'entrée souhaitée; par exemple, en vérifiant le nombre de caractères ou encore la valeur maximale d'une valeur.
Après avoir préalablement installé le module WTForms, par exemple via pip, nous créons un fichier forms.py contenant les champs du formulaire précédemment créé à la main :
from wtforms import Form, TextField, SelectField, SubmitField, PasswordField,
validators
class BasicForm(Form):
user = TextField(u’Utilisateur’, [validators.Length(min=4, max=20)])
passwd = PasswordField(u’Mot de passe’, [validators.Length(min=8, max=32)])
active = SelectField(u’Actif’, choices = [(‘yes’, ‘Yes’), (‘no’, ‘No’)])
action = SubmitField(u’Register’)
On importe cette classe dans notre fichier basic.py et instancions un objet form que l'on passera en paramètre à notre template, capable de l'utiliser via Jinja :
from flask import Flask, request, render_template, url_for
from forms import BasicForm
app = Flask(__name__)
@app.route(‘/’, methods=[‘GET’, ‘POST’])
def basicform():
form = BasicForm(request.form)
if request.method == ‘POST’ and form.validate():
with open(‘/tmp/test.txt’, ‘w’) as f:
for k in request.form:
f.write(‘{0}: {1}\n’.format(k, request.form[k]))
return render_template(‘basic.html’, form=form)
if __name__ == ‘__main__’:
app.run(debug=True)
On notera qu'on passe la requète en paramètre à la classe BasicForm, ceci afin de contrôler la validité des champs postés via notre formulaire.
Apprécions maintenant la puissance du duo Jinja/WTForms. Voici notre précédent template, réduit à un plus simple appareil :
$ cat templates/basic.html
<!doctype html>
<title>Bienvenue, visiteur du futur !</title>
<div class="content">
<h2>Identification</h2>
<hr>
<form method="POST" action="/">
{% for field in form %}
{{ field.label }}
{{ field }}<br />
{% endfor %}
</form>
</div>
Ah oui, ça oui, c'est classe (ah-ah jeu de mots involontaire). Dans ce minuscule template, nous bouclons sur le paramètre form que nous avons passé en paramètre et, pour chaque valeur, on affiche la propriété label, la chaîne de caractères spécifiée en premier paramètre des champs définis dans le fichier forms.py, et le champs lui même. WTForms se charge seul de placer correctement toutes les balises nécessaires et nomme les attributs selon le nom de la variable contenant l'objet à afficher.
Fig. 2 : Pouah...
3. Et tu nous prends pas pour des jambons des fois ?
Je sais, je sais, c'est toujours moche. Nous y venons.
Bootstrap est un framework libéré en Août 2011 par Twitter simplifiant de façon radicale la stylisation d'un site web. Son utilisation est remarquablement aisée et sa documentation limpide et concrète. Le champs d'action de Bootstrap va des formulaires aux barres de navigation, en passant par les labels et autres tableaux. Une particularité notable de Bootstrap, et l'un des aspects qui en font un projet extrêmement populaire, réside dans sa capacité native à gérer le Responsive Design, ou en termes moins flanbyesques, la capacité d'un site à s'adapter à tout type de périphériques: stations de travail aux écrans sur-dimentionnés, tablettes, smartphones… Cette fonctionnalité repose sur un concept clair et intelligent, Bootstrap découpe la fenêtre du navigateur en une grille de douze colonnes; en ajoutant à vos balises une classe de type col-<type>-<taille>, on sait exactement comment se positionnera l'élément dans la page.
Exemple :
<div class="col-xs-12 col-md-8">.col-xs-12 col-md-8</div>
Ici, pour un périphérique mobile, bénéficiant donc d'une taille réduite (xs pour extra small), on spécifie que l'élément <div> occupera 12 colonnes alors que pour un équipement de taille moyenne (md pour medium), il n'en occupera que 8.
De façon plus générale, la stylisation de tous les éléments d'un site web à l'aide de ce framework s'effectue en ajoutant des classes aux éléments souhaités, on rendra par exemple un bouton beau en lui appliquant les classes btn btn-default pour un design standard ou encore btn btn-primary pour un bouton sur lequel on veut mettre l'emphase.
Dans notre template, on “active” Bootstrap de façon triviale. Après avoir téléchargé l'archive de Bootstrap, on copie les fichiers dist/css/bootstrap.min.css et dist/css/bootstrap-theme.min.css dans le répertoire static de notre projet Flask. Il s'agit ici des versions minifiées, privées des retours à la ligne et espacements inutiles. Nous ne nous occupperons pas, dans cet article, des capacités JavaScript de Bootstrap, aussi nous ne copierons pas le fichier dist/js/bootstrap.min.js.
Voici l'allure de notre template bootstrapé :
<!doctype html>
<html>
<head>
<title>Bienvenue, visiteur du futur !</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel=stylesheet type=text/css href="{{ url_for(‘static’,
filename=’bootstrap.min.css’) }}" media="screen">
<link rel=stylesheet type=text/css href="{{ url_for(‘static’,
filename=’bootstrap-theme.min.css’) }}" media="screen">
</head>
<body>
<div class="content" style="padding: 10px; width: 300px;">
<form method="POST" action="/">
<fieldset>
<legend>Identification</legend>
{% for field in form %}
<div class="form-group">
{% if field.type == ‘SubmitField’ %}
{{ field(class="btn btn-primary") }}
{% else %}
{{ field.label }}
{{ field(class="form-control") }}
{% endif %}
</div>
{% endfor %}
</fieldset>
</form>
</div>
</body>
</html>
L'en-tête de ce template est très largement inspirée de l'exemple proposé sur le site de Bootstrap, que ces derniers recommandent de copier verbatim. La seule modification, outre l'absence de l'inclusion de la section JavaScript, réside dans l'utilisation de la fonction Flask url_for('static'), qui renvoie le répertoire contenant les éléments statiques du projet Flask.
La structure du formulaire est elle aussi dictée par la documentation de Bootstrap, cette simple organisation et l'ajout de la classe form-control aux éléments du formulaire a pour immédiate conséquence le rendu suivant :
Fig. 3 : oooOOOOOoooh
Eh oui, tout ce temps je vous avais caché qu'en fait, je suis un puissant web designer r0x0r.
4. Je vois tes yeux briller
Vous imaginez bien que les possibilités du triptique Flask-WTForms-Bootstrap sont très vastes, pensez qu'en écrivant les macros Jinja adéquates, on peut encore factoriser de façon drastique une application web, si complexe soit-elle. Voici deux exemples de macros que j'utilise dans un projet professionnel articulé autour des composants que nous avons expérimenté :
{% set bs_col = ‘col-sm-8’ %}
{% macro input_text(field) -%}
<div class="form-group {{ bs_col }}">
{{ field.label }}
{% if ‘dsc’ in field.description %}
<small><em>( {{ field.description[‘dsc’] }} )</em></small>
{% endif %}
{{ field(class="form-control", placeholder=field.description[‘ph’]) }}
</div>
{%- endmacro %}
{% macro select(field) -%}
<div class="form-group {{ bs_col }}">
{{ field.label }}
{{ field(class="form-control") }}
</div>
{%- endmacro %}
Il est alors possible d'appeler des macros au sein d'autres templates Jinja grâce à la capacité de ce dernier module à importer des fichiers, tels que son langage originel python.
Pour finir, la courbe d'apprentissage des solutions présentées est relativement courte, essentiellement grâce aux excellentes documentations proposées par ces projets (tous Libres, je le rappelle), profitez donc de ces technologies modernes, pourvues d'un rendu exemplaire, qui ne manqueront certainement pas d'épater la galerie.
Liens
[2]: http://fr.wikipedia.org/wiki/Responsive_Web_Design
[3]: http://wtforms.simplecodes.com