Собираем приложение: блог от и до

Пора соединить всё изученное в одно приложение — блог с авторами, постами, формами и шаблонами. Цель — увидеть, как кирпичики складываются в систему.
Отдельные темы — это детали. Сила в их связке: фабрика собирает приложение, блюпринт группирует маршруты, модели хранят данные, формы валидируют ввод, шаблоны рисуют страницы. Разберём поток данных через все слои на примере «создать пост».

Спроектируем блог. Структура — по канонам из раздела про фабрику:

  blog/
  ├── blog/
  │   ├── __init__.py     # create_app()
  │   ├── extensions.py   # db = SQLAlchemy()
  │   ├── models.py       # User, Post
  │   ├── forms.py        # PostForm (Flask-WTF)
  │   ├── views.py        # блюпринт posts
  │   └── templates/      # base.html, list.html, form.html
  ├── config.py
  └── requirements.txt

Поток «создать пост» проходит через все слои, которые мы изучили:

  GET /posts/new
     │ view создаёт PostForm, render_template(form)
     ▼ пользователь заполняет, сабмит
  POST /posts/new
     │ form.validate_on_submit()  (валидация + CSRF)
     ├─ невалидно → показать форму с ошибками
     └─ валидно:
          Post(title=form.title.data, author=current)
          db.session.add(post); commit()
          flash("Пост создан")
          redirect(url_for("posts.list"))   ← PRG

Блюпринт группирует связанные маршруты и регистрируется в фабрике:

# views.py
from flask import Blueprint, render_template, redirect, url_for, flash
posts = Blueprint("posts", __name__, url_prefix="/posts")

@posts.route("/")
def list():
    items = db.session.execute(select(Post)).scalars().all()
    return render_template("list.html", posts=items)

@posts.route("/new", methods=["GET", "POST"])
def new():
    form = PostForm()
    if form.validate_on_submit():
        post = Post(title=form.title.data, body=form.body.data)
        db.session.add(post)
        db.session.commit()
        flash("Пост создан", "success")
        return redirect(url_for("posts.list"))
    return render_template("form.html", form=form)

Главный навык этого урока — мыслить приложение как конвейер слоёв с чётким разделением ответственности. Запрос входит сверху и идёт вниз: маршрутизация выбирает view, форма валидирует и отбивает CSRF, модель пишет в базу через сессию, шаблон рендерит безопасный HTML, ответ уходит наружу. Каждый слой знает только своё дело, и это делает код тестируемым и изменяемым: можно поменять вёрстку, не трогая модель, или базу, не трогая шаблоны. View в этой схеме — тонкий дирижёр: он оркеструет слои, но не содержит тяжёлой логики, которую выносят в сервисы. Поток «создать сущность» — валидация, запись, flash, PRG-редирект — ты будешь воспроизводить почти в каждом действии, меняющем данные.

Как работает под капотом

Запрос проходит конвейер слоёв сверху вниз и собирает ответ. Каждый слой отвечает за своё: маршрутизация выбирает view, форма валидирует, модель пишет в базу, шаблон рисует.

  HTTP-запрос
     ▼ маршрутизация (блюпринт + route)
  view-функция
     ▼ форма (валидация + CSRF)
  модель (db.session → база)
     ▼ данные
  шаблон Jinja (наследование, автоэкранирование)
     ▼
  HTTP-ответ

Смоделируем этот конвейер обработки запроса обычным Python — увидишь, как слои передают данные дальше.

db_posts = []

def validate_form(form):                       # слой формы
    errors = {}
    if not form.get("title", "").strip():
        errors["title"] = "Заголовок обязателен"
    return errors

def save_post(form):                           # слой модели
    post = {"id": len(db_posts) + 1, "title": form["title"]}
    db_posts.append(post)
    return post

def handle(method, form):                      # view-функция
    if method == "POST":
        errors = validate_form(form)
        if errors:
            return ("200", "форма с ошибками", errors)
        save_post(form)
        return ("302", "/posts", None)         # PRG
    return ("200", "пустая форма", {})

print(handle("POST", {"title": "Привет, Flask"}))
print(handle("POST", {"title": ""}))
print("в базе постов:", len(db_posts))

Запусти: валидный POST сохраняет пост и отдаёт редирект, пустой возвращает ошибки. Это скелет реального view блога — только вместо словарей реальные форма, модель и шаблон.

Частые ошибки

  • Толстые view. Бизнес-логику выноси в сервисы/модели; view — только оркестрация.
  • Забыть url_prefix или имя блюпринта в url_for. Ссылка — "posts.list", а не "list".
  • Логика в шаблонах. Готовь данные во view, шаблон только отображает.

Best practices

  • Группируй маршруты по блюпринтам (posts, auth, admin).
  • Один поток на действие: валидация → запись → flash → PRG-редирект.
  • Держи слои разделёнными: маршрут, форма, модель, шаблон — каждый о своём.

Что запомнить

  • Приложение — конвейер слоёв, собранный фабрикой.
  • Запрос идёт: маршрут → форма → модель → шаблон → ответ.
  • View — тонкий дирижёр; тяжёлую логику выносят в сервисы.
  • Типовое действие: валидация → запись → flash → PRG-редирект.

Итог: приложение — это конвейер слоёв, собранный фабрикой. Запрос идёт маршрут → форма → модель → шаблон → ответ. Дальше — блюпринты подробнее как способ масштабировать структуру.

Проверьте себя
1. В каком порядке запрос проходит слои при создании поста?
Aшаблон → форма → модель → маршрут
Bмаршрут → форма (валидация) → модель (БД) → шаблон → ответ
Cмодель → шаблон → форма
Dформа → ответ → маршрут
2. Как правильно построить ссылку на endpoint list внутри блюпринта posts?
Aurl_for('list')
Burl_for('posts.list') — с именем блюпринта через точку
Curl_for('/posts/')
Durl_for(posts.list)