Búsqueda por palabras clave y paginación
En este capítulo aprenderás a implementar búsqueda combinada con SQLAlchemy y paginación con offset/limit.
Búsqueda ilike insensible a mayúsculas y minúsculas
Ejemplo
posts = db.query(Post).filter(Post.title.ilike(f'%{keyword}%')).all()
# Búsqueda combinada de múltiples campos (relación OR)
from sqlalchemy import or_
posts = db.query(Post).filter(
or_(
Post.title.ilike(f'%{keyword}%'),
Post.summary.ilike(f'%{keyword}%')
)
).all()
Paginación con offset/limit
Flask-SQLAlchemy proporciona un conveniente.paginate()método.
FastAPI utiliza SQLAlchemy nativo, por lo que se necesitaoffset + limitimplementar la paginación manualmente.
| Método de paginación | Flask-SQLAlchemy | FastAPI (SQLAlchemy nativo) |
|---|---|---|
| Obtener datos de la página actual | .paginate(page, per_page).items | .offset(skip).limit(per_page).all() |
| Número total de registros | pagination.total | query.count() |
| Total de páginas | pagination.pages | Calcular manualmente math.ceil(total / per_page) |
Fórmula de cálculo de paginación
Ejemplo
from fastapi import Query
def get_pagination(page: int = 1, size: int = 6):
"""
Calcular parámetros de paginación
page: número de página actual (desde 1)
size: número de elementos por página
"""
skip = (page - 1) * size # Número de registros a omitir
return skip, size
# Ejemplo de uso
skip, limit = get_pagination(page=2, size=6)
# skip = 6, limit = 6 → omitir los primeros 6 registros, tomar los del 7 al 12
Vista completa de búsqueda + paginación
Ejemplo
from sqlalchemy import or_
from math import ceil
from fastapi import APIRouter, Depends, Request, Query
@router.get("/", name="index")
def index(
request: Request,
category: str | None = Query(None),
q: str | None = Query(None, description=Palabra clave de búsqueda),
page: int = Query(1, ge=1, description=Número de página),
size: int = Query(6, ge=1, le=50, description=Elementos por página),
db: Session = Depends(get_db)
):
"""Página de inicio: filtro de categoría + búsqueda por palabra clave + paginación"""
posts_query = db.query(Post).order_by(Post.created_at.desc())
# Filtro de categoría
if category:
posts_query = posts_query.join(Category).filter(Category.slug == category)
# Búsqueda por palabra clave
if q:
keyword = f'%{q.strip()}%'
posts_query = posts_query.filter(
or_(
Post.title.ilike(keyword),
Post.summary.ilike(keyword)
)
)
# Paginación
total = posts_query.count()
total_pages = ceil(total / size)
skip = (page - 1) * size
posts = posts_query.offset(skip).limit(size).all()
return templates.TemplateResponse("index.html", {
"request": request,
"posts": posts,
"categories": db.query(Category).all(),
"category_slug": category or "",
"keyword": q or "",
"page": page,
"total_pages": total_pages,
"total": total
})
Query(1, ge=1)engees la abreviatura de greater than or equal. Los parámetros de consulta de FastAPI admiten restricciones numéricas completas: ge, le, gt, lt. Si el usuario pasa page=0, FastAPI devuelve automáticamente un error 422.
Navegación de paginación en la plantilla
Ejemplo
{% if total_pages > 1 %}
<div class="pagination">
{% if page > 1 %}
<a href="/?page={{ page - 1 }}&category={{ category_slug }}&q={{ keyword }}">← Página anterior</a>
{% endif %}
<span class="page-info">Página {{ page }} / {{ total_pages }} páginas en total</span>
{% if page < total_pages %}
<a href="/?page={{ page + 1 }}&category={{ category_slug }}&q={{ keyword }}">Página siguiente →</a>
{% endif %}
</div>
{% endif %}
Resumen del capítulo
En este capítulo implementaste las funciones prácticas de la página de listado: búsqueda ilike (insensible a mayúsculas y minúsculas), búsqueda combinada de múltiples campos con or_(), paginación manual con offset/limit (reemplazando el paginate de Flask), y parámetros de consulta limitados con Query().
La búsqueda + el filtro de categoría + la paginación se pueden combinar de manera arbitraria, las condiciones no se pierden.
Otras extensiones