DY

06 Formulaire

Nous avons vu comment créer notre schéma de base de données et des entités de manière automatique. Toutefois toute application web a à un moment ou un autre besoin d'utiliser des formulaires. Nous allons donc découvrir dans ce tp comment créer et gérer des saisies utilisateurs.

Flask et les formulaires

Flask ne fournit pas de librairie de gestion de formulaire par défault, il laisse la faculté à l'utilisateur de choisir la librairie qui lui convient.

Découverte de WTForms

WTForms est la librairie de gestion de formulaire. Celle-ci est open source et disponible à cette adresse et pour une rapide introduction.

Installation

Nous allons utiliser Flask WTForms qui est une simple intégration de WTForms au sein de Flask.

Comme toujours un simple pipenv install flask-wtf.

Notre premier formulaire

Nous allons réutiliser la base de données que nous avons créé dans le tp précédent pour permettre l'ajout et l'édition de formulaire.

Nous définissons dans un premier temps notre formulaire au sein du fichier forms.py:

from flask_wtf import FlaskForm
from wtforms import StringField, SelectField
from wtforms.fields.html5 import DateField
from wtforms.validators import DataRequired, Length

class BoardgameForm(FlaskForm):
    pass

Une fois cela fait nous pouvons définir les champs un à un :

class BoardgameForm(FlaskForm):
    name = StringField('Name', validators=[
                       DataRequired(), Length(min=3, max=20)])

Ainsi nous indiquons que le champ name:

class BoardgameForm(FlaskForm):
    name = StringField('Name', validators=[
                       DataRequired(), Length(min=3, max=20)])
    release_date = DateField('Release date', validators=[DataRequired()])

Nous définissons également notre champ date de sortie puis enfin le champ model qui lui est un peu plus complexe:

class BoardgameForm(FlaskForm):
    name = StringField('Name', validators=[
                       DataRequired(), Length(min=3, max=20)])
    release_date = DateField('Release date', validators=[DataRequired()])
    category = SelectField('Category')  

Il s'agit d'un champ de type Select qui possédent comme choix les couples id/version issus de la table Category.

Nous avons défini notre formulaire nous pouvons désormais passé à notre vue.

Vue

Dans notre fichier app.py nous allons créer la vue suivante:

@app.route('/boardgame/create')
def boardgame_create():
    form = BoardgameForm()
    form.category.choices = [(m.id, m.name) for m in Category.select()]
    return render_template('boardgame/form.html', form=form)

Et nous allons également créer le template correspondant.

Template jinja

Créer le fichier boardgames/form.html dans le répertoire template avec le contenu suivant:

{% extends 'base.html' %}

{% block body %}

<h2>Create a boardgame</h2>

<form method="POST">
    {% for field in form %}
        {% if field.widget.input_type != 'hidden' %}
            {{ field.label() }}
            {% if field.errors %}
                {% for error in field.errors %}
                <div class="alert alert-error">{{ error }}</div>
                {% endfor %}
            {% endif %}
        {% endif %}
        {{ field() }}

    {% endfor %}
    <button type="submit">Save</button>
</form>
{% endblock %}

Nous affichons un à un les champs de notre formulaire en n'affichant que si nécessaire les labels. Les messages d'erreurs par champs sont également gérés.

Essayez maintenant de soumettre votre formulaire que se passe t'il ?

Méthodes HTTP

Pour informer Flask qu'il doit accepter d'autres méthodes HTTP que GET il suffit de l'indiquer dans la signature de la route.

@app.route('/boardgame/create', methods=['GET', 'POST', ])
def boardgame_create():
    pass

Vous aurez besoin au préalable d'indiquer une clé secrête à votre application Flask pour que celle-ci puisse gérer la validation csrf.

Par exemple:

app.secret_key = 'HelloWorld' #Don't use it .. !

Essayez maintenant de soumettre votre formulaire que se passe t'il ?

Gestion du formulaire

La méthode validate_on_submit permet de gérer automatiquement la récupération des données depuis le corps de la requête POST et de valider celles-ci.

@app.route('/boardgame/create', methods=['GET', 'POST', ])
def boardgame_create():
    form = BoardgameForm()
    if form.validate_on_submit():
        pass
    return render_template('boardgame/form.html', form=form)

Essayez maintenant de soumettre votre formulaire que se passe t'il ?

Gestion des données en base

Nous allons créer une instance de boardgame (vide) qui sera ensuite peupler depuis notre formulaire via la méthode populate_obj

@app.route('/boardgame/create', methods=['GET', 'POST', ])
def boardgame_create():
    boardgame = Boardgame()
    form = BoardgameForm()
    if form.validate_on_submit():
        form.populate_obj(boardgame)
        boardgame.save()
        flash('Hooray ! Boardgame created !')
        return redirect(url_for('boardgames'))
    return render_template('boardgames/form.html', form=form)

La fonction flash permet de lancer des messages .. flash au sein de notre application. Pour en savoir plus je vous invite à lire encore une fois la documentation.

Vous aurez besoin de mettre à jour votre fichier jinja (base.html ?) avec le contenu suivant:

{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul class=flashes>
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

Essayez maintenant de soumettre votre formulaire que se passe t'il ?

Type de données

Les données reçues depuis le corps d'une requête POST sont toutes de types string. Il convient donc d'indiquer à notre SelectField que le type de données reçu doit être forcer en int.

class BoardgameForm(FlaskForm):
    name = StringField('Name', validators=[
                       DataRequired(), Length(min=3, max=20)])
    release_date = DateField('Release date', validators=[DataRequired()])
    category = SelectField('Category', coerce=int)

Définir un formulaire plus simplement

Il existe une librairie qui permet de faire le lien entre un model peewee et un formulaire wtf, celle-ci se nomme wtf-peewee. Vous savez désormais comment l'installer dans votre environnement virtuel.

Créer la classe formulaire automatiquement

Au sein de notre fichier forms.py:

from wtfpeewee.orm import model_form


SimpleBoardgameForm = model_form(Boardgame)
Utilisation de cette classe

Voici un exemple de vue pour l'édition d'un boardgame:

@app.route('/baordgame/edit/<int:boardgame_id>/', methods=['GET', 'POST'])
def boardgame_edit(boardgame_id):
    try:
        boardgame = Boardgame.get(id=boardgame_id)
    except Entry.DoesNotExist:
        abort(404)

    if request.method == 'POST':
        form = SimpleBoardgameForm(request.form, obj=boardgame)
        if form.validate():
            form.populate_obj(boardgame)
            boardgame.save()
            flash('Your entry has been saved')
    else:
        form = SimpleBoardgameForm(obj=boardgame)

    return render_template('boardgames/form.html', form=form, boardgame=boardgame)

À vous de jouer

  1. Créer une route éditer un jeu;
  2. Créer une route pour ajouter/modifier une catégorie;
  3. Créer une route pour supprimer un jeu.

Conclusion

Nous venons de voir que gérer les formulaires et leurs validations est extrèmement simple avec les bons outils.