angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Flask mit mehreren Formularen auf einer Seite, die mit AJAX gepostet wurden und ein gerendertes Formularergebnis zurückgeben

Die Rückgabe eines gerenderten Formulars an den Kunden bedeutet, dass der Kunde nicht viel über das Formular wissen muss und die kundenseitige Kodierung reduziert.

22 Februar 2020
post main image
https://unsplash.com/@emilydafinchy

Bei der Arbeit am Kommentarsystem stieß ich zum ersten Mal auf das Problem, dass mehrere WTForms -Formulare auf einer einzigen Seite vorhanden sind. Auf jeder Seite gibt es auch das Suchformular, aber dies ist kein POST -Formular. Es macht einen GET. Das Kommentarsystem verwendet zwei Formen:

  • Comment form
  • Comment reply form

Das Kommentarformular befindet sich unmittelbar unter dem Inhaltselement, dem Blogbeitrag oder der Seite, das Kommentarantwort-Formular wird zunächst ausgeblendet und eingefügt und angezeigt, wenn der Besucher auf die Schaltfläche Antworten klickt. Die Formulare sind fast identisch, der Hauptunterschied besteht darin, dass das Kommentar-Antwort-Formular einen zusätzlichen Parameter hat, die parent_id. Wenn das Kommentarantwort-Formular angezeigt wird, wird die parent_id in das Formular kopiert. Andere Unterschiede sind eher textlicher Natur. Beide Formulare könnten in ein einziges Formular gequetscht werden, was jedoch andere Schwierigkeiten mit sich bringen würde, wie das Hinzufügen von Zusatzcode für den Formularwechsel und das Kopieren von Formulardaten. Wir könnten die Formulare auch ohne AJAX veröffentlichen, aber letztendlich wollen wir hier AJAX verwenden.

Verwendung eines Formulars in einer Vorlage mit HTML

Der traditionelle Weg, dies zu tun, besteht darin, das Formular zu senden und eine (JSON) Antwort zu erhalten, die nur aus dem Status besteht. Sie müssen den Status entschlüsseln und im Falle von Fehlern die richtigen Fehlermeldungen irgendwo auf der Seite einstellen.

Die Lösung, die ich verwenden möchte, ist eine andere und zielt darauf ab, den clientseitigen Code zu minimieren, ich habe bereits so etwas für das Kontaktformular verwendet. Die Idee ist, dass die Seite nichts über das Formular und seine Antwort weiß. Für ein Formular verwende ich eine Jinja -Vorlage, die auch die Fehlermeldungen und andere Meldungen enthält. Ich verwende das Makro (Bootstrap), um alle Formulare auf der Website zu rendern, und dazu gehört auch das Rendern der Fehlermeldungen. Ein weiterer Vorteil dieses Ansatzes ist, dass alle Formulare und Fehlermeldungen gleich aussehen, keine Duplizierung im Client.

Der Titel ist in dieser Vorlage nicht enthalten, da ich diesen Teil der Kommentarseite betrachte. Auf dem Server verwenden wir die Standardverarbeitung WTForms und rendern die Formularvorlage. Dann wird die gerenderte Vorlage an den Kunden zurückgeschickt. Ich denke, Sie sehen hier den Vorteil. Die Logik auf der Client-Seite ist anders, da wir an den richtigen Positionen (ids) statt der Warnmeldungen und, im Falle eines Fehlers, der Fehlermeldungen, nun einen Teil der HTML auf der Seite ersetzen.

Zwei WTForms Formulare auf einer Seite

Mein erster Versuch war nur, beide Formulare auf die Seite zu bringen. Ich habe Ihnen in einem früheren Blogbeitrag gesagt, dass ich Formulare mit Hilfe eines Makros auf die Seite gestellt habe:

	{{ bootstrap_form(comment_form, id='comment_form', form_action=url_for('pages.content_item_comment_new')) }}
    ...
	{{ bootstrap_form(comment_reply_form, id='comment_reply_form', form_action=url_for('pages.content_item_comment_new')) }}

Ich habe das id-Attribut der Formulare gesetzt, damit ich auf die Formulare in jQuery zugreifen kann. Dann begannen die roten Konsolenmeldungen zu kommen, CSRF Token-Feld-ID war doppelt, Submit-Feld-ID war doppelt. Dies ist in HTML5 nicht erlaubt! In HTML5 muss eine ID im Dokument eindeutig sein.

Bei der Suche im Internet und beim Lesen der Dokumentation zu WTForms stellte sich heraus, dass die Formularklasse mit dem Parameter 'prefix' versehen ist. Die Beschreibung lautet:

prefix - Falls vorhanden, wird allen Feldern der Name prefix mit dem Wert zugeordnet.

