#11 Работа с формами во Flask
Формы — важный элемент любого веб-приложения, но, к сожалению, работать с ними достаточно сложно. Сначала нужно подтвердить данные на стороне клиента, затем — на сервере. И даже этого недостаточно, если разработчик приложения озабочен такими проблемами безопасности как CSRF, XSS, SQL Injection и так далее. Все вместе — это масса работы. К счастью, есть отличная библиотека WTForms, выполняет большую часть задач за разработчика. Перед тем как узнать больше о WTForms, следует все-таки разобраться, как работать с формами без библиотек и пакетов.
Работа с формами — сложный вариант
Для начала создадим шаблон login.html со следующим кодом:
html lang="en"> head> meta charset="UTF-8"> title>Logintitle> head> body> if message %> p>message >>p> endif %> form action="" method="post"> p> label for="username">Usernamelabel> input type="text" name="username"> p> p> label for="password">Passwordlabel> input type="password" name="password"> p> p> input type="submit"> p> form> body> html>
Этот код нужно добавить после функции представления books() в файле main2.py :
from flask import Flask, render_template, request #. @app.route('/login/', methods=['post', 'get']) def login(): message = '' if request.method == 'POST': username = request.form.get('username') # запрос к данным формы password = request.form.get('password') if username == 'root' and password == 'pass': message = "Correct username and password" else: message = "Wrong username or password" return render_template('login.html', message=message) #.
Стоит обратить внимание, что аргумент methods передан декоратору route() . По умолчанию обработчик запросов вызывается только в тех случаях, когда метод request.method — GET или HEAD. Это можно изменить, передав список разрешенных HTTP-методов аргументу-ключевому слову methods . С этого момента функция представления login будет вызываться только тогда, когда запрос к /login/ будет сделан с помощью методов GET, POST или HEAD. Если попробовать получить доступ к URL /login/ другим методом, появится ошибка HTTP 405 Method Not Allowed.
В прошлых уроках обсуждалось то, что объект request предоставляет информацию о текущем веб-запросе. Информация, полученная с помощью формы, хранится в атрибуте form объекта request . request.form — это неизменяемый объект типа словарь, известный как ImmutableMultiDict .
Дальше нужно запустить сервер и зайти на https://localhost:5000/login/ . Откроется такая форма.

Запрос к странице был сделан с помощью метода GET, поэтому код внутри блока if функции login() пропущен.
Если попробовать отправить форму без ввода данных, страница будет выглядеть следующим образом:

В этот раз страница была отправлена методом POST, поэтому код внутри if оказался исполнен. Внутри этого блока приложение принимает имя пользователя и пароль и устанавливает сообщение для message . Поскольку форма оказалась пустой, отобразилось сообщение об ошибке.
Если заполнить форму с корректными именем пользователям и паролем и нажать Enter, появится приветственное сообщение “Correct username and password” :

