Применение WTForms для работы с формами сайта

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

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

  • проверить принимаемые данные на стороне сервера;
  • сформировать ошибки для формы (если что-то пошло не так);
  • продумать безопасное сохранение и представление данных на сервере

и так далее. Это необходимая и не всегда тривиальная работа. Но, к счастью, есть расширения для Flask, которые значительно облегчают реализацию этих и подобных им типовых задач. В частности, библиотека

WTForms

позволяет достаточно просто оперировать формами и выполняет большую часть задач за разработчика. На этом занятии мы, как раз, и познакомимся с основами ее функционала на примере нашего тестового сайта.

Вообще, WTForms – это библиотека, написанная на Python и независимая от фреймворков. Она способна генерировать формы, проверять их, наполнять начальной информацией, работать с reCaptcha и многое другое. Кроме того, в нее встроена защита от CSRF:

CSRF (Cross-Site Request Forgery) – межсайтовая подделка запросов.

Это атака, при которой происходит имитация запроса пользователя к стороннему сайту со страницы другого сайта. То есть, злоумышленник создает некую страницу на своем сайте, жертва заходит на нее и из формы запроса отправляется запрос на сайт, в котором посетитель авторизован. Если на сайте нет защиты от CSRF-атаки, злоумышленник, от имени пользователя получает доступ к стороннему ресурсу и творит свои «черные дела».

Так вот, такие атаки будут нипочем, при использовании WTForms. И первым делом нужно установить это расширение. Для Flask оно называется

Flask-WTF

и устанавливается с помощью команды:

pip install flask_wtf

Концепция создания форм здесь состоит в расширении базового класса

FlaskForm

А все поля формы описываются переменными этого класса и ссылаются на соответствующие объекты, которые могут быть образованы из следующих встроенных классов:

  • StringField – для работы с полем ввода;
  • PasswordField – для работы с полем ввода пароля;
  • BooleanField – для checkbox полей;
  • TextAreaField – для работы с вводом текста;
  • SelectField – для работы со списком;
  • SubmitField – для кнопки submit.

Это лишь часть классов. Полную документацию можно посмотреть на официальном сайте.

Создание класса формы

Давайте для примера создадим в нашем проекте вспомогательный файл forms.py, в котором будем определять все классы форм и начнем с класса LoginForm:

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, BooleanField, PasswordField
from wtforms.validators import DataRequired, Email, Length
 
class LoginForm(FlaskForm):
    email = StringField("Email: ", validators=[Email()])
    psw = PasswordField("Пароль: ", validators=[DataRequired(), Length(min=4, max=100)])
    remember = BooleanField("Запомнить", default = False)
    submit = SubmitField("Войти")

Смотрите, мы прописали переменные: email, psw, remember и submit, которые ссылаются на соответствующие объекты. У каждого объекта вначале указана строка, которую, затем, можно будет отобразить рядом с полем ввода и параметр validators. Этот параметр содержит список валидаторов, с помощью которых выполняется проверка корректности введенных данных. Например:

  • DataRequired – валидатор, требующий ввода каких-либо данных;
  • Email – проверяет корректность введенного email-адреса;
  • Length – проверяет количество введенных символов.

Конечно, это не все валидаторы, которые есть в WTForms. Полный их список и набор параметров можно посмотреть на странице официальной документации.

И, обратите внимание, валидатор Email может требовать отдельной дополнительной установки:

pip install email-validator

Поэтому, если при запуске программы будет отображаться ошибка с указанием класса Email, то просто выполните его установку.

Создание шаблона формы

Итак, класс определен. Как им теперь пользоваться? Для начала в основном модуле программы выполним импорт:

from forms import LoginForm

И, далее, в функции представления login создадим его экземпляр и передадим шаблону login.html:

@app.route("/login", methods=["POST", "GET"])
def login():
    form = LoginForm()
    return render_template("login.html", menu=dbase.getMenu(), title="Авторизация", form=form)

То есть, в шаблоне будет доступ к переменным этого класса через параметр form:

{% extends 'base.html' %}
 
{% block content %}
{{ super() }}
{% for cat, msg in get_flashed_messages(True) %}
<div class="flash {{cat}}">{{msg}}</div>
{% endfor %}
<form action="" method="post" class="form-contact">
{{ form.hidden_tag() }}
{{ form.email.label() }} {{ form.email() }}
{{ form.psw.label() }} {{ form.psw() }}
{{ form.remember.label() }} {{ form.remember() }}
{{ form.submit() }}
<hr align=left width="300px">
<a href="{{url_for('register')}}">Регистрация</a>
</form>
{% endblock %}

Смотрите, здесь в самом начале идет вызов метода:

form.hidden_tag()

который создает скрытое поле, содержащее токен, используемый для защиты формы от CSRF-атак. Это все, что от нас требуется, остальное Flask-WTF сделает автоматически. Как видите, все просто и удобно.

Далее, мы вызываем методы label, которые вставляют в форму тег:

<label>Название</label>

А методы email, psw, remember, submit – создают соответствующие теги полей ввода, кнопок, чекбоксов и так далее.

Давайте запустим программу и посмотрим как будет выглядеть эта форма и что представлять на уровне HTML-документа.

Отлично, это сделано, теперь в обработчике login выполним обработку элементов этой формы и свяжем ее с ранее созданным функционалом:

@app.route("/login", methods=["POST", "GET"])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('profile'))
 
    form = LoginForm()
    if form.validate_on_submit():
        user = dbase.getUserByEmail(form.email.data)
        if user and check_password_hash(user['psw'], form.psw.data):
            userlogin = UserLogin().create(user)
            rm = form.remember.data
            login_user(userlogin, remember=rm)
            return redirect(request.args.get("next") or url_for("profile"))
 
        flash("Неверная пара логин/пароль", "error")
 
    return render_template("login.html", menu=dbase.getMenu(), title="Авторизация", form=form)

Смотрите, здесь вначале идет проверка validate_on_submit() корректности переданных данных по POST-запросу и правильность заполненных полей формы. И это очень удобно, т.к. программа пойдет дальше только в случае верных данных. В этом случае мы дополнительно проверяем корректность ввода пароля и, затем, авторизовываем пользователя и перенаправляем его на соответствующий URL. Иначе, снова отображается форма авторизации.

Видео по теме