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

Flask с несколькими формами на странице, отправленной с использованием AJAX и возвращающей результат отрисовки формы

Возврат клиенту отрисованной формы означает, что клиенту не нужно много знать о форме и уменьшает кодировку на стороне клиента.

22 февраля 2020
post main image
https://unsplash.com/@emilydafinchy

При работе над системой комментариев я впервые столкнулся с проблемой наличия нескольких форм WTForms на одной странице. На каждой странице также есть форма поиска, но это не форма POST . Он делает GET. Система комментариев использует две формы:

  • Comment form
  • Comment reply form

Форма комментария находится непосредственно под элементом содержимого, постом в блоге или страницей, форма ответа на комментарий изначально скрыта и вставлена и отображается, когда посетитель нажимает кнопку "Ответить". Формы практически идентичны, основное отличие состоит в том, что форма ответа на комментарий имеет один дополнительный параметр - parent_id. При отображении формы ответа на комментарий в форму копируется parent_id. Другие различия более текстовые. Обе формы могут быть сжаты в одну, но это вызовет другие трудности, такие как добавление дополнительного кода для работы с переключением форм и копированием данных формы. Мы могли бы размещать формы без AJAX , но в конце концов мы хотим использовать AJAX здесь.

Использование формы в шаблоне с HTML

Традиционный способ сделать это - отправить форму и получить ответ (JSON), который состоит только из статуса. Необходимо декодировать статус и в случае ошибок установить соответствующие сообщения об ошибках где-нибудь на странице.

Решение, которое я хочу использовать, отличается и нацелено на минимизацию кода на стороне клиента, я уже использовал что-то подобное для контактной формы. Идея заключается в том, что страница ничего не знает о форме и ее реакции. Для формы я использую шаблон Jinja , который также содержит сообщения об ошибках и другие сообщения. Я использую (Bootstrap) макрос для отрисовки всех форм на сайте, и это включает в себя отрисовку сообщений об ошибках. Еще одним плюсом такого подхода является то, что все формы и сообщения об ошибках выглядят одинаково, нет дублирования в клиенте.

Название не содержится в этом шаблоне, так как я считаю эту часть страницы комментариев. На сервере мы используем стандартную обработку WTForms и выводим шаблон формы. Затем отрисовываемый шаблон отправляется обратно клиенту. Думаю, здесь ты видишь преимущество. Логика со стороны клиента отличается, потому что вместо того, чтобы вытаскивать предупреждающие сообщения и, в случае ошибки, сообщения об ошибке, в соответствующих позициях (ids), мы теперь заменяем часть HTML на странице.

Две формы WTForms на странице

Моей первой попыткой было просто разместить обе формы на странице. В предыдущей записи в блоге я говорил, что размещаю формы на странице с помощью макроса:

	{{ 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')) }}

Я установил атрибут id форм, чтобы получить доступ к формам в jQuery. Затем начали поступать красные консольные сообщения, дублировался идентификатор поля токена CSRF , дублировался идентификатор поля отправки. Это запрещено в HTML5! В HTML5 идентификатор должен быть уникальным в документе.

Поиск в интернете и чтение документации WTForms показалось, что класс формы поставляется с параметром 'prefix'. Описание:

prefix - Если предусмотрено, то все поля будут иметь имя prefix со значением.

Использовать это очень просто. У меня есть следующие формы:

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'))

В функции просмотра мы просто добавляем prefixes:

    ...
    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(
        ...
        )

Чтобы разместить формы на странице, мы делаем то же самое, что и выше, никаких изменений здесь нет. Доступ к элементам формы из jQuery также легко получить, зная форму prefixes. Например, чтобы установить скрытое поле parent_id в форме ответа на комментарий, можно сделать что-то вроде:

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

Все элементы имеют уникальные идентификаторы, их легко кодировать и читать. Пока все хорошо.

Размещение форм с помощью AJAX

Код jQuery для размещения формы на самом деле не сложен. Многие примеры можно найти в интернете. Как объяснялось выше, при отправке сервер обрабатывает форму. В случае ошибки сервер просто возвращает отрисованную форму снова, включая предупреждающие сообщения и сообщения об ошибках. Логика клиента заменяет часть HTML на странице возвращенным HTML . Если ошибок нет, сервер возвращает идентификатор anchor комментария, который был добавлен. Логика клиента затем делает перенаправление на ту же самую страницу, используя url страницы с этим anchor. Это может быть оптимизировано позже.

Функция jQuery serialize() не включает кнопку отправки

Я использую функцию jQuery serialize() для получения данных формы. К сожалению jQuery serialize() не включает кнопку отправки, так как на форме может быть несколько кнопок отправки. Поскольку я хочу использовать механизм, описанный выше (определить, какая форма была отправлена, проверив кнопку отправки), я добавляю кнопку отправки к сериализованным данным формы другим, не очень сложным способом. После вызова функции jQuery replace() мы должны повторно прикрепить события.

В функции $(document).ready к форме прикреплены два события:

  • Мероприятие по размещению формы
  • Событие, показывающее количество оставшихся символов

После замены HTML эти привязки теряются и должны быть снова присоединены. Опять же, не так уж и сложно. Может быть, мы также можем использовать делегацию мероприятий, но я не исследовал это.

Шаблоны форм

Ниже приведен шаблон формы комментария. Шаблон ответа на комментарий практически идентичен. Сервер может добавлять предупреждающие сообщения и сообщения об ошибках, а клиент ничего о них не знает. В случае ошибки элемент с id comment_form-post-container заменяется отрисовываемой формой HTML , возвращаемой вызовом AJAX .

<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>

Форма комментария вставляется на странице путем ее включения:

    <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>

Код клиента jQuery :

$(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');
    });
    ...
});

И код для отправки формы:

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;

        }
    });
}

Переход к вставленному комментарию с помощью anchor

После того, как комментарий был вставлен в базу данных сервером, мы должны показать новое дерево комментариев. Мы можем сделать это, оставаясь на используемом клиентском коде, но на данный момент я выбираю возврат anchor к вызову AJAX . Затем страница вызывается, обновляется, используя url страницы и anchor. Я добавляю метку времени, чтобы предотвратить проблемы с кэшированием браузера. В HTML5 идентификатор используется как anchor. Так как я использую Bootstrap с липким наваром, мы также должны компенсировать дополнительные пикселы навара:

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

Резюме

Выше показана возможная реализация Flask, WTforms и AJAX. Я попытался сократить код клиентской стороны, вернув визуализированную форму, включающую в себя предупреждающие сообщения и сообщения об ошибках, а не просто сообщения об ошибках. Защита CSRF также работает, поскольку мы используем FlaskForm. Из документации по расширению Flask_WTF: Любое представление, использующее FlaskForm для обработки запроса, уже получает защиту CSRF . Есть еще много работы, но я подумал поделиться этим с тобой.

Ссылки / кредиты

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

Подробнее

AJAX WTForms

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.