Таким образом можно работать с формами во Flask. Теперь же стоит обратить внимание на пакет WTForms.
WTForms
WTForms – это мощная библиотека, написанная на Python и независимая от фреймворков. Она умеет генерировать формы, проверять их и предварительно заполнять информацией (удобно для редактирования) и многое другое. Также она предлагает защиту от CSRF. Для установки WTForms используется Flask-WTF.
Flask- WTF – это расширение для Flask, которое интегрирует WTForms во Flask. Оно также предлагает дополнительные функции, такие как загрузка файлов, reCAPTCHA, интернационализация (i18n) и другие. Для установки Flask-WTF нужно ввести следующую команду.
(env) gvido@vm:~/flask_app$ pip install flask-wtf
Создание класса Form
Начать стоит с определения форм в виде классов Python. Каждая форма должна расширять класс FlaskForm из пакета flask_wtf . FlaskForm — это обертка, содержащая полезные методы для оригинального класса wtform.Form , который является основной для создания форм. Внутри класса формы, поля формы определяются в виде переменных класса. Поля формы определяются путем создания объекта, ассоциируемого с типом поля. Пакет wtform предлагает несколько классов, представляющих собой следующие поля: StringField , PasswordField , SelectField , TextAreaField , SubmitField и другие.
Для начала нужно создать файл forms.py внутри словаря flask_app и добавить в него следующий код.
from flask_wtf import FlaskForm from wtforms import StringField, SubmitField, TextAreaField from wtforms.validators import DataRequired, Email class ContactForm(FlaskForm): name = StringField("Name: ", validators=[DataRequired()]) email = StringField("Email: ", validators=[Email()]) message = TextAreaField("Message", validators=[DataRequired()]) submit = SubmitField("Submit")
Здесь определен класс формы ContactForm с четырьмя полями: name , email , message и sumbit . Эти переменные будут использоваться, чтобы отрендерить поля формы, а также назначать и получать информацию из них. Эта форма создана с помощью двух StringField , TextAreaField и SumbitField . Каждый раз когда создается объект поля, определенные аргументы передаются его функции-конструктору. Первый аргумент — строка, содержащая метку, которая будет отображаться внутри тега в тот момент, когда поле отрендерится. Второй опциональный аргумент — список валидаторов (элементов системы проверки), которые передаются конструктору в виде аргументов-ключевых слов. Валидаторы — это функции или классы, которые определяют, корректна ли введенная в поле информация. Для каждого поля можно использовать несколько валидаторов, разделив их запятыми ( , ). Модуль wtforms.validators предлагает базовые валидаторы, но их можно создавать самостоятельно. В этой форме используются два встроенных валидатора: DataRequired и Email .
DataRequired: он проверяет, ввел ли пользователь хоть какую-информацию в поле.
Email: проверяет, является ли введенный электронный адрес действующим.
Введенные данные не будут приняты до тех пор, пока валидатор не подтвердит соответствие данных.
Примечание: это лишь основа полей форм и валидаторов. Полный список доступен по ссылке https://wtforms.readthedocs.io.
Установка SECRET_KEY
По умолчанию Flask-WTF предотвращает любые варианты CSFR-атак. Это делается с помощью встраивания специального токена в скрытый элемент внутри формы. Затем этот токен используется для проверки подлинности запроса. До того как Flask-WTF сможет сгенерировать csrf-токен, необходимо добавить секретный ключ. Установить его в файле main2.py необходимо следующим образом:
#. app.debug = True app.config['SECRET_KEY'] = 'a really really really really long secret key' manager = Manager(app) #.
Здесь используется атрибут config объекта Flask . Атрибут config работает как словарь и используется для размещения параметров настройки Flask и расширений Flask, но их можно добавлять и самостоятельно.
Секретный ключ должен быть строкой — такой, которую сложно разгадать и, желательно, длинной. SECRET_KEY используется не только для создания CSFR-токенов. Он применяется и в других расширениях Flask. Секретный ключ должен быть безопасно сохранен. Вместо того чтобы хранить его в приложении, лучше разместить в переменной окружения. О том как это сделать — будет рассказано в следующих разделах.
Формы в консоли
Откроем оболочку Python с помощью следующей команды:
(env) gvido@vm:~/flask_app$ python main2.py shell
Это запустит оболочку Python внутри контекста приложения.
Теперь нужно импортировать класс ContactForm и создать экземпляр объекта новой формы, передав данные формы.
>>> >>> from forms import ContactForm >>> from werkzeug.datastructures import MultiDict >>> >>> >>> form1 = ContactForm(MultiDict([('name', 'jerry'),('email', 'jerry@mail.com')])) >>>
Стоит обратить внимание, что данные передаются в виде объекта MultiDict , потому что функция-конструктор класса wtforms.Form принимает аргумент типа MutiDict . Если данные формы не определены при создании экземпляра объекта формы, а форма отправлена с помощью запроса POST, wtforms.Form использует данные из атрибута request.form . Стоит вспомнить, что request.form возвращает объект типа ImmutableMultiDict . Это то же самое, что и MultiDict , но он неизменяемый.
Метод validate() проверяет форму. Если проверка прошла успешно, он возвращает True , если нет — False .
>>> >>> form1.validate() False >>>
Форма не прошла проверку, потому что обязательному полю message при создании объекта формы не было передано никаких данных. Получить доступ к ошибкам форм можно с помощью атрибута errors объекта формы:
>>> >>> form1.errors 'message': ['This field is required.'], 'csrf_token': ['The CSRF token is missing.']> >>>
Нужно обратить внимание, что в дополнение к сообщению об ошибке для поля message , вывод также содержит сообщение об ошибке о недостающем csfr-токене. Это из-за того что в данных формы нет запроса POST с csfr-токеном.
Отключить CSFR-защиту можно, передав csfr_enabled=False при создании экземпляра класса формы. Пример:
>>> form3 = ContactForm(MultiDict([('name', 'spike'),('email', 'spike@mail.com')]), csrf_enabled=False) >>> >>> form3.validate() False >>> >>> form3.errors 'message': ['This field is required.']> >>> >>>
Как и предполагалось, теперь ошибка появляется только для поля message . Теперь можно создать другой объект формы, но в этот раз передать ему информацию для всех полей.
>>> >>> form4 = ContactForm(MultiDict([('name', 'jerry'), ('email', 'jerry@mail.com'), ('message', "hello tom")]), csrf_enabled=False) >>> >>> form4.validate() True >>> >>> form4.errors > >>>
Проверка формы в этот раз прошла успешно.
Следующий шаг — рендеринг формы.
Рендеринг формы
Существует два варианта рендеринга:
- Один за одним.
- С помощью цикла
Рендеринг полей один за одним
Поскольку в шаблонах есть доступ к экземпляру формы, можно использовать имена полей, чтобы отрендерить имена, метки и ошибки:
form.field_name.label() >> form.field_name() >> for error in form.field_name.errors %> error >> endfor %>
Стоит протестировать этот способ в консоли:
>>> >>> from forms import ContactForm >>> from jinja2 import Template >>> >>> form = ContactForm() >>>
Здесь экземпляр объекта формы был создан без данных запроса. Так и происходит, когда форма отображается первый раз с помощью запроса GET.
>>> >>> >>> Template(">").render(form=form) ' ' >>> >>> Template(">").render(form=form) '' >>> >>> >>> Template(">").render(form=form) ' ' >>> >>> Template(">").render(form=form) '' >>> >>> >>> Template(">").render(form=form) '' >>> >>> Template(">").render(form=form) ' ' >>> >>> >>> Template(">").render(form=form) '' >>> >>>
Поскольку форма выводится первый раз, у полей не будет ошибок проверки. Следующий код наглядно демонстрирует это:
>>> >>> >>> Template(">").render(form=form) '' >>> >>> >>> Template(">").render(form=form) '' >>> >>> >>> Template(">").render(form=form) '' >>> >>>
Вместо отображения ошибок проверки для каждого поля можно использовать form.errors , чтобы получить доступ к ошибкам валидации, относящимся к форме. forms.errors используется чтобы отображать ошибки проверки в верхней части формы.
>>> >>> Template(">").render(form=form) '' >>>
При рендеринге полей и меток можно добавить дополнительные аргументы-ключевые слова, которые окажутся в HTML-коде в виде пар ключей-значений. Например:
>>> >>> Template('>').render(form=form) '' >>> >>> >>> Template('>').render(form=form) ' ' >>> >>>
Предположим, форма была отправлена. Теперь можно попробовать отрендерить поля и посмотреть, что получится.
>>> >>> from werkzeug.datastructures import MultiDict >>> >>> form = ContactForm(MultiDict([('name', 'spike'),('email', 'spike@mail.com')])) >>> >>> form.validate() False >>> >>> >>> Template(">").render(form=form) '' >>> >>> >>> Template(">").render(form=form) '' >>> >>> >>> Template(">").render(form=form) ' ' >>> >>>
Стоит обратить внимание, что у атрибута value в полях name и email есть данные. Но элемент для поля message пуст, потому что ему данные переданы не были. Получить доступ к ошибке валидации для поля message можно следующим образом:
>>> >>> Template(">").render(form=form) 'This field is required.' >>>
Как вариант, form.errors можно использовать, чтобы перебрать все ошибки валидации за раз.
Стоит обратить внимание, что ошибки csfr-токена нет, потому что запрос был отправлен без токена. Отрендерить поле csfr можно как и любое другое поле:
>>> >>> Template(">").render(form=form) '' >>>
Рендеринг полей один из одним может занять много времени, особенно если их несколько. Для таких случаев используется цикл.
Рендеринг полей с помощью цикла
Следующий код демонстрирует, как можно отрендерить поля с помощью цикла for.
>>> >>> s = """\ . . > . . . . > . > . . > . . . . """ >>> >>> >>> print(Template(s).render(form=form)) div> input id="csrf_token" name="csrf_token" type="hidden" value="IjZjOTBkOWM4ZmQ0MGMzZTY3NDc3ZTNiZDIxZTFjNDAzMGU1YzEwOTYi.DQlFlA.GQ-PrxsCJkQfoJ5k6i5YfZMzC7k"> /div> div> label for="name">Name: /label> input id="name" name="name" type="text" value="spike"> /div> div> label for="email">Email: /label> input id="email" name="email" type="text" value="spike@mail.com"> /div> div> label for="message">Message/label> textarea id="message" name="message">/textarea> div class="error">This field is required./div> /div> div> label for="submit">Submit/label> input id="submit" name="submit" type="submit" value="Submit"> /div> >>> >>>
Важно заметить, что вне зависимости от используемого метода нужно вручную добавлять тег , чтобы обернуть поля формы.
Теперь, зная как создавать, поверять и рендерить формы, можно использовать полученные знания для создания реальных форм.
Вначале нужно создать шаблон contact.html со следующим кодом:
html lang="en"> head> meta charset="UTF-8"> title>Titletitle> head> body> form action="" method="post"> form.csrf_token() >> for field in form if field.name != "csrf_token" %> p>field.label() >>p> p>field >> for error in field.errors %> error >> endfor %> p> endfor %> form> body> html>
Единственный недостающий кусочек пазла — функция представления, которая будет создана далее.
Работа с подтверждением формы
Откроем main2.py , чтобы добавить следующий код после функции представления login() .
from flask import Flask, render_template, request, redirect, url_for from flask_script import Manager, Command, Shell from forms import ContactForm #. @app.route('/contact/', methods=['get', 'post']) def contact(): form = ContactForm() if form.validate_on_submit(): name = form.name.data email = form.email.data message = form.message.data print(name) print(email) print(message) # здесь логика базы данных print("\nData received. Now redirecting . ") return redirect(url_for('contact')) return render_template('contact.html', form=form) #.
В 7 строке создается объект формы. На 8 строке проверяется значение, которое вернул метод validate_on_submit() для исполнения кода внутри инструкции if.
Почему используется validate_on_sumbit() , а не validate() , как это было в консоли?
validate() всего лишь проверяет, корректны ли данные формы. Он не проверяет, был ли запрос отправлен с помощью метода POST. Это значит, что если использовать метод validate() , тогда запрос GET к /contact/ запустит форму проверки, а пользователь увидит ошибки валидации. Вообще процедура проверки запускается только в том случае, если данные были отправлены с помощью метода POST. В противном случае вернется False . Метод validate_on_submit() вызывает метод validate() внутри себя. Также нужно обратить внимание, что при создании экземпляра объекта формы данные не передаются, потому что когда форма отправляется с помощью запроса POST, WTForm считывает данные формы из атрибута request.form .
Поля формы, определенные в классе формы становятся атрибутами объекта формы. Чтобы получить доступ к данным поля используется атрибут data поля формы:
form.name.data # доступ к данным в поле name. form.email.data # доступ к данным в поле email.
Чтобы получить доступ ко всем данные формы сразу нужно использовать атрибут data к объекту формы:
form.data # доступ ко всем данным
Если использовать запрос GET при посещении /contact/ , метод validate_on_sumbit() вернет False . Код внутри if будет пропущен, а пользователь получит пустую HTML-форму.
Когда форма отправляется с помощью запроса POST, validate_on_sumbit() возвращает True , предполагая, что данные верны. Вызовы print() внутри блока if выведут данные, введенные пользователем, а функция redirect() перенаправит пользователя на страницу /contact/ . С другой стороны, если validate_on_sumbit() вернет False , исполнение инструкций внутри тела if будет пропущено, и появится сообщение об ошибке валидации.
Если сервер не запущен, его нужно запустить и открыть https://localhost:5000/contact/ . Появится следующая контактная форма:

