Писал я приложение на Flask, захотелось мне сделать модальное bootstrap окно с формой редактирования профиля пользователя, погуглил и нашел отличную статью, так сказать отправную точку в изучении вопроса. Но главная проблема была в том, что в статье описан пример модальной формы находящейся в том же роуте, где модальное окно и вызывается, а я хотел сделать доступной данную форму с любой страницы (образно говоря, разместить в шапке сайта кнопку «редактировать профиль» по которой откроется модальное окно для редактирования профиля (а на самом деле роут для этой формы /user/<id>/edit).
Сразу оговорюсь, данное решение применимо к приложению на Flask, wtforms, bootstrap.
Итак, модель и форма уже должны быть созданы. Показываю пример роута для этой формы.
import json
from flask import render_template, request, jsonify
@app.route('/user/<id>/edit', methods=['GET', 'POST'])
def user_edit(id):
user = User.query.filter_by(id=id).first_or_404()
form = ProfileEditForm(user.email)
if form.validate_on_submit():
user.email = form.email.data
db.session.commit()
return jsonify(status='ok')
elif request.method == 'GET':
form.email.data = user.email
else:
data = json.dumps(form.errors, ensure_ascii=False)
return jsonify(data)
return render_template('_form_edit.html', title="Редактирование пользователя", form=form)
Смысл таков.
- Если GET — отдаем форму с данными (если они есть) используя шаблон формы _form_edit.html
- Если POST (форма прошла валидацию) — сохраняем форму и отвечаем json’ом «ok»
- Если форма не прошла валидацию — собираем ошибки валидации в json и отправляем в ответ.
Теперь переходим к «front-end». Для начала добавим html пустого модального окна (я это сделал сразу в base.html, это у меня header).
<!-- Dynamic Modal -->
<div class="modal fade" id="Modal" tabindex="-1" role="dialog" aria-labelledby="FormModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<!-- load content here -->
</div>
</div>
</div>
<!-- End Dynamic Modal -->
и сразу же кнопку, которая будет его вызывать. Добавляйте на любую страницу или сразу в base.html в главное меню.
<a class="edit-modal-opener" data-toggle="modal"
data-whatever="{{ url_for('user_edit', id=user.id) }}" href="#">Изменить</a>
А теперь шаблон содержимого модального окна (modal-content) — _form_edit.html
{% from 'bootstrap/wtf.html' import form_field %}
<form id="ModalForm" name="stepForm" class="form" method="post">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">{{ title }}</h4>
</div>
<div class="modal-body">
{{ form.hidden_tag() }}
{{ form_field(form.email) }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Отмена</button>
<button id="submit" type="submit" class="btn btn-success">Сохранить</button>
</div>
</form>
И самый сок — javascript, который собственно подгружает форму в модальное окно, ожидает ответ «ok» от бэка или json с ошибками, для того, что-бы присвоить эти ошибки валидации к полям формы. Это нужно поместить в блок <script> в base.html или отдельный файл, если вы реализовали это.
$(document).ready(function () {
$('.edit-modal-opener').click(function () {
var url = $(this).data('whatever');
$.get(url, function (data) {
$('#Modal .modal-content').html(data);
$('#Modal').modal();
$('#submit').click(function (event) {
event.preventDefault();
$.post(url, data = $('#ModalForm').serialize(), function (
data) {
if (data.status == 'ok') {
$('#Modal').modal('hide');
location.reload();
} else {
var obj = JSON.parse(data);
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var value = obj[key];
}
}
$('.help-block').remove()
$('<p class="help-block">' + value + '</p>')
.insertAfter('#' + key);
$('.form-group').addClass('has-error')
}
})
});
})
});
});
Собственно на этом все. Все запросы формы (GET и POST) будут передаваться в фоне, модальное окно закроется только после успешной валидации и сохранения формы, если flask-wtf вернет ошибки валидации — форма это отобразит без перезагрузки страницы.