WTForm
.WTForm
.WTForm.Form
.validate_<fieldname>
в качестве валидатора поля формы.Form
.wtforms.validators
.WTForm
.Ни один веб-сайт не обходится без HTML-форм, будь это страница обратной связи, страница авторизации или даже форма для комментариев. Когда нужно работать с данными HTML-формы, то код быстро становится очень трудным для чтения. Существуют библиотеки, призванные упростить управление этим процессом. Одним из них является WTForms
.
Модуль WTForms
определяет свои формы для использования в шаблонах как классы. Для этого рекомендуется разбить приложение на несколько модулей и добавить отдельный модуль для форм.
Примечание. Расширение Flask-WTF добавляет несколько небольших помощников, которые делают работу с формами и фреймворком Flask более быстрой и удобной.
Пример формы для типичной страницы регистрации:
# например forms.py from wtforms import Form, BooleanField, StringField, PasswordField, validators class RegistrationForm(Form): username = StringField('Имя пользователя', [validators.Length(min=4, max=25)]) email = StringField('Email-адрес', [validators.Length(min=6, max=35)]) password = PasswordField('Новый пароль', [ validators.DataRequired(), validators.EqualTo('confirm', message='Пароли должны совпадать') ]) confirm = PasswordField('Повторите пароль') accept_tos = BooleanField('Я принимаю TOS', [validators.DataRequired()])
В функции-представлении, обработка формы, определенной выше, выглядит так:
# например views.py @app.route('/register', methods=['GET', 'POST']) def register(): # создаем экземпляр класса формы form = RegistrationForm(request.form) # если HTTP-метод POST и данные формы валидны if request.method == 'POST' and form.validate(): # используя схему `SQLAlchemy` создаем объект, # для последующей записи в базу данных user = User(form.username.data, form.email.data, form.password.data) db_session.add(user) flash('Спасибо за регистрацию') return redirect(url_for('login')) # если HTTP-метод GET, то просто отрисовываем форму return render_template('register.html', form=form)
Обратите внимание, что здесь представление использует модуль для работы с базой данных SQLAlchemy
, но это, конечно, не является обязательным требованием. При необходимости код нужно скорректировать.
То, что нужно помнить:
request.form
, если данные отправляются с помощью метода HTTP POST
, и request.args
, если данные отправляются как GET
..validate()
, который вернет True
, если данные валидны, и False
в противном случае.form.<NAME>.data
, где <NAME>
- имя поля формы.Модуль WTForms
генерирует практически всю форму за нас. Чтобы это было еще приятнее, можно написать макрос, который отображает поле с меткой и списком ошибок, если таковые имеются.
Пример шаблона jinja2
_formhelpers.html
с таким макросом:
{# _formhelpers.html #} {% macro render_field(field) %} <dt>{{ field.label }} <dd>{{ field(**kwargs)|safe }} {% if field.errors %} <ul class=errors> {% for error in field.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} </dd> {% endmacro %}
Этот макрос принимает пару ключевых аргументов, которые передаются в функцию поля WTForm
, которая генерирует поле. Ключевые аргументы будут вставлены в качестве атрибутов HTML. Так, например, можно вызвать render_field(form.username, class='username')
, чтобы добавить атрибут class
в HTML-элемент <input>
. Обратите внимание, что WTForms
возвращает стандартные строки Python, поэтому необходимо сообщить Jinja2, что эти данные уже экранированы с помощью фильтра {{ val | safe }}
.
Пример шаблона register.html
, который использует преимущества макроса, импортируемого из шаблона _formhelpers.html
, созданного ранее:
{# register.html #} {% from "_formhelpers.html" import render_field %} <form method=post> <dl> {{ render_field(form.username) }} {{ render_field(form.email) }} {{ render_field(form.password) }} {{ render_field(form.confirm) }} {{ render_field(form.accept_tos) }} </dl> <p><input type=submit value=Register></p> </form>
WTForm
.WTForm.Form
Класс Form
содержит определения полей, делегирует проверку/валидацию, принимает ввод, объединяет ошибки и в целом служит связующим звеном, скрепляющим все вместе.
Чтобы определить форму, нужно создать подкласс Form
и декларативно определить поля как атрибуты класса:
from wtforms import Form, StringField, validators class MyForm(Form): first_name = StringField('First Name', validators=[validators.input_required()]) last_name = StringField('Last Name', validators=[validators.optional()])
Имена полей могут быть любыми допустимыми идентификаторами python со следующими ограничениями:
'_'
.'validate'
.Формы могут при необходимости подклассифицировать другие формы. Новая форма будет содержать все поля родительской формы, а также любые новые поля, определенные в подклассе. Повторное использование имени поля в подклассе приводит к тому, что новое определение переопределяет исходное.
class PastebinEdit(Form): language = SelectField('Programming Language', choices=PASTEBIN_LANGUAGES) code = TextAreaField() class PastebinEntry(PastebinEdit): name = StringField('User Name')
validate_<fieldname>
в качестве валидатора поля формы.Чтобы обеспечить настраиваемую проверку/валидацию, для каждого поля формы можно определить метод с именем validate_<fieldname>
, где fieldname
- это имя поля:
class SignupForm(Form): age = IntegerField('Age') def validate_age(form, field): if field.data < 13: raise ValidationError("We're sorry, you must be 13 or older to register")
Form
.Атрибуты экземпляра класса Form
:
Form.data
: словарь, содержащий данные для каждого поля. Обратите внимание, что он генерируется каждый раз, когда к нему обращаются. При неоднократном обращении - это может быть дорогостоящей операцией. Обычно используется, для перебора всех значений полей формы. Если нужно получить доступ к данным для известных полей, то должны использовать form.<field>.data
, а не прокси form.data[
field]
.Form.errors
: словарь, содержащий список ошибок (после проверки методом Form.validate()
) для каждого поля формы. Будет пустой, если форма не была проверена или ошибок не было.Методы экземпляра класса Form
:
Form.validate()
: проверяет форму, вызвав функцию validate()
для каждого поля. Возвращает True
, если проверка прошла успешно. Если форма определяет метод `validate_ Form.populate_obj(obj)
: заполняет атрибуты переданного объекта obj
данными из полей формы.
Примечание: Любой атрибут переданного obj
с тем же именем, что и поле, будет переопределен. Используйте с осторожностью.
Одним из распространенных способов использования:
def edit_profile(request): user = User.objects.get(pk=request.session['userid']) form = EditProfileForm(request.POST, obj=user) if request.POST and form.validate(): form.populate_obj(user) user.save() return redirect('/home') return render_to_response('edit_profile.html', form=form)
Поля формы отвечают за визуализацию и преобразование данных. Они делегируют полномочия валидаторам для проверки данных.
Поля декларативно определяются как атрибуты класса формы Form
:
class MyForm(Form): name = StringField('Full Name', [validators.required(), validators.length(max=10)]) address = TextAreaField('Mailing Address', [validators.optional(), validators.length(max=200)])
Когда в форме определено поле, параметры построения сохраняются до тех пор, пока форма не будет создана. Во время создания экземпляра формы создается копия поля со всеми параметрами, указанными в определении. Каждый экземпляр поля хранит свои собственные данные поля и список ошибок.
Метка label
и валидаторы могут быть переданы конструктору в качестве позиционных аргументов, в то время как все остальные аргументы должны передаваться в качестве ключевых аргументов. Некоторые поля (например, SelectField
) также могут принимать дополнительные аргументы ключевых слов для конкретных полей. Обратитесь к справочнику по встроенным полям для получения информации о них.
label
: метка поля.validators
: последовательность встроенных валидаторов или вызываемых объектов, которые вызываются методом Form.validate()
и по очереди применяются к значению поля. filters
: последовательность фильтров, которые запускаются для входных данных процессом.description
: описание поля, обычно используется для текста справки.Id
: - идентификатор для использования в поле. Разумное значение по умолчанию задается формой, и вам не нужно устанавливать его вручную.default
: значение по умолчанию для присвоения полю, если не предусмотрена форма или ввод объекта. Может быть вызываемым.widget
: если предоставляется, переопределяет виджет, используемый для визуализации поля.render_kw
: словарь, если предоставлен, то должен содержать ключевые слова (по умолчанию), которые будут переданы виджету widget
во время рендеринга.Встроенные типы полей обычно представляют скалярные типы данных с отдельными значениями и относятся к одному полю формы.
Во всех встроенных типах полей, аргумент field_arguments
- это аргументы конструктора базового класса поля.
BooleanField(field_arguments, false_values=None)
: представляет . Устанавливает статус checked
с помощью аргумента конструктора default
. Любое значение default
, например default="checked", делает отметку в html-элементе и устанавливает данные в значение True
.
Необязательный аргумент false_values
: последовательность строк, каждая из которых является строкой точного соответствия тому, что считается ложным значением. По умолчанию используется кортеж (False, 'false', '',)
DateField(field_arguments, format='%Y-%m-%d')
: текстовое поле, в котором хранится значение, соответствующее формату datetime.date
.
DateTimeField(field_arguments, format='%Y-%m-%d %H:%M:%S')
: текстовое поле, в котором хранится значение, соответствующее формату datetime.datetime
.
DecimalField(field_arguments, places=2, rounding=None, use_locale=False, number_format=None)
: текстовое поле, в котором отображаются и приводятся данные типа decimal.Decimal
.
Аргументы:
places
: На сколько знаков после запятой нужно округлить значение для отображения в форме. Если None
, то значение не округляется.rounding
: Как округлить значение, например decimal.ROUND_UP
. Если значение не установлено, то используется значение из контекста текущего потока.use_locale
: Если True
, то форматирование чисел будет на основе локали. Для форматирования чисел на основе локали требуется пакет babel
.FileField(field_arguments)
: отображает поле загрузки файла. По умолчанию, значением будет имя файла, отправленное в данных формы.
Модуль WTForms
не занимается обработки файлов. Расширение Flask-WTF
может заменить значение имени файла объектом, представляющим загруженные данные.
Пример использования:
class UploadForm(Form): image = FileField('Image File', [validators.regexp('^[^/\\]\.jpg$')]) description = TextAreaField('Image Description') def validate_image(form, field): if field.data: field.data = re.sub(r'[^a-z0-9_.-]', '_', field.data) def upload(request): form = UploadForm(request.POST) if form.image.data: image_data = request.FILES[form.image.name].read() open(os.path.join(UPLOAD_PATH, form.image.data), 'w').write(image_data)
MultipleFileField(field_arguments)
: то же самое, что и FileField
, но позволяет выбирать несколько файлов.
FloatField(field_arguments)
: текстовое поле, за исключением того, что все вводимые данные приводятся к типу float
. Ошибочный ввод игнорируется и не принимается в качестве значения. Для большинства применений DecimalField
предпочтительнее FloatField
, за исключением случаев, когда IEEE float
абсолютно желателен вместо десятичного значения.
IntegerField(field_arguments)
: текстовое поле, за исключением всего ввода, приводится к целому числу. Ошибочный ввод игнорируется и не принимается в качестве значения.
RadioField(field_arguments, choices=[], coerce=unicode)
: подобно SelectField()
, за исключением того, что отображает список переключателей.
Итерация/цикл по полю приведет к созданию подполей (каждое из которых также содержит метку).
{% for subfield in form.radio %} <tr> <td>{{ subfield }}</td> <td>{{ subfield.label }}</td> </tr> {% endfor %}
Простой вывод поля без создания цикла, приведет к получению <ul>
списка.
SelectField(field_arguments, choices=[], coerce=unicode, option_widget=None, validate_choice=True)
:
Поля <select>
принимают параметр choices
, который представляет собой список пар (value, label)
. Это также может быть список только значений value
, и в этом случае значение используется в качестве метки label
. Значение может быть любого типа, но т.к. данные формы отправляются в браузер в виде строк, необходимо будет предоставить функцию coerce
, которая преобразует строку обратно в ожидаемый тип.
Пример поля SelectField()
со статическими значениями:
class PastebinEntry(Form): language = SelectField('Programming Language', choices=[('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')])
Обратите внимание, что ключевое слово choices
оценивается только один раз, поэтому, если надо создать динамический раскрывающийся список, то нужно будет назначить список вариантов для поля после создания экземпляра. Любые введенные варианты, которых нет в данном списке, приведут к сбою проверки в поле. НО можно пропустить проверку, передав аргумент validate_choice=False
.
Пример поля SelectField()
со статическими значениями:
class UserDetails(Form): group_id = SelectField('Group', coerce=int) def edit_user(request, id): user = User.query.get(id) form = UserDetails(request.POST, obj=user) form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]
Обратите внимание, что мы не передали варианты выбора конструктору SelectField
, а создали список в функции-представлении. Кроме того, ключевое слово coerce
для SelectField()
говорит о том, что для приведения данных формы используется int()
.
SelectMultipleField(field_arguments, choices=[], coerce=unicode, option_widget=None)
: ни чем не отличается от обычного поля SelectField()
, за исключением того, что оно может принимать и проверять несколько вариантов. Для этого необходимо указать HTML-атрибут size
для поля <select>
при рендеринге.
SubmitField(field_arguments)
: представляет <input type="submit">
. Это позволяет проверить, была ли нажата данная кнопка отправки.
StringField(field_arguments)
: это поле является основой для большинства более сложных полей и представляет собой <input type="text">
.
HiddenField(field_arguments)
: это строковое поле с виджетом HiddenInput
. Оно будет отображаться как , но в противном случае принудительно преобразуется в строку.
Скрытые поля похожи на любое другое поле в том смысле, что они могут принимать валидаторы и значения и быть доступны в объекте формы. Рассмотрите возможность проверки/валидации скрытых полей так же, как обычных.
PasswordField(field_arguments)
: поле ввода пароля <input type="password">
, всегда преобразуется в строку. Кроме того, любое значение, принятое этим полем, не отображается обратно в браузер, как обычные поля.
TextAreaField(field_arguments)
: поле представляет собой текстовое поле <textarea>
и может использоваться для многострочного ввода.
Поля формы, определенные в подклассе класса Form
, могут проверяться встроенными валидаторами, определенными в wtform.validators
. Встроенные валидаторы передаются списком, как аргумент типа поля формы.
from wtforms import Form, PasswordField from wtforms.validators import InputRequired, EqualTo class ChangePassword(Form): password = PasswordField('Новый пароль', [InputRequired(), EqualTo('confirm', message='Пароли должны совпадать')]) confirm = PasswordField('Повторите Пароль')
wtforms.validators
:Email(message=None, granular_message=False, check_deliverability=False, allow_smtputf8=True, allow_empty_local=False)
:
Проверяет адрес электронной почты. Требуется установка модуля email_validator
.
Аргументы:
message
: сообщение об ошибке, которое должно появиться в случае ошибки проверки.granular_messsage
: сообщение об ошибке проверки из модуля email_validator
(по умолчанию False
).check_deliverability
: выполняет проверку разрешения доменных имен (по умолчанию False
).allow_smtputf8
: вызывает ошибку проверки адресов, для которых требуется SMTPUTF8 (по умолчанию True
).allow_empty_local
: разрешает пустую локальную часть (т. е. @example.com
), например, для проверки псевдонимов (по умолчанию False
).EqualTo(fieldname, message=None)
: сравнивает значения двух полей.
Этот валидатор можно использовать для облегчения одного из наиболее распространенных сценариев формы смены пароля:
Аргументы:
fieldname
: имя другого поля для сравнения.message
: – сообщение об ошибке, которое должно появиться в случае ошибки проверки. Можно интерполировать с помощью %(other_label)s
и %(other_name)s
, чтобы обеспечить более полезную ошибку.Пример:
from wtforms import Form, PasswordField from wtforms.validators import InputRequired, EqualTo class ChangePassword(Form): password = PasswordField('Новый пароль', [InputRequired(), EqualTo('confirm', message='Пароли должны совпадать')]) confirm = PasswordField('Повторите Пароль')
Здесь используется валидатор InputRequired()
, чтобы предотвратить попытку валидатора EqualTo()
проверить, не совпадают ли пароли, если пароли не были указаны вообще. Поскольку InputRequired()
останавливает цепочку проверки, то EqualTo()
не запускается в случае, если поле пароля остается пустым.
InputRequired(message=None)
: проверяет, что для поля были предоставлены данные. Другими словами, значение поля - не пустая строка. Этот валидатор также устанавливает флаг обязательного поля формы для заполнения.
IPAddress(ipv4=True, ipv6=False, message=None)
: проверяет IP-адрес. Аргумент Ipv4
- если True
, принимать адреса IPv4 как действительные (по умолчанию True
). Аргумент Ipv6
- если True
, принимать IPv6-адреса как действительные (по умолчанию False
)
Length(min=- 1, max=- 1, message=None)
: проверяет длину строки. Аргумент min
- минимальная необходимая длина строки. Если не указан, минимальная длина проверяться не будет. Аргумент max
- максимальная длина строки. Если не указан, максимальная длина проверяться не будет.
MacAddress(message=None)
: проверяет MAC-адрес. Аргумент message
- сообщение об ошибке, которое будет выдано в случае ошибки проверки.
NumberRange(min=None, max=None, message=None)
: проверяет, что число имеет минимальное и/или максимальное значение включительно. Это будет работать с любым сопоставимым типом чисел, таким как числа с плавающей запятой и десятичные дроби, а не только с целыми числами.
Optional(strip_whitespace=True)
: разрешает пустой ввод (необязательное поле) и останавливает продолжение цепочки проверки. Если ввод пуст, также удаляются предыдущие ошибки из поля (например, ошибки обработки). Если аргумент strip_whitespace=True
(по умолчанию), то также остановит цепочку проверки, если значение поля состоит только из пробелов.
Regexp(regex, flags=0, message=None)
: проверяет поле на соответствие регулярному выражению, предоставленному пользователем. Аргумент regex
- cтрока регулярного выражения для использования. Также может быть скомпилированным шаблоном регулярного выражения. Аргумент flags
- используемые флаги регулярного выражения, например re.IGNORECASE
. Игнорируется, если регулярное выражение не является строкой.
URL(require_tld=True, message=None)
: простая проверка URL на основе регулярного выражения. Вероятно потребуется его проверка на доступность другими способами.
UUID(message=None)
: проверяет UUID.
AnyOf(values, message=None, values_formatter=None)
: сравнивает входящие данные с последовательностью допустимых входных данных. Аргумент values
- последовательность допустимых входных данных. Аргумент values_formatter
- функция, используемая для форматирования списка значений в сообщении об ошибке message
.
NoneOf(values, message=None, values_formatter=None)
: сравнивает входящие данные с последовательностью неверных входных данных. Аргумент values
- последовательность допустимых входных данных. Аргумент values_formatter
- функция, используемая для форматирования списка значений в сообщении об ошибке message
.
Выше было показано использование встроенного в класс Form
валидатора (как метода с определенным именем) для проверки одного поля. Встроенные валидаторы хороши, но их сложно использовать повторно.
Так как тип поля формы принимает аргумент validators
в качестве последовательности вызываемых объектов, то это может быть простая функция Python, которая возвращает значение bool
. НО лучше использовать фабричную функцию, которая возвращает вызываемый объект:
def length(min=-1, max=-1, message=None): if not message: message = f'Must be between {min} and {max} characters long.' def _length(form, field): l = field.data and len(field.data) or 0 if l < min or max != -1 and l > max: raise ValidationError(message) return _length # использование валидатора `length` class MyForm(Form): name = StringField('Name', [InputRequired(), length(max=50)])