Die Verwendung ist sehr einfach. Ich habe die folgenden Formulare:

class ContentItemCommentForm(FlaskForm):

    message = TextAreaWithCounterField(_l('Your message'), 
        validators=[ InputRequired(), Length(min=6, max=1000) ] )

    submit = SubmitField(_l('Add'))


class ContentItemCommentReplyForm(FlaskForm):

    parent_uid = HiddenField('parent uid')

    message = TextAreaWithCounterField(_l('Your message'), 
        validators=[ InputRequired(), Length(min=6, max=1000) ] )

    submit = SubmitField(_l('Add'))

In der Ansichtsfunktion fügen wir einfach die prefixes hinzu:

    ...
    comment_form_prefix  = 'comment_form'
    comment_form = ContentItemCommentForm(prefix=comment_form_prefix)

    comment_reply_form_prefix  = 'comment_reply_form'
    comment_reply_form = ContentItemCommentReplyForm(prefix=comment_reply_form_prefix)

    if request.method == 'POST':

        if comment_form.submit.data and comment_form.validate_on_submit():
            ...
            # message
            message = comment_form.message.data
            ...

        elif comment_reply_form.submit.data and comment_reply_form.validate_on_submit():
            ...
            # parent_id
            parent_id = comment_reply_form.parent_id.data

            # message
            message = comment_reply_form.message.data
            ...

    return  render_template(
        ...
        )

Um die Formulare auf die Seite zu bringen, machen wir dasselbe wie oben, keine Änderungen hier. Der Zugriff auf Formularelemente von jQuery aus ist ebenfalls einfach, wenn man das Formular prefixes kennt. Um beispielsweise das versteckte Feld parent_id des Kommentar-Antwort-Formulars zu setzen, können wir etwas wie folgt tun:

    $('#comment_reply_form-parent_id').val(parent_comment_id);

Alle Elemente haben eindeutige IDs und sind leicht zu kodieren und zu lesen. So weit, so gut.

Buchung der Formulare mit AJAX

Der Code jQuery für die Veröffentlichung eines Formulars ist nicht wirklich schwierig. Viele Beispiele sind im Internet zu finden. Wie oben erläutert, verarbeitet der Server bei einem Submit das Formular. Im Falle eines Fehlers gibt der Server einfach das gerenderte Formular erneut zurück, einschließlich der Warn- und Fehlermeldungen. Die Client-Logik ersetzt einen Teil der HTML auf der Seite durch die zurückgegebene HTML . Wenn es keine Fehler gibt, gibt der Server eine anchor -ID des hinzugefügten Kommentars zurück. Die Client-Logik führt dann eine Umleitung auf dieselbe Seite unter Verwendung der Seitenurl mit dieser anchor durch. Dies kann später optimiert werden.

Die Funktion jQuery serialize() enthält nicht die Schaltfläche "Submit".

Ich verwende die Funktion jQuery serialize() , um die Daten des Formulars zu erhalten. Leider enthält jQuery serialize() nicht die Schaltfläche zum Absenden, da das Formular mehrere Schaltflächen zum Absenden enthalten kann. Da ich den oben beschriebenen Mechanismus verwenden möchte (um festzustellen, welches Formular eingereicht wurde, muss ich die Schaltfläche "Submit" ankreuzen), füge ich die Schaltfläche "Submit" den serialisierten Formulardaten auf eine andere Art und Weise an, die nicht wirklich schwierig ist. Nach dem Aufruf der Funktion jQuery replace() müssen wir die Ereignisse erneut anfügen.

In der Funktion $(document).ready werden zwei Ereignisse an ein Formular angehängt:

  • Die Veranstaltung zur Veröffentlichung des Formulars
  • Das Ereignis zur Anzeige der Anzahl der verbleibenden Zeichen

Nach dem Ersetzen des HTML gehen diese Bindungen verloren und müssen wieder angebracht werden. Wiederum nicht wirklich schwierig. Vielleicht können wir auch eine Veranstaltungsdelegation verwenden, aber ich habe das nicht recherchiert.

Die Formularvorlagen

Nachfolgend finden Sie die Vorlage des Kommentarformulars. Die Kommentar-Antwortvorlage ist fast identisch. Der Server kann Warn- und Fehlermeldungen hinzufügen, und der Client weiß nichts davon. Im Falle eines Fehlers wird das Element mit der id comment_form-post-container durch die gerenderte Form HTML ersetzt, die vom Aufruf AJAX zurückgegeben wird.

