Динамічні форми з Flask: створення та обробка змінних полів
Flask є одним із найпопулярніших мікрофреймворків для розробки веб-додатків на Python. Завдяки його простоті та гнучкості розробники можуть швидко створювати додатки, де частини логіки реалізовано у різних бібліотеках (наприклад, для роботи з формами). Одна з цікавих задач — реалізація динамічних форм, які можуть змінювати кількість полів або типи полів залежно від обставин. У цій статті ми розглянемо, як створити та обробляти такі “живі” форми з Flask, звертаючи увагу на використання Flask-WTF, генерацію форм на льоту і роботу з шаблонами Jinja2.
Що таке динамічні форми
Динамічна форма — це форма, яка будується або доповнюється у реальному часі, залежно від даних, введених користувачем, інформації з бази даних чи інших умов. Наприклад:
- Залежно від того, скільки товарів у кошику, ми виводимо різну кількість полів для їх редагування.
- Залежно від вибраного типу об’єкта (наприклад, “Книга” чи “Фільм”) змінюємо набір полів (автор, режисер, тривалість).
- Дозволяємо користувачам додавати нові поля у формі (динамічно додаючи блоки “Додати ще один номер телефону” тощо).
Основний інструментарій
Flask Головний фреймворк для створення маршрутизації та логіки. Flask-WTF Бібліотека, що спрощує роботу з формами, використовує WTForms. Надає зручний спосіб описувати поля і валідацію на Python. Jinja2 Шаблонізатор, що дозволяє динамічно рендерити HTML, включно з формами.
Приклад простої форми у Flask-WTF
Почнемо з найпростішого варіанта. Створимо просту форму, яка містить декілька полів.
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class SimpleForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
app.py
from flask import Flask, render_template, redirect, url_for
from forms import SimpleForm
app = Flask(__name__)
app.config['SECRET_KEY'] = 'mysecret'
@app.route('/', methods=['GET', 'POST'])
def index():
form = SimpleForm()
if form.validate_on_submit():
name = form.name.data
return redirect(url_for('success', username=name))
return render_template('index.html', form=form)
@app.route('/success/<username>')
def success(username):
return f'Hello, {username}!'
index.html (шаблон)
<!DOCTYPE html>
<html>
<head>
<title>Simple Form</title>
</head>
<body>
<form method="POST" action="">
{{ form.csrf_token }}
{{ form.name.label }}: {{ form.name(size=20) }} <br>
{{ form.submit() }}
</form>
</body>
</html>
Це статична форма (кількість полів і тип полів визначені наперед). Далі розглянемо випадок, коли поля динамічні.
Динамічна зміна кількості полів
Одне з найтиповіших завдань — коли кількість полів невідома заздалегідь. Припустимо, у нас є список телефонів, що треба відобразити у формі, і користувач може додавати нові або видаляти наявні.
Підхід 1: Генерація форм у шаблоні
Можна не описувати поля у WTForms, а динамічно створювати поля у шаблоні (наприклад, цикл for для phoneNumbers). Проте тоді ми втрачаємо частину автоматичної валідації.
Підхід 2: FieldList i FormField (WTForms)
WTForms має FieldList, що дає змогу динамічно працювати зі списком підформ.
forms.py
from flask_wtf import FlaskForm
from wtforms import Form, StringField, FieldList, FormField, SubmitField
class PhoneForm(Form):
phone_number = StringField('Phone Number')
class DynamicForm(FlaskForm):
phones = FieldList(FormField(PhoneForm), min_entries=1, max_entries=5)
submit = SubmitField('Submit')
app.py
@app.route('/dynamic', methods=['GET', 'POST'])
def dynamic():
form = DynamicForm()
if form.validate_on_submit():
# Обробка списку телефонів
phone_data = [p.phone_number.data for p in form.phones]
print('Phones:', phone_data)
return 'Data saved!'
return render_template('dynamic.html', form=form)
dynamic.html
<!DOCTYPE html>
<html>
<head>
<title>Dynamic Form</title>
</head>
<body>
<form method="POST">
{{ form.csrf_token }}
<ul>
{% for subform in form.phones %}
<li>{{ subform.phone_number.label }} {{ subform.phone_number() }}</li>
{% endfor %}
</ul>
{{ form.submit() }}
</form>
</body>
</html>
У цьому прикладі поле phones (FieldList) містить від 1 до 5 телефонів. Коли користувач надсилає форму, Flask-WTF зчитує дані і відображає їх у form.phones.
Проте, якщо треба динамічно додавати або видаляти поля без перезавантаження сторінки, потрібно застосовувати JavaScript, щоб “клонувати” HTML-елемент і додавати його у form. При відправленні ці поля приймають імена у вигляді phones-0-phone_number, phones-1-phone_number і так далі.
Умовна логіка для різних полів
Другий сценарій: у нас є один і той самий route, проте якщо користувач обирає опцію “Фізична особа”, потрібні поля ім’я та прізвище, а якщо “Компанія”, тоді поля назви компанії і код ЄДРПОУ. Можна згенерувати поля залежно від обраного типу.
Підхід: створити кілька форм-класів або одну велику з усіма полями, а в шаблоні вже вирішувати, що показувати. Але обробка стане складнішою.
Реактивна зміна з боку клієнта
Для більш інтерактивних змін (наприклад, якщо користувач обрав “Компанія”, то підтягуємо базу даних і показуємо список можливих напрямів), часто використовують JS на клієнтському боці, який викликає AJAX запит до Flask, потім Flask повертає JSON з переліком потрібних полів, і JS динамічно малює форму.
Ключові моменти для покращення досвіду
Безпека
Якщо форма має динамічно змінювану структуру, перевіряйте (наприклад, через CSRF-токен), що ця форма справді походить з вашого сайту, а не з підробленого.
Валідація
Залежно від логіки, може знадобитися різна валідація (наприклад, якщо поле номер телефону — валідація формату, якщо поле email — перевірка поштового формату).
Зберігання даних
Якщо форми стають дуже розгалуженими, варто продумати структуру бази даних (один-до-багатьох для телефонів, таблиці для різних типів сутностей).
Підтримка
Іноді варто створювати свою функцію для побудови форми, що приймає параметри або конфігурацію з бази, а повертає готову форму WTForms.
Висновок
Динамічні форми у Flask можуть бути реалізовані кількома методами: від простого генераторного підходу у шаблонах (через for-цикли) до використання FieldList та FormField у Flask-WTF. Такі інструменти як JavaScript (для наочного додавання/видалення полів на льоту) і WTForms (для валідації) доповнюють один одного. Важливо продумати, яка саме логіка у вас: чи потрібен просто список полів (FieldList), чи умовний набір полів залежно від типу користувача, чи взагалі складний сценарій, де форма переробляється за запитом із сервера. Водночас безпека та коректна валідація мають бути на висоті, аби динамічна форма була зручною і не створювала шпарин у коді.