Маршрутизация, список статей и страница статьи
В этой главе вы научитесь использовать APIRouter для разделения маршрутов, реализуя список статей на главной странице и страницу статьи.
APIRouter — решение FastAPI для разделения маршрутов
APIRouter аналогичен Blueprint во Flask и include() в Django.
Пример
from fastapi import APIRouter, Depends, HTTPException, Запрос
from sqlalchemy.orm import Session
from database import get_db
from models import Post, Category
from schemas import PostResponse
router = APIRouter(
prefix="/posts", # Все маршруты автоматически имеют префикс /posts
tags=["Article"] # Группировать теги в документации Swagger
)
@router.get("/{post_id}", response_model=PostResponse, name="post_detail")
def post_detail(post_id: int, db: Session = Depends(get_db)):
"""Детали статьи (JSON API)"""
post = db.query(Post).filter(Post.id == post_id).first()
if not post:
raise HTTPException(status_code=404, detail=“Статья не существует”)
return post
Подключить APIRouter в main.py:
Пример
from routers.categories import router as categories_router
app.include_router(posts_router)
app.include_router(categories_router)
Отрисовать страницу списка статей
Пример
from fastapi import APIRouter, Depends, Request, Запрос
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from database import get_db
from models import Post, Category
router = APIRouter()
templates = Jinja2Templates(directory="templates")
@router.get("/", name="index")
def index(
request: Request,
category: str | None = Query(None), # Параметры запроса: необязательные
db: Session = Depends(get_db)
):
"""Главная страница: список статей + фильтр по категориям"""
# Базовый запрос
posts_query = db.query(Post).order_by(Post.created_at.desc())
# Фильтр по категории
if category:
posts_query = posts_query.join(Category).filter(Category.slug == category)
posts = posts_query.all()
categories = db.query(Category).all()
return templates.TemplateResponse("index.html", {
"request": request,
"posts": posts,
"categories": categories,
"category_slug": category or ""
})
str | None = Query(None)Это синтаксис объединения типов Python 3.10+, эквивалентныйOptional[str]. FastAPI автоматически, основываясь на аннотациях типов: 1. сделает параметр необязательным; 2. пометит его как необязательный в документации Swagger; 3. не выдаст ошибку, если он не указан.
Отрисовать страницу статьи
Пример
@router.get("/post/{post_id}", name="post_detail")
def post_detail(request: Request, post_id: int, db: Session = Depends(get_db)):
"""Страница статьи"""
post = db.query(Post).filter(Post.id == post_id).first()
if not post:
raise HTTPException(status_code=404, detail=“Статья не существует”)
return templates.TemplateResponse("post_detail.html", {
"request": request,
"post": post
})
Обратите внимание на различие: один и тот же маршрут может предоставлять как JSON API (с объявлением response_model), так и HTML-страницу (возвращая TemplateResponse). Обычная практика: маршруты API размещаются под префиксом /api/, а маршруты страниц — в корневом пути.
Создайте шаблоны главной страницы и страницы статьи
Пример
{% extends 'base.html' %}
{% block title %}EXAMPLE Blog - Главная{% endblock %}
{% block content %}
<h2 class="section-title">Последние статьи</h2>
<div class="category-bar">
<a href="/" class="{% if not category_slug %}active{% endif %}">Все</a>
{% for cat in categories %}
<a href="/?category={{ cat.slug }}"
class="{% if category_slug == cat.slug %}active{% endif %}">
{{ cat.name }}
</a>
{% endfor %}
</div>
{% if posts %}
<div class="article-grid">
{% for post in posts %}
<a href="/post/{{ post.id }}" class="card-link">
<div class="article-card">
<div class="card-content">
<span class="card-category">{{ post.category.name }}</span>
<h3>{{ post.title }}</h3>
<p>{{ post.summary|truncate(80) }}</p>
<span class="card-date">{{ post.created_at.strftime('%Y-%m-%d') }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="empty-tip">В этой категории пока нет статей.</p>
{% endif %}
{% endblock %}
Пример
{% extends 'base.html' %}
{% block title %}{{ post.title }} - ПРИМЕР Блог{% endblock %}
{% block content %}
<article class="post-view">
<span class="category-tag">{{ post.category.name }}</span>
<h1>{{ post.title }}</h1>
<time>{{ post.created_at.strftime('%Y-%m-%d') }}</time>
<div class="content">{{ post.content|safe }}</div>
<a href="/" class="back-link">← Назад на главную</a>
</article>
{% endblock %}
Итоги главы
В этой главе вы завершили разработку основной маршрутизации FastAPI: APIRouter разделяет маршруты по модулям (prefix + tags), параметры запроса объявляются с помощью Query() + аннотаций типов, TemplateResponse рендерит страницы, а также обработка исключений 404.
Теперь в блоге есть три полноценные страницы: список статей на главной, фильтрация по категориям и страница подробностей.
Другие расширения