Зарегистрируйтесь, чтобы продолжить обучение

Модифицирующие формы Python: Веб-разработка (Flask)

Помимо форм поисковых, которые не изменяют данные, а только фильтруют их, существуют формы ввода данных. Они устроены сложнее как с клиентской стороны, так и с серверной. Для уверенной работы с ними необходимо разбираться в следующих вопросах:

  • Знание соответствующих 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-форму с нужными полями и выводом ошибок.


Самостоятельная работа

  1. Создайте шаблон и добавьте обработчик, который выводит форму создания пользователя с полями: name и email. А id должен генерироваться внутри приложения
  2. Добавьте в обработчик сохранение введенных данных. Для хранения пользователя используйте файл. Сами данные можно кодировать в json с помощью функции модуля json dumps() и декодировать с помощью loads()
  3. После добавления данных в файл должен происходить редирект на адрес /users

image_processing.png

Эталонное приложение

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff