Писал я приложение на 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 вернет ошибки валидации — форма это отобразит без перезагрузки страницы.