<div id="comment_form-post-container">

    {% if comment_alert %}
    <div class="alert alert-info alert-dismissible fade show" role="alert">
        {{ comment_alert_message }}
        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>

    </div>
    {% endif %}

    {% if comment_error and comment_error_type == 2 %}
    <div class="alert alert-danger alert-dismissible fade show" role="alert">
        {{ comment_error_message }}
        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>

    </div>
    {% endif %}

    {{ bootstrap_form(comment_form, id='comment_form', form_action=url_for('pages.content_item_comment_new')) }}

</div>

Das Kommentarformular wird durch Einfügen in die Seite eingefügt:

    <div class="row mt-2">
        <div class="col-12 p-0">

            <h2>{{ _('Add comment') }}</h2>

        </div>
    </div>

    <div class="row mb-2">
        <div class="col-12 p-0">

            {%- include "pages/comment_form.html" -%}

        </div>
    </div>

Der Kunde jQuery Code:

$(document).ready(function(){
    ...
    // comment form: post
    $('#comment_form').on('submit', function(event){
        event.preventDefault();
        post_comment_form('comment_form');
        return false;
    });

    // comment form: character count
    update_character_count('comment_form-message');
    $('#comment_form-message').on('change input paste', function(event){
        update_character_count('comment_form-message');
    });
    ...
});

Und den Code zum Absenden des Formulars:

function post_comment_form(form_id){

    var form = $('#'  +  form_id)

    // submit button must be added to the form data
    var submit_button = $('input[type=submit]', form);

    // get url 
    var page_url =  window.location.href.replace(/#.*/, '');
    page_url = page_url.replace(/\?.*/,"");

    $.ajax({
        data: $('#'  +  form_id).serialize()   +  '&'  +  encodeURI( $(submit_button).attr('name') )  +  '='  +  encodeURI( $(submit_button).val() ),
        type: 'POST',
        url: url_comment_new,
        contentType: 'application/x-www-form-urlencoded; charset=UTF-8', 
        dataType: 'json'
    })
    .done(function(data){
        if(data.error){

            // replace
            $('#'  +  form_id  +  '-post-container').replaceWith(data.rendered_form);

            // attach submit event again 
            $('#'  +  form_id).on('submit', function(event){
                event.preventDefault();
                post_comment_form(form_id);
                return false;
            });

            // attach character count event again 
            update_character_count(form_id  +  '-message');
            $('#'  +  form_id  +  '-message').on('change input paste', function(event){
                update_character_count(form_id  +  '-message');
            });

        }else{

            var new_url = page_url  +  '?t='  +  (+new Date())  +  '#'  +  data.comment_anchor;
            window.location.href = new_url;

        }
    });
}

Mit einem anchor zu einem eingefügten Kommentar springen

Nachdem der Server einen Kommentar in die Datenbank eingefügt hat, müssen wir den neuen Baum der Kommentare anzeigen. Wir können dies tun, indem wir auf dem verwendenden Client-Code bleiben, aber für den Moment entscheide ich mich dafür, einen anchor an den AJAX -Aufruf zurückzugeben. Die Seite wird dann mit der Seitenurl und dem anchor aufgerufen und aktualisiert. Ich füge einen Zeitstempel hinzu, um Probleme mit dem Browser-Caching zu vermeiden. In HTML5 wird die ID als anchor verwendet. Da ich Bootstrap mit einer klebrigen Navbar verwende, müssen wir auch die zusätzlichen Navbar-Pixel ausgleichen:

.anchor5 {
    display: block;
    position: relative;
    top: -100px;
    visibility: hidden;
}

Zusammenfassung

Die obenstehende Abbildung zeigt eine mögliche Implementierung von Flask, WTforms und AJAX. Ich habe versucht, den Code auf der Client-Seite zu reduzieren, indem ich ein gerendertes Formular mit Warn- und Fehlermeldungen statt nur mit Fehlermeldungen zurückschickte. Der Schutz von CSRF funktioniert auch, weil wir FlaskForm verwenden. Aus der Flask_WTF-Erweiterungsdokumentation: Jede Ansicht, die FlaskForm zur Bearbeitung der Anfrage verwendet, erhält bereits den Schutz von CSRF . Es gibt noch viel mehr zu tun, aber ich dachte, ich könnte Sie daran teilhaben lassen.

Links / Impressum

Combining multiple forms in Flask-WTForms but validating independently
https://dev.to/sampart/combining-multiple-forms-in-flask-wtforms-but-validating-independently-cbm

flask-bootstrap with two forms in one page
https://stackoverflow.com/questions/39738069/flask-bootstrap-with-two-forms-in-one-page/39739863#39739863

Forms
https://wtforms.readthedocs.io/en/stable/forms.html

Multiple forms in a single page using flask and WTForms
https://stackoverflow.com/questions/18290142/multiple-forms-in-a-single-page-using-flask-and-wtforms

Mehr erfahren

AJAX WTForms

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.