Помимо форм поисковых, которые не изменяют данные, а только фильтруют их, существуют формы ввода данных. Они устроены сложнее как с клиентской стороны, так и с серверной. Для уверенной работы с ними необходимо разбираться в следующих вопросах:
- Знание соответствующих HTML-тегов
- Понимание того, как отправляются формы по HTTP
- Обработка на стороне сервера
- Валидация и вывод ошибок
Начнем с того, что за вывод формы и ее обработку должны отвечать два разных обработчика — это разные маршруты. Один обработчик по GET-запросу выводит пустую форму для заполнения, второй обработчик по POST-запросу принимает отправленные данные. Ниже пример маршрутов для создания нового пользователя:
- GET /users/new — страница с формой, которую заполняет пользователь. По нажатию кнопки отправить эта форма отправляет POST-запрос с данными на адрес /users, который указывается в атрибуте
action
- POST /users — маршрут, который обрабатывает полученные данные формы
Подобная схема именования рекомендуется и автоматически создается многими фреймворками. Она хорошо ложится на REST-архитектуру, о которой мы еще поговорим.
Чтобы реализовать добавление нового пользователя, нам нужно добавить три составляющих: описание формы, маршрут, обработчик маршрута.
Форма
Создадим новый шаблон new.html. В нем опишем поля сущности user
:
<!-- templates/users/new.html -->
<form action="/users" method="post">
<div>
<label>
Имя
<input type="text" name="name">
</label>
</div>
<div>
<label>
Email
<input type="email" required name="email">
</label>
</div>
<div>
<label>
Пароль
<input type="password" required name="password">
</label>
</div>
<div>
<label>
Подтверждение пароля
<input type="password" required name="passwordConfirmation">
</label>
</div>
<div>
<label>
Город
<select name="city">
<option value="3">Москва</option>
<option value="13">Пенза</option>
<option value="399">Томск</option>
</select>
</label>
</div>
<input type="submit" value="Sign Up">
</form>
Обработка данных
Сперва рассмотрим как выглядит обработка полученных данных. Для задания маршрута воспользуемся декоратором @app.post()
, который позволяет обрабатывать POST-запросы:
@app.post("/users")
def users_post():
repo = UserRepository()
# извлекаем данные из формы
user = request.form.to_dict()
# валидируем данные
errors = validate(user)
if errors:
return render_template(
"users/new.html",
user=user,
errors=errors,
)
# сохраняем нового пользователя
repo.save(user)
# делаем редирект на список пользователей
return redirect("/users", code=302)
Обработка данных формы начинается с извлечения данных из тела запроса. Это можно сделать двумя способами, похожими на то, как мы извлекаем параметры запроса:
request.form.to_dict()
– извлекает все данные и преобразовывает их в словарьrequest.form.get()
– извлекает значение конкретного параметра. Вторым параметром принимает значение по умолчанию
В нашем случае для извлечения данных мы используем метод request.form.to_dict()
:
user = request.form.to_dict()
Одно из главных правил веб-разработки - не доверять данным от пользователя. Все полученное от пользователей необходимо проверять на ошибки прежде чем внести в базу данных. Процесс проверки корректности данных называется валидацией. Flask, как и большинство микрофреймворков, не предоставляет механизмов для валидации. Но ее можно получить из сторонних библиотек.
В нашем случае валидация реализуется функцией validate()
. Она проверяет данные формы и возвращает специальный словарь errors
, в котором ключ — это название поля, а значение — выводимый в форме текст ошибки:
def validate(user):
errors = {}
if not user["name"]:
errors["name"] = "Can't be blank"
return errors
errors = validate(user)
Если ошибок нет, то данные формы сохраняются, например, в базу данных. После сохранения обычно выполняется перенаправление — HTTP-redirect. За перенаправление отвечает заголовок Location и статусы с кодом 3XX:
repo.save(user)
return redirect('/users', code=302)
Если в процессе обработки возникли ошибки, выполняется рендеринг формы из того же шаблона, что мы использовали для /users/new. В этот шаблон передаются как данные формы, так и список ошибок. Редирект не происходит, а в адресной строке остается адрес /users.
Если попробовать в этот момент нажать f5, то браузер выдаст предупреждение о том, что мы пытаемся повторно отправить данные. Это сообщение предупреждает о том, что метод POST не идемпотентен, и повторная отправка формы может привести к повторному созданию пользователя. Вернем соответствующий статус с кодом 4XX, что укажет на ошибку со стороны пользователя:
if errors:
return render_template(
'users/new.html',
user=user,
errors=errors,
), 422
Вернемся к нашей форме и изменим ее, чтобы в нее подставлялись как возникающие ошибки, так и значения полей, которые вводились пользователем:
<!-- templates/users/new.html -->
<form action="/users" method="post">
<div>
<label>
Имя
<input type="text" name="name" value="{{ user.name }}">
</label>
{% if errors['name'] %}
<div>{{ errors['name'] }}</div>
{% endif %}
</div>
<div>
<label>
Email
<input type="email" required name="email" value="{{ user.email }}">
</label>
{% if errors['email'] %}
<div>{{ errors['email'] }}</div>
{% endif %}
</div>
<div>
<label>
Пароль
<input type="password" required name="password" value="{{ user.password }}">
</label>
{% if errors['password'] %}
<div>{{ errors['password'] }}</div>
{% endif %}
</div>
<div>
<label>
Подтверждение пароля
<input type="password" required name="passwordConfirmation" value="{{ user.passwordConfirmation }}">
</label>
</div>
<div>
<label>
Город
<select name="city">
<option value="">Select</option>
<option {{ 'selected' if city == 3 else '' }} value="3">Москва</option>
<option {{ 'selected' if city == 13 else '' }} value="13">Пенза</option>
<option {{ 'selected' if city == 399 else '' }} value="399">Томск</option>
</select>
</label>
{% if errors['city'] %}
<div>{{ errors['city'] }}</div>
{% endif %}
</div>
<input type="submit" value="Sign Up">
</form>
Такое изменение формы требует изменения обработчика /users/new. Чтобы избежать ошибок, необходимо передать в шаблон пустые словари errors
и user
, в котором необходимо задать значения по умолчанию для соответствующих полей формы. Так в шаблоне не придется выполнять проверку данных формы на существование:
@app.route("/users/new")
def users_new():
user = {
"name": "",
"email": "",
"password": "",
"passwordConfirmation": "",
"city": "",
}
errors = {}
return render_template("users/new.html", user=user, errors=errors)
Обратите внимание, как форма увеличилась в размерах. На практике она будет еще больше из-за дополнительного оформления, например, отступов и подсветки ошибок. Когда удастся сделать десяток форм, станет очевидно, что это усложнит работу. Ради простейшей обработки придется писать много практически идентичного кода в HTML. Эта работа требует автоматизации.
Чтобы генерировать формы, используют специальные билдеры. Обычно у микрофреймворков нет встроенных билдеров, поэтому придется искать их самостоятельно.
Популярным расширением для Flask является WTForms. В этом компоненте каждая форма представлена собственным классом. Компонент поддерживает валидацию, имеет встроенные механизмы защиты от некоторых атак и многое другое.
Заключение
Мы узнали как добавить модифицирующую форму. Сперва необходимо создать обработчики для вывода и обработки формы. Также все введеные данные необходимо валидировать прежде чем сохранять в базу данных. Наконец, нужно добавить саму HTML-форму с нужными полями и выводом ошибок.
Самостоятельная работа
- Создайте шаблон и добавьте обработчик, который выводит форму создания пользователя с полями:
name
иemail
. Аid
должен генерироваться внутри приложения - Добавьте в обработчик сохранение введенных данных. Для хранения пользователя используйте файл. Сами данные можно кодировать в json с помощью функции модуля json
dumps()
и декодировать с помощьюloads()
- После добавления данных в файл должен происходить редирект на адрес /users
Эталонное приложение
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.