Принять и обработать форму во Flask: пошаговая инструкция
Разбираем, как создать HTML-форму, принять данные на сервере и вернуть результат пользователю — от простого текстового поля до загрузки файла. Всё на Flask 3.x с проверкой каждого шага.

Что понадобится до начала
В конце у вас будет работающее Flask-приложение, которое принимает имя и email из формы, валидирует их на сервере и показывает результат. На всё уйдёт около 20 минут, если Python уже установлен.
Убедитесь, что у вас есть:
- Python 3.9 или новее (проверьте командой
python3 --version); - pip — менеджер пакетов Python;
- терминал и текстовый редактор или IDE.
Шаг 1. Установить Flask и создать структуру проекта
Создайте каталог проекта и виртуальное окружение. Flask 3.x требует Python 3.9+.
mkdir flask-form && cd flask-form
python3 -m venv .venv
source .venv/bin/activate
pip install flask
Проверка: команда pip show flask покажет версию 3.x. Если версия ниже 3.0 — обновите pip и повторите установку.
Структура каталога:
flask-form/
├── app.py
└── templates/
├── form.html
└── result.html
Шаг 2. Написать HTML-форму
Создайте файл templates/form.html. Атрибут method="POST" отправляет данные в теле запроса, а не в строке адреса — для форм с персональными данными это единственный разумный вариант.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
</head>
<body>
<h1>Регистрация</h1>
{% if error %}
<p style="color:red">{{ error }}</p>
{% endif %}
<form method="POST" action="/submit">
<label>Имя:
<input type="text" name="username" value="{{ values.username or '' }}">
</label><br><br>
<label>Email:
<input type="email" name="email" value="{{ values.email or '' }}">
</label><br><br>
<button type="submit">Отправить</button>
</form>
</body>
</html>
Обратите внимание: имена полей username и email — именно по ним Flask найдёт данные на сервере. Значения подставляются обратно через Jinja2, чтобы пользователь не терял ввод при ошибке валидации.
Шаг 3. Принять данные на сервере
Создайте app.py. Ключевой объект — request.form: это словарь, в который Flask кладёт все поля из тела POST-запроса.
from flask import Flask, request, render_template, redirect, url_for
app = Flask(__name__)
@app.route("/")
def index():
# Показываем пустую форму
return render_template("form.html", error=None, values={})
@app.route("/submit", methods=["POST"])
def submit():
username = request.form.get("username", "").strip()
email = request.form.get("email", "").strip()
# Простая серверная валидация
if not username or not email:
return render_template(
"form.html",
error="Заполните оба поля.",
values={"username": username, "email": email},
)
if "@" not in email or "." not in email.split("@")[-1]:
return render_template(
"form.html",
error="Введите корректный email.",
values={"username": username, "email": email},
)
return render_template("result.html", username=username, email=email)
if __name__ == "__main__":
app.run(debug=True)
Почему request.form.get(), а не request.form["username"]? Прямой доступ по ключу выбросит 400 Bad Request, если поле отсутствует — например, при запросе через curl без нужного параметра. Метод .get() вернёт значение по умолчанию и позволит обработать ошибку самостоятельно.
Шаг 4. Показать результат
Создайте templates/result.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Готово</title>
</head>
<body>
<h1>Спасибо, {{ username }}</h1>
<p>Письмо отправим на {{ email }}.</p>
<a href="/">Назад к форме</a>
</body>
</html>
Jinja2 автоматически экранирует вывод — переменные {{ username }} и {{ email }} безопасны от XSS-инъекций по умолчанию, потому что autoescaping включён для .html-шаблонов.
Шаг 5. Запустить и проверить
python3 app.py
В терминале появится строка * Running on http://127.0.0.1:5000. Откройте этот адрес в браузере, заполните оба поля и нажмите «Отправить». Вы увидите страницу с приветствием и указанным email.
Проверьте негативные сценарии: оставьте одно из полей пустым — форма вернётся с красным сообщением. Введите email без точки после «@» — та же ошибка.
GET vs POST: когда что использовать
| Критерий | GET | POST |
|---|---|---|
| Где данные | В URL (query string) | В теле запроса |
| Видно ли в истории браузера | Да | Нет |
| Ограничение длины | ~2000 символов (зависит от браузера) | Ограничено настройкой сервера |
| Для чего подходит | Поиск, фильтрация, закладки | Регистрация, логин, загрузка файлов |
| Объект Flask | request.args | request.form |
Если форма ищет товары — GET: пользователь сможет скопировать URL с результатами. Если форма отправляет пароль или файл — только POST.
Серверная валидация обязательна, даже если вы добавите проверку на клиенте. HTML-атрибуты вродеrequiredиtype="email"легко обойти: любой запрос можно собрать без браузера. Flask-приложение должно считать входящие данные враждебными по умолчанию.
Загрузка файла через ту же форму
Если нужно принять файл — добавьте к тегу <form> атрибут enctype="multipart/form-data", иначе браузер отправит только имя файла. На стороне Flask файлы живут в request.files, а не в request.form.
# Фрагмент обработки файла в маршруте /submit
uploaded = request.files.get("document")
if uploaded and uploaded.filename:
# Безопасное имя файла — защита от path traversal
from werkzeug.utils import secure_filename
safe_name = secure_filename(uploaded.filename)
uploaded.save(f"./uploads/{safe_name}")
Функция secure_filename из Werkzeug* убирает из имени файла точки-слэши и спецсимволы, которые позволили бы записать файл за пределы целевого каталога.
* Werkzeug — WSGI-библиотека, на которой построен Flask. Устанавливается автоматически как зависимость Flask.
Если не получилось
jinja2.exceptions.TemplateNotFound: form.html— шаблон лежит не в папкеtemplates/рядом сapp.py, или папка названа иначе. Flask ищет шаблоны строго вtemplates/.405 Method Not Allowed— маршрут/submitне разрешает POST. Проверьте, что в@app.routeуказаноmethods=["POST"].- Данные приходят пустые, хотя форма заполнена — скорее всего, форма отправляется GET-запросом. Убедитесь, что в теге
<form>стоитmethod="POST", а неmethod="GET"или пропущен атрибут (по умолчанию — GET). UnicodeDecodeErrorпри загрузке файла — вы пытаетесь прочитать бинарный файл как текст. Используйтеuploaded.read()для байтов илиuploaded.save()для записи на диск.- Файл сохраняется, но каталог
uploads/не существует — Flask не создаёт его автоматически. Добавьтеos.makedirs("./uploads", exist_ok=True)перед вызовом.save().
Что делать дальше
Форма работает, данные приходят — но это только начало. Вот что стоит добавить следующим шагом:
- CSRF-защита. Без неё сторонний сайт может отправить POST-запрос от имени вашего пользователя. Расширение Flask-WTF добавляет токен в форму одной строкой — загляните в его документацию.
- Flash-сообщения. Вместо передачи ошибки через шаблон используйте встроенный механизм
flask.flash()— сообщения переживают редирект и не дублируются при обновлении страницы. - Паттерн POST → Redirect → GET. Сейчас после отправки формы браузер остаётся на POST-странице: если пользователь нажмёт F5, данные отправятся повторно. Решение — после успешной обработки вернуть
redirect(url_for("result"))вместо прямогоrender_template. - Сохранение в базу. Данные нужно куда-то класть. SQLite через Flask-SQLAlchemy — самый быстрый старт.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: Flask — Quickstart: Accessing Request Data (официальная документация), Werkzeug — secure_filename (документация)
Частые вопросы
Чем request.form отличается от request.args?
Нужно ли экранировать данные из формы перед выводом в шаблоне?
Что будет, если отправить форму с пустым полем, а я обращаюсь к request.form['username']?
Этот код будет работать на Flask 2.x?
Как принять JSON вместо данных формы?
Обязательно ли ставить Flask-WTF для работы с формами?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.