Обработка ошибок во Flask-WTF

Файл проекта: https://github.com/selfedu-rus/flasksite-18

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

form.<переменная поля>.errors

Добавим их вывод:

{{ form.email.label() }} 
{% if form.email.errors %}
         {{ form.email(class="invalid") }}
<span class="invalid-feedback">
         {% for e in form.email.errors %}
         {{ e }}
         {% endfor %}
</span>
{% else %}
         {{ form.email() }}
{% endif %}
 
{{ form.psw.label() }}
{% if form.psw.errors %}
         {{ form.psw(class="invalid") }}
<span class="invalid-feedback">
         {% for e in form.psw.errors %}
         {{ e }}
         {% endfor %}
</span>
{% else %}
         {{ form.psw() }}
{% endif %}

Мы здесь после отображения имени поля проверяем: существуют ли ошибки и если да, то указываем класс стилей для отображения ошибочных полей а, затем, выводим список ошибок (их может быть несколько). Если же ошибок нет, то поле отображается в первоначальном виде.

Чтобы все выглядело относительно прилично, добавим стили для отображения ошибочных состояний:

.form-contact .invalid {
         display: inline-block;
         background: #FF9898;
}
.form-contact .invalid-feedback {
         color: #CC0000;
}

Перейдем в браузер, наберем неверный email и увидим сообщение об ошибке:

Invalid email address.

По умолчанию в WTForms ошибки показываются на английском языке. Чтобы прописать свои собственные, в классе LoginForm нужно их явно прописать:

class LoginForm(FlaskForm):
    email = StringField("Email: ", validators=[Email("Некорректный email")])
    psw = PasswordField("Пароль: ", validators=[DataRequired(),
                                                Length(min=4, max=100, message="Пароль должен быть от 4 до 100 символов")])
    remember = BooleanField("Запомнить", default = False)
    submit = SubmitField("Войти")

Теперь, при неверном вводе информации мы будем видеть указанные нами сообщения.

Формирование полей в шаблоне через цикл

Если вернуться к нашей форме авторизации, то увидим повторяющиеся части текста, что не очень хорошо. Чтобы этого не было, мы можем создавать однотипные поля формы через цикл, используя параметр form как итерируемый объект:

{% for field in form if field.name not in ['csrf_token', 'remember', 'submit'] -%}
         {{ field.label() }} 
         {% if field.errors %}
                   {{ field(class="invalid") }}
         <span class="invalid-feedback">
                   {% for e in field.errors %}
                   {{ e }}
                   {% endfor %}
         </span>
         {% else %}
                   {{ field() }}
         {% endif %}
{% endfor %}

Смотрите, мы здесь выбираем все поля (field) из формы, кроме полей с именами 'csrf_token', 'remember' и 'submit'. Первое поле – это специальный токен, служащий для защиты от CSRF-атак, а последние два – это флажок «Запомнить» и кнопка «Войти». Мы их отображаем на форме без указания списка ошибок.

Если теперь перейти в браузер, то при обновлении страницы увидим ту же самую форму авторизации со всеми полями. Причем, порядок полей тот же, что и порядок переменных в классе LoginForm. Мало того, если будет добавлена еще какая-либо типовая переменная поля, то она автоматически добавится в форму и будет соответствующим образом обрабатываться, что очень удобно.

Форма регистрации

Давайте заменим в нашем сайте еще одну форму для регистрации пользователей. Ее класс можно прописать следующим образом:

class RegisterForm(FlaskForm):
    name = StringField("Имя: ", validators=[Length(min=4, max=100, message="Имя должно быть от 4 до 100 символов")])
    email = StringField("Email: ", validators=[Email("Некорректный email")])
    psw = PasswordField("Пароль: ", validators=[DataRequired(),
                                                Length(min=4, max=100, message="Пароль должен быть от 4 до 100 символов")])
 
    psw2 = PasswordField("Повтор пароля: ", validators=[DataRequired(), EqualTo('psw', message="Пароли не совпадают")])
    submit = SubmitField("Регистрация")

Мы здесь используем еще один валидатор EqualTo для проверки совпадения паролей. Все остальное очень похоже на форму авторизации.

Далее, шаблон register.html запишем в виде:

{% extends 'base.html' %}
 
{% block content %}
{{ super() }}
{% for cat, msg in get_flashed_messages(True) %}
<div class="flash {{cat}}">{{msg}}</div>
{% endfor %}
<form action="{{ url_for('register') }}" method="post" class="form-contact">
{{ form.hidden_tag() }}
 
{% for field in form if field.name not in ['csrf_token', 'submit'] -%}
         {{ field.label() }} 
         {% if field.errors %}
                   {{ field(class="invalid") }}
         <span class="invalid-feedback">
                   {% for e in field.errors %}
                   {{ e }}
                   {% endfor %}
         </span>
         {% else %}
                   {{ field() }}
         {% endif %}
{% endfor %}
 
{{ form.submit() }}
</form>
{% endblock %}

И обработчик register:

@app.route("/register", methods=["POST", "GET"])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
            hash = generate_password_hash(request.form['psw'])
            res = dbase.addUser(form.name.data, form.email.data, hash)
            if res:
                flash("Вы успешно зарегистрированы", "success")
                return redirect(url_for('login'))
            else:
                flash("Ошибка при добавлении в БД", "error")
 
    return render_template("register.html", menu=dbase.getMenu(), title="Регистрация", form=form)

Как видите, все достаточно просто и при этом получаем доступ к богатому функционалу модуля WTForms.

Конечно, мы здесь рассмотрели лишь общий принцип использования расширения WTForms. Этих знаний вполне достаточно для простых реализаций. А для более глубокого погружения в эту тему советую почитать официальную документацию по данному модулю.

Видео по теме