Ajout d'un formulaire de contact à une page multilingue avec le contenu d'une base de données
Lorsque le contenu de la page provient d'une base de données, vous voudrez ajouter un formulaire de contact en utilisant une balise.
Mise à jour 11 octobre 2019 : J'ai changé le tag addon de'{% addon : .... %}' à '[[ addon : .... ]]'. La raison est que je voulais être capable de rendre le texte de la page provenant de la base de données, en utilisant render_template_string, et'{% ... %}' conflits avec les Jinja2 balises. Et oui, je ne veux pas implémenter un tag Jinja2 personnalisé.
Qu'est-ce qui est difficile dans la mise en œuvre d'une page de contact avec un formulaire de contact avec Flask et WTForms? Vous pouvez trouver des solutions sur la façon d'implémenter une page de contact dans Flask mais chaque fois que la page est une page en une seule langue et utilise un fichier Jinja2 modèle. Alors pourquoi écrire un post à ce sujet ?
La raison en est que ce n'est pas trivial lorsque le contenu de la page peut être multilangue et provient d'une base de données. J'ai le contenu de la page que je peux éditer en utilisant l'administrateur, et je veux que le formulaire de contact soit placé quelque part sur la page en utilisant un tag. Pourquoi un tag ? Parce que nous devons être en mesure de mettre le formulaire de contact à n'importe quel endroit dans le contenu. Une fois que l'étiquette est remplacée par le formulaire de contact, elle doit également être traitée au moment de l'envoi. Facile ? Peut-être pour toi, mais pas pour moi.
Présentation des add-ons
En regardant d'autres solutions, j'ai pensé qu'il serait utile d'implémenter le formulaire de contact en tant qu'add-on. Pourquoi ? Parce qu'un add-on est quelque chose que vous devriez pouvoir ajouter très facilement à votre contenu. Il devrait également être possible d'ajouter le formulaire de contact à plusieurs pages. Il y a plus qu'un add-on, par exemple l'add-on formulaire de contact ajoute également une fonction formulaire de contact à l'admin où nous pouvons regarder les formulaires de contact qui ont été soumis.
Mise en œuvre de l'add-on
La première chose que j'ai faite a été de définir une balise qui identifierait le module complémentaire du formulaire de contact :
{% addon:contact_form,id=87 %}
C'est la balise que nous pouvons ajouter au contenu de notre page multilingue qui provient de la base de données. Les autres composants de l'add-on du formulaire de contact sont :
- ContactForm, le modèle (tableau)
- Partie administrative, où l'on peut voir les formulaires soumis
Et puis nous avons besoin d'un mécanisme général qui traite l'add-on lorsque nous affichons une page. Comme vous vous en souvenez peut-être dans un message précédent, il n'y a qu'une seule fonction qui génère une page. Comme le contenu ne change pas, il est mis en cache :
@pages_blueprint.route('/<slug>', methods=['GET', 'POST'])
def page_view(slug):
...
# get content_item
...
# render content_item of get from cache
hit, rendered_content_item = current_app.app_cache.load(cache_item_id)
if not hit:
rendered_content_item = render_template(
...
content_item=content_item,
content_item_translation=content_item_translation,
)
# cache it
current_app.app_cache.dump(cache_item_id, rendered_content_item)
...
return render_template(
...
rendered_content_item=rendered_content_item,
)
Cette fonction doit être modifiée et étendue pour qu'elle soit capable de gérer les add-ons.
Conversion de MVC en classe
Dans l' Flask utilisation WTForms du formulaire de contact, par exemple, la mise en œuvre est très simple :
@pages_blueprint.route('/contact-form', methods=['GET', 'POST'])
def contact_form():
form = ContactFormForm()
if form.validate_on_submit():
contact_form = ContactForm()
form.populate_obj(contact_form)
db.add(contact_form)
db.commit()
flash( _('Contact form submitted.'), 'info')
return redirect(url_for('pages.thank_you'))
return render_template(
'pages/contact_form.html',
form=form)
Et le ContactFormFormFormForm est :
class ContactFormForm(FlaskForm):
name = StringField(_l('Name'), validators=[
Length(min=2, max=60),
InputRequired()])
email = StringField(_l('Your email'), validators=[
InputRequired(),
Email()])
message = TextAreaField(_l('Your message'), validators=[
Length(min=6, max=500),
InputRequired()])
submit = SubmitField(_l('Send'))
On ne peut pas l'utiliser ici, alors on réécrit ça en classe. J'ai décidé de retourner le succès ou l'erreur pour les méthodes GET et POST et j'ai une méthode séparée pour obtenir le formulaire de contact rendu.
class AddonContactForm:
def __init(self)__:
...
self.errors = False
self.rendered_contact_form = ''
def get_contact_form(self):
self.errors = False
form = ContactFormForm()
self.rendered_contact_form = render_template(
'addons/contact_form.html',
form=form)
return self.errors
def get_rendered_contact_form(self):
return self.rendered_contact_form
def post_contact_form(self):
self.errors = False
form = ContactFormForm()
if form.validate_on_submit():
contact_form = ContactForm()
form.populate_obj(contact_form)
db.add(contact_form)
db.commit()
flash( _('Contact form submitted.'), 'info')
return redirect(url_for('pages.thank_you'))
self.errors = True
self.rendered_contact_form = render_template(
'addons/contact_form.html',
form=form)
return self.errors
Le ContactFormFormFormForm est étendu avec un paramètre caché identifiant l'add-on :
addon_id = HiddenField('Addon id')
En utilisant ceci nous pouvons maintenant changer la fonction page_view :
@pages_blueprint.route('/<slug>', methods=['GET', 'POST'])
def page_view(slug):
...
if request.method == 'POST':
addon_id = None
if 'addon_id' in request.form:
addon_id = request.form['addon_id']
if addon_id is not None:
if addon_id == 'contact_form':
addon_contact_form = AddonContactForm()
if addon_contact_form.process_contact_form():
addon_redirect_url = addon_contact_form.get_redirect_url()
return redirect(addon_redirect_url)
# error(s) found during processing
rendered_contact_form = addon_contact_form.get_rendered_contact_form()
addon_error = True
addon_error_message = addon_contact_form.get_error_message()
....
# addon: processing if '{% addon' found
if '{% addon:' in rendered_content_item:
m = re.findall('\{\%\s*(addon)\s*\:\s*([a-z_]+)\s*.*?\%\}', rendered_content_item)
addon_name = None
if m:
addon_name = m[0][1]
if addon_name == 'contact_form':
if request.method == 'GET':
addon_contact_form = AddonContactForm()
if addon_contact_form.get_contact_form():
rendered_contact_form = addon_contact_form.get_rendered_contact_form()
else:
rendered_contact_form = ''
error = True
error_message = addon_contact_form.get_error_message()
rendered_content_item = re.sub('\{\%\s*(addon)\s*\:\s*([a-z_]+)\s*.*?\%\}', rendered_contact_form, rendered_content_item)
elif request.method == 'POST':
# here we just paste the result from the addon
# typically we only come here when an error was detected in the form
rendered_content_item = re.sub('\{\%\s*(addon)\s*\:\s*([a-z_]+)\s*.*?\%\}', rendered_contact_form, rendered_content_item)
return render_template(
...
)
Résumé
Ce qui précède n'est qu'un résumé, il y a plus, mais je voulais juste vous donner les bases. J'ai aussi implémenté un add-on FAQ, où nous n'avons à traiter qu'avec un GET. Vous pouvez consulter les pages Contact et FAQ sur ce site. Ce n'était qu'une première tentative d'implémentation d'add-ons, et non, ce n'est pas définitif. Je dois maintenant définir une interface claire de toutes les méthodes et attributs qu'un add-on peut ou doit utiliser. Une autre fois, ...
Liens / crédits
Exact difference between add-ons, plugins and extensions
https://stackoverflow.com/questions/33462500/exact-difference-between-add-ons-plugins-and-extensions
Intro to Flask: Adding a Contact Page
https://code.tutsplus.com/tutorials/intro-to-flask-adding-a-contact-page--net-28982
En savoir plus...
Flask Jinja2 Multilanguage WTForms
Récent
- Masquer les clés primaires de la base de données UUID de votre application web
- Don't Repeat Yourself (DRY) avec Jinja2
- SQLAlchemy, PostgreSQL, nombre maximal de lignes par user
- Afficher les valeurs des filtres dynamiques SQLAlchemy
- Transfert de données sécurisé grâce au cryptage à Public Key et à pyNaCl
- rqlite : une alternative à haute disponibilité et dist distribuée SQLite
Les plus consultés
- Utilisation des Python's pyOpenSSL pour vérifier les certificats SSL téléchargés d'un hôte
- Utiliser UUIDs au lieu de Integer Autoincrement Primary Keys avec SQLAlchemy et MariaDb
- Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
- Utiliser PyInstaller et Cython pour créer un exécutable Python
- SQLAlchemy : Utilisation de Cascade Deletes pour supprimer des objets connexes
- Flask RESTful API validation des paramètres de la requête avec les schémas Marshmallow