Если попробовать нажать Submit, не вводя данных, появятся следующие сообщения об ошибках валидации:


Теперь можно ввести определенные данные в поля Name и Message и некорректные данные в поле Email, и попробовать отправить форму снова.
Нужно обратить внимание, что все поля содержат данные из прошлого запроса.
Теперь можно ввести корректный email в поле Email и нажать Submit. Теперь проверка пройдет успешно, а в оболочке появится следующий вывод:
Spike spike@gmail.com A Message Data received. Now redirecting .
После отображения принятых данных в оболочке функция представления перенаправит пользователя по адресу /contact/ . В этот момент должна отображаться пустая форма без ошибок валидации так, будто пользователь впервые открыл /contact/ с помощью запроса GET.
Рекомендуется отображать обратную связь пользователю после успешной отправки. Во Flask это делается с помощью всплывающих сообщений.
Всплывающие сообщения
Всплывающие сообщения — еще одна из тех функций, которые зависят от секретного ключа. Он необходим, потому что сообщения хранятся в сессиях. Сессиям во Flask будет посвящен отдельный урок. Поскольку в этом уроке секретный ключ уже был настроен, можно двигаться дальше.
Для отображения сообщения используется функция flash() из пакета flask . Функция flash() принимает два аргумента: сообщение и категория (опционально). Категория указывает на тип сообщения: _success_ , _error_ , _warning_ и так далее. Категория может быть использована в шаблоне, чтобы определить тип сообщения.
Снова откроем main2.py , чтобы добавить flash(“Message Received”, “success”) прямо перед вызовом redirect() в функции представления contact() :
from flask import Flask, render_template, request, redirect, url_for, flash #. # здесь логика базы данных print("\nData received. Now redirecting . ") flash("Message Received", "success") return redirect(url_for('contact')) return render_template('contact.html', form=form)
Сообщение, заданное с помощью функции flash() , будет доступно только последующему запросу, а потом удалится.
Это только настройка сообщения. Для его отображения нужно поменять также шаблон.
Для этого нужно открыть файл contact.html и изменить его следующим образом:
Jinja предлагает функцию get_flashed_messages() , которая возвращает список активных сообщений без категории. Чтобы получить их вместе с категорией нужно передать with_category=True при вызове get_flashed_messages() . Когда значение with_categories – True , get_flashed_messages() вернет список кортежей формы (category, message) .
После этих изменений следует открыть https://localhost:5000/contact снова. Заполнить форму и нажать Submit . Сообщение об успешной отправке отобразится в верхней части формы.

- ТЕГИ
- Flask
- Уроки по Flask на русском
Как получить данные из input и перенести их в код Flask?
Мне нужно получить данные из поля input в HTML в код Flask для дальнейшей работы с ними. Я подключил модуль request , но получить данные все равно не удается. На странице ошибка 400 Bad Request . Можно ли ее как-то решить? HTML-код:
>" /> Регистрация Регистрация