Flask: Модальное окно bootstrap с валидацией формы

Писал я приложение на Flask, захотелось мне сделать модальное bootstrap окно с формой редактирования профиля пользователя, погуглил и нашел отличную статью, так сказать отправную точку в изучении вопроса. Но главная проблема была в том, что в статье описан пример модальной формы находящейся в том же роуте, где модальное окно и вызывается, а я хотел сделать доступной данную форму с любой страницы (образно говоря, разместить в шапке сайта кнопку «редактировать профиль» по которой откроется модальное окно для редактирования профиля (а на самом деле роут для этой формы /user/<id>/edit).

Сразу оговорюсь, данное решение применимо к приложению на Flask, wtforms, bootstrap.

Итак, модель и форма уже должны быть созданы. Показываю пример роута для этой формы.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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)
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)
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).

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<!-- 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 -->
<!-- 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 -->
<!-- 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 в главное меню.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<a class="edit-modal-opener" data-toggle="modal"
data-whatever="{{ url_for('user_edit', id=user.id) }}" href="#">Изменить</a>
<a class="edit-modal-opener" data-toggle="modal" data-whatever="{{ url_for('user_edit', id=user.id) }}" href="#">Изменить</a>
<a class="edit-modal-opener" data-toggle="modal"
                            data-whatever="{{ url_for('user_edit', id=user.id) }}" href="#">Изменить</a>

А теперь шаблон содержимого модального окна (modal-content) — _form_edit.html

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{% 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">&times;</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>
{% 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">&times;</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>
{% 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">&times;</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 или отдельный файл, если вы реализовали это.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$(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')
}
})
});
})
});
});
$(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') } }) }); }) }); });
$(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 вернет ошибки валидации — форма это отобразит без перезагрузки страницы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *