Ci-dessous, les différences entre deux révisions de la page.
| Prochaine révision | Révision précédente | ||
| dev:python:flask:flask_formulaires_wtforms [2025/02/23 16:17] – créée yoann | dev:python:flask:flask_formulaires_wtforms [2026/04/12 10:26] (Version actuelle) – yoann | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| - | {{tag> | + | {{tag> |
| - | :TODO: | + | :TODO_DOCUPDATE: |
| - | ====== | + | ====== |
| + | [[https:// | ||
| + | |||
| + | Ici on utilise le paquet Flask-WTF qui intègre la bibliothèque WTForms au micro framework Flask. | ||
| + | |||
| + | ===== Initialiser l' | ||
| + | |||
| + | Création d'un dépôt git pour le projet et initialisation de l' | ||
| + | |||
| + | <code bash> | ||
| + | git init hello-wtf | ||
| + | cd hello-wtf/ | ||
| + | git branch -m main | ||
| + | |||
| + | # Création de l' | ||
| + | python3 -m venv venv | ||
| + | source venv/ | ||
| + | pip install Flask Flask-WTF email_validator | ||
| + | |||
| + | # Création de l' | ||
| + | mkdir -p helloforms/ | ||
| + | touch helloforms/ | ||
| + | chmod a+x helloforms/ | ||
| + | </ | ||
| + | |||
| + | |||
| + | Peupler le fichier '' | ||
| + | |||
| + | <code python __init__.py> | ||
| + | # | ||
| + | |||
| + | from flask import Flask | ||
| + | |||
| + | |||
| + | app = Flask(__name__) | ||
| + | |||
| + | |||
| + | @app.route('/' | ||
| + | def show_home_page(): | ||
| + | """ | ||
| + | |||
| + | """ | ||
| + | return '< | ||
| + | |||
| + | |||
| + | |||
| + | if __name__ == ' | ||
| + | |||
| + | # execution en mode debug | ||
| + | app.run(debug=True) | ||
| + | |||
| + | </ | ||
| + | |||
| + | |||
| + | Tester l' | ||
| + | |||
| + | <code bash> | ||
| + | FLASK_APP=helloforms FLASK_ENV=development flask run --debug | ||
| + | </ | ||
| + | |||
| + | A ce stade la commande doit lancer le serveur web et afficher un message du type : | ||
| + | |||
| + | < | ||
| + | * Serving Flask app ' | ||
| + | * Debug mode: on | ||
| + | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. | ||
| + | * Running on http:// | ||
| + | Press CTRL+C to quit | ||
| + | * Restarting with stat | ||
| + | * Debugger is active! | ||
| + | * Debugger PIN: 845-679-934 | ||
| + | </ | ||
| + | |||
| + | Ouvrir son navigateur à l'URL '' | ||
| + | |||
| + | {{capture-2025-02-27_01.png}} | ||
| + | |||
| + | |||
| + | Pour la mise en forme, on utilise le framework Spectre CSS | ||
| + | |||
| + | |||
| + | ===== Création du formulaire ===== | ||
| + | |||
| + | * Pour créer un formulaire, on définit une classe héritant de **FlaskForm**. | ||
| + | * Chaque attribut de la notre classe représentant un champ de formulaire sera un objet héritant de la classe de base '' | ||
| + | * L' | ||
| + | |||
| + | |||
| + | <code python> | ||
| + | from flask_wtf import FlaskForm | ||
| + | from wtforms import EmailField, SubmitField | ||
| + | from wtforms.fields.simple import BooleanField | ||
| + | |||
| + | class NewsLetterForm(FlaskForm): | ||
| + | |||
| + | email = EmailField(label=' | ||
| + | news_letter = BooleanField(label=" | ||
| + | submit_button = SubmitField(label=' | ||
| + | </ | ||
| + | |||
| + | Comme illustré ci-dessus, le constructeur des objets héritant de la classe de base '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | |||
| + | La liste exhaustive des paramètres est disponible dans la documentation [[https:// | ||
| + | |||
| + | |||
| + | Quelques objets représentant les champs de base des formulaires : | ||
| + | |||
| + | * **StringField** : Champ type texte ; | ||
| + | * **TextAreaField** : | ||
| + | * **SelectField** : | ||
| + | * **BooleanField** : Champ de type checkbox ; | ||
| + | * **PasswordField** | ||
| + | * **HiddenField** : Champ masqué ; | ||
| + | * **SubmitField** : Bouton soumission du formulaire ; | ||
| + | |||
| + | |||
| + | Champs plus spécifiques : | ||
| + | * **EmailField** ; | ||
| + | * **TelField** ; | ||
| + | * **URLField** ; | ||
| + | * **TimeField** ; | ||
| + | * **ColorField** ; | ||
| + | * **IntegerField** : Saisie d'un nombre entier; | ||
| + | * **IntegerRangeField** | ||
| + | * **FloatField** | ||
| + | * **DecimalRangeField** ; | ||
| + | * **FileField** | ||
| + | |||
| + | |||
| + | |||
| + | ===== Les filtres ===== | ||
| + | |||
| + | Les filtres (filters) permettent de traiter, en amont de la validation, les valeurs saisies par l' | ||
| + | |||
| + | * Retirer les espaces en début et fin de saisie ; | ||
| + | * Passer la saisie en majuscule, en minuscule ; | ||
| + | * Arrondir les valeurs décimales etc. | ||
| + | |||
| + | |||
| + | <code python> | ||
| + | from flask_wtf import FlaskForm | ||
| + | from wtforms import StringField | ||
| + | |||
| + | class RegisterForm(FlaskForm): | ||
| + | |||
| + | lastname = StringField(label=' | ||
| + | | ||
| + | | ||
| + | |||
| + | </ | ||
| + | |||
| + | Ci-dessus, on utilise les filtres pour retirer les espaces autour de la saisie et la passer en majuscules. | ||
| + | |||
| + | ===== Les validateurs ===== | ||
| + | |||
| + | Les **validators** permettent de contrôler la saisie de l' | ||
| + | |||
| + | |||
| + | <code python> | ||
| + | from flask_wtf import FlaskForm | ||
| + | from wtforms import StringField | ||
| + | from wtforms.validators import InputRequired, | ||
| + | |||
| + | class RegisterForm(FlaskForm): | ||
| + | |||
| + | email = StringField(label=' | ||
| + | </ | ||
| + | |||
| + | Ici on instancie deux validators pour contraindre la saisie de l' | ||
| + | * Le champs ne doit pas être vide, | ||
| + | * La saisie doit respecter le formalisme d'un email. | ||
| + | |||
| + | Dans ce cas, peut également utiliser la classe '' | ||
| + | |||
| + | <code python> | ||
| + | from flask_wtf import FlaskForm | ||
| + | from wtforms import EmailField | ||
| + | |||
| + | |||
| + | class RegisterForm(FlaskForm): | ||
| + | |||
| + | email = EmailField(label=' | ||
| + | </ | ||
| + | |||
| + | Il existe de nombreux validators citons par exemple **InputRequired**, | ||
| + | |||
| + | * Ici la [[https:// | ||
| + | |||
| + | |||
| + | |||
| + | ===== Rendu du formulaire dans le template ===== | ||
| + | |||
| + | Du côté du template, on peut s' | ||
| + | |||
| + | <code html _helpers.html> | ||
| + | {# | ||
| + | Produire le code HTML d'un champ de formulaire. | ||
| + | #} | ||
| + | |||
| + | {% macro render_field_with_div(field) %} | ||
| + | <div> | ||
| + | {{ field.label }} {{ field(**kwargs)|safe }} | ||
| + | {% if field.errors %} | ||
| + | <ul class=errors> | ||
| + | {% for error in field.errors %} | ||
| + | < | ||
| + | {% endfor %} | ||
| + | </ul> | ||
| + | {% endif %} | ||
| + | </ | ||
| + | {% endmacro %} | ||
| + | </ | ||
| + | |||
| + | Une fois la macro définie, on peut l' | ||
| + | |||
| + | <code html> | ||
| + | <div> | ||
| + | {% from " | ||
| + | <form method=" | ||
| + | {{ form.csrf_token }} | ||
| + | {{ render_field_with_div(form.first_name) }} | ||
| + | {{ render_field_with_div(form.last_name) }} | ||
| + | {{ render_field_with_div(form.email) }} | ||
| + | {{ render_field_with_div(form.subject) }} | ||
| + | {{ render_field_with_div(form.message) }} | ||
| + | {{ render_field_with_div(form.submit_button) }} | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | ===== Validation ===== | ||
| + | |||
| + | C'est la fonction contrôleur de la vue qui se charge de valider le formulaire retourné en méthode POST. | ||
| + | |||
| + | <code python [highlight_lines_extra=" | ||
| + | @frontend_bp.post('/ | ||
| + | def parse_contact_form(): | ||
| + | """ | ||
| + | Analyse le formulaire de contact renseigné par l' | ||
| + | """ | ||
| + | from app.services.forms import ContactForm | ||
| + | form = ContactForm() | ||
| + | if form.validate_on_submit(): | ||
| + | # Les valeurs saisies sont correctes | ||
| + | return redirect(url_for(' | ||
| + | else: | ||
| + | # Reafficher le formulaire pour permettre à l' | ||
| + | flash(' | ||
| + | return render_template(' | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | L' | ||
| + | </ | ||
| + | |||
| + | La méthode '' | ||
| + | |||
| + | |||
| + | ===== Débogages ===== | ||
| + | |||
| + | ==== Échec systématique de la validation ==== | ||
| + | |||
| + | Lors du traitement du formulaire dans la vue, celui-ci est systématiquement considéré comme invalide. | ||
| + | |||
| + | |||
| + | Lorsqu' | ||
| + | |||
| + | <code html> | ||
| + | <form method=" | ||
| + | {{ form.csrf_token }} | ||
| + | | ||
| + | <!-- vos autres champs ici --> | ||
| + | |||
| + | <input type=" | ||
| + | </ | ||
| + | </ | ||
| ===== Références ===== | ===== Références ===== | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| * https:// | * https:// | ||
| * https:// | * https:// | ||
| + | * Vidéo [[https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * [[https:// | ||
| + | |||
| + | |||