Flask с несколькими формами на странице, отправленной с использованием AJAX и возвращающей результат отрисовки формы
Возврат клиенту отрисованной формы означает, что клиенту не нужно много знать о форме и уменьшает кодировку на стороне клиента.
При работе над системой комментариев я впервые столкнулся с проблемой наличия нескольких форм 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">×</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">×</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
Недавний
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
- Don't Repeat Yourself (DRY) с Jinja2
- SQLAlchemy, PostgreSQL, максимальное количество строк для user
- Показать значения в динамических фильтрах SQLAlchemy
- Безопасная передача данных с помощью шифрования Public Key и pyNaCl
- rqlite: альтернатива dist с высокой степенью готовности и SQLite
Большинство просмотренных
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- Подключение к службе на хосте Docker из контейнера Docker
- Использование PyInstaller и Cython для создания исполняемого файла Python
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов
- Flask Удовлетворительный запрос API проверка параметров запроса с помощью схем Маршмэллоу