Обработка HTML-форм в веб-приложениях — несложная задача. Казалось бы, о чем говорить: набросал форму в шаблоне, создал обработчики на сервере, и готово. Проблемы начинаются, когда форма разрастается: нужно следить за полями, их ID, атрибутами name, корректно маппить атрибуты на бэкенде при генерации и процессинге данных. А если часть формы нужно еще и переиспользовать, то разработка превращается в постоянную рутину: приходится бесконечно копировать атрибуты тегов с клиента на сервер и копипастить однотипный код. Однако есть способы сделать работу с формами удобной.
 

Зачем это нужно?

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

<form action="">
    <!-- personal info -->
    <input type="text" id="f_name" name="f_name" placeholder="John" />
    <input type="text" id="l_name" name="l_name" placeholder="Dow" />

    <!-- account info -->
    <input type="email" id="email" name="email" placeholder="john@example.com" />
    <input type="password" id="password" name="password" placeholder="**********" />

    <!-- meta info -->
    <select name="gender" id="gender">
        <option value="0">Male</option>
        <option value="1" selected>Female</option>
    </select>
    <input type="city" id="city" name="city" placeholder="Saint-Petersburg" />
    <textarea name="signature" id="signature" cols="30" rows="10"></textarea>

    <input type="submit" value="Create user!" />
</form>

Эта форма выглядит просто. Однако использование в реальном приложении добавит ряд задач.

  1. У каждого поля (или в одном блоке) нужно вывести информацию об ошибках, которые могут появиться при валидации формы.
  2. Скорее всего, для некоторых полей мы захотим иметь подсказки.
  3. Наверняка нам нужно будет повесить по одному или несколько CSS-классов на каждое поле или даже делать это динамически.
  4. Часть полей должна содержать предзаполненные данные с бэкенда — предыдущие попытки сабмита формы или данные для выпадающих списков. Частный случай с полем gender прост, однако опции для селекта могут формироваться запросами к БД.

И так далее. Все эти доделки раздуют нашу форму как минимум вдвое.

А теперь посмотрим на то, как мы будем обрабатывать эту форму на сервере. Для каждого поля мы должны сделать следующее.

  1. Корректно смаппить его по name.
  2. Проверить диапазон допустимых значений — валидировать форму.
  3. Если были ошибки, сохранить их, вернув форму для редактирования назад на клиентскую часть.
  4. Если все ОK, то смаппить их на объект БД или аналогичную по свойствам структуру для дальнейшего процессинга.

Вдобавок при создании пользователя тебе как админу нужно заполнять только часть данных (email и password), остальное пользователь заполнит сам в профиле. В этом случае тебе, скорее всего, придется скопировать шаблон, удалив часть полей, создать идентичный обработчик формы на сервере или вставлять проверки в текущий для различных вариантов формы. Логику валидации полей придется или копировать, или выносить в отдельную функцию. При этом нужно не запутаться в названиях полей, приходящих с клиента, иначе данные просто потеряются.

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

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

Было бы удобнее описать форму в каком-то декларативном формате, например в виде Python-класса, одноразово описав все параметры, классы, валидаторы, обработчики, а заодно предусмотрев возможности ее наследования и расширения. Вот тут-то нам и поможет библиотека WTForms.

INFO

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

 

Установка

Для начала установим саму библиотеку. Я буду показывать примеры на Python 3. Там, где нужен контекст, код исполняется в обработчике фреймворка aiohttp. Сути это не меняет — примеры будут работать с Flask, Sanic или любым другим модулем. В качестве шаблонизатора используется Jinja2. Устанавливаем через pip:

pip install wtforms

Проверяем версию.

import wtforms
wtforms.__version__
# '2.2.1'

Попробуем переписать форму выше на WTForms и обработать ее.

 

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

В WTForms есть ряд встроенных классов для описания форм и их полей. Определение формы — это класс, наследуемый от встроенного в библиотеку класса Form. Поля формы описываются атрибутами класса, каждому из которых при создании присваивается инстанс класса поля типа, соответствующего типу поля формы. Звучит сложно, на деле проще.

from wtforms import Form, StringField, TextAreaField, SelectField, validators

class UserForm(Form):
    first_name = StringField('First name', [validators.Length(min=5, max=30)])
    last_name = StringField('Last name', [validators.Length(min=5, max=30)])

    email = StringField('Email', [validators.Email()])
    password = StringField('Password')

    # meta
    gender = SelectField('Gender', coerce=int, choices=[  # cast val as int
        (0, 'Male'),
        (1, 'Female'),
    ])
    city = StringField('City')
    signature = TextAreaField('Your signature', [validators.Length(min=10, max=4096)])

Вот что мы сделали:

  • создали класс UserForm для нашей формы. Он наследован от встроенного FormBaseForm);
  • каждое из полей формы описали атрибутом класса, присвоив объект встроенного в либу класса типа Field.

В большинстве полей формы мы использовали импортированный класс StringField. Как нетрудно догадаться, поле gender требует ввода другого типа — ограниченного набора значений (м/ж), поэтому мы использовали SelectField. Подпись пользователя тоже лучше принимать не в обычном input, а в textarea, поэтому мы использовали TextAreaField, чье HTML-представление (виджет) — тег <textarea>. Если бы нам нужно было обрабатывать числовое значение, мы бы импортировали встроенный класс IntegerField и описали бы поле им.

WWW

В WTForms множество встроенных классов для описания полей, посмотреть все можно здесь. Также можно создать поле кастомного класса.

О полях нужно знать следующее.

  1. Каждое поле может принимать набор аргументов, общий для всех типов полей.
  2. Почти каждое поле имеет HTML-представление, так называемый виджет.
  3. Для каждого поля можно указать набор валидаторов.
  4. Некоторые поля могут принимать дополнительные аргументы. Например, для SelectField можно указать набор возможных значений.
  5. Поля можно добавлять к уже существующим формам. И можно модифицировать, изменять значения на лету. Это особенно полезно, когда нужно чуть изменить поведение формы для одного конкретного случая, при этом не создавать новый класс формы.
  6. Поля могут провоцировать ошибки валидации по заданным правилам, они будут храниться в form.field.errors.

Продолжение доступно только подписчикам

Материалы из последних выпусков можно покупать отдельно только через два месяца после публикации. Чтобы продолжить чтение, необходимо купить подписку.

Подпишись на «Хакер» по выгодной цене!

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

4 комментария

  1. act274

    24.09.2018 at 17:03

    Чем это лучше Django’вских форм?

  2. tolytrader

    28.09.2018 at 01:26

    Почему-то тип не преобразует

    import wtforms as wtf

    class Form(wtf.Form):
    a = wtf.IntegerField()

    f = Form(data={‘a’: ‘1’})

    if f.validate():
    print(type(f.a.data))

    # выдает
    # а если передать строку ‘1aaa’ — валидацию пройдет

Оставить мнение

Check Also

Целенаправленная социальная инженерия. Нестандартные техники введения в заблуждение

В предыдущей статье мы разобрали массовые атаки. Но их применимость ограничена: пентестер …