UE5 : Fondamentaux du développement web frontal, niveau 2

Séance 5 : TD : Les formulaires

L'objectif de ce TD est d'implémenter des formulaires en POST afin que l'utilisateur puisse interagir avec les données.

Installations et configurations

Afin que tout le monde réalise ce TD sur une application Flask sans erreurs et correctement configurée:

  1. Installer sur son PC l'application Seance5_base_TD
  2. Y installer un environnement virtuel Python3
  3. Installer les dépendances de l'application
  4. Télécharger la base de données factbook.sqlite quelque part sur son PC
  5. Remplir les variables SQLALCHEMY_DATABASE_URI, RESOURCES_PER_PAGE et PAYS_PER_PAGE dans le fichier .env

Effectuer un filtre multi-critères sur les données à afficher

Le but de cette partie est d'obtenir, sur la future page /recherche, un formulaire qui permette de filtrer sur le nom du pays, une de ses ressources, son continent.

h:300px

Suivant la méthodologie décrite en cours, suivre les quatre étapes suivantes:

Classe FlaskForm

De même que SQLAlchemy est une librairie utilisée dans Flask-SQLAlchemy pour interagir avec une base de données, il existe une librairie Flask-WTF utilisant WTForms pour la gestion des formulaires. Cette librairie facilitera le développement en:

D'abord, il faut installer Flask-WTF:

pip install flask-wtf

Les classes représentant nos formulaires sont finalement des modèles au même titre que celles de la base de données. C'est pour cela que l'on peut les ranger dans app/models/ dans un fichier formulaires.py. Ces modèles détermineront quelles données nos formulaires vont capturer, de même que les logiques de validation de ces dernières.

Comme pour toute extension, Flask-WTF nécessite une petite configuration initiale avec 2 variables d'environnement supplémentaires:

En utilisant la documentation et celle-ci, écrire la classe Recherche() représentant un formulaire contenant les entrées suivantes:

Template du formulaire

Les formulaires pouvant être utilisés à divers endroits de l'application, il convient de les considérer comme des "partials".

Construction de la route /recherche

Voici ci-dessous la route écrite:

from ..app import app, db
    from flask import render_template, request
    from ..models.factbook import Country
    from ..models.formulaires import Recherche
    from ..utils.transformations import  clean_arg

    ...

    @app.route("/recherche", methods=['GET','POST'])
    @app.route("/recherche/<int:page>", methods=['GET','POST'])
    def recherche(page=1):
        form = Recherche() 

        # récupération des éventuels arguments de l'URL qui seraient le signe de l'envoi d'un formulaire
        nom_pays =  clean_arg(request.form.get("nom_pays", None))
        ressource =  clean_arg(request.form.get("ressources", None))
        continent =  clean_arg(request.form.get("continents", None))

        # initialisation des données de retour dans le cas où il n'y ait pas de requête
        donnees = []

        if form.validate_on_submit():
            # si l'un des champs de recherche a une valeur, alors cela veut dire que le formulaire a été rempli et qu'il faut lancer une recherche 
            # dans les données
            if nom_pays  or continent or ressource:
                # initialisation de la recherche; en fonction de la présence ou nom d'un filtre côté utilisateur, nous effectuerons des filtres SQLAlchemy,
                # ce qui signifie que nous pouvons jouer ici plusieurs filtres d'affilée
                query_results = Country.query

                if nom_pays:
                    query_results = query_results.filter(Country.name.ilike("%"+nom_pays.lower()+"%"))
                
                if ressource:
                    resource = db.session.execute(text("""select a.id from country a 
                        inner join country_resources b on b.id = a.id and b.resource  == '"""+ressource+"""'
                        """)).fetchall()
                    query_results = query_results.filter(Country.id.in_([r.id for r in resource] ))
                
                if continent:
                    map = db.session.execute(text("""select a.id from country a 
                        inner join country_map b on b.id = a.id and map_ref == '"""+continent+"""'
                        """)).fetchall()
                    query_results = query_results.filter(Country.id.in_([m.id for m in map] ))
                
                donnees = query_results.order_by(Country.name).paginate(page=page, per_page=app.config["PAYS_PER_PAGE"])

                # renvoi des filtres de recherche pour préremplissage du formulaire
                form.nom_pays.data = nom_pays
                form.continents.data = continent
                form.ressources.data = ressource

        return render_template("pages/resultats_recherche.html", 
                sous_titre= "Recherche" , 
                donnees=donnees,
                form=form)

Template de la route

Le template de la route a déjà été rempli lors de l'inclusion du template du formulaire. La route est accessible à localhost:5000/recherche.

Insérer des données avec un formulaire

Développer une route POST /insertions/pays qui permette d'ajouter un pays dans la base de données selon la méthodologie vue précédemment. Le résultat attendu est :

!h:300px !h:300px