Condicionales if-elif-else
Los condicionales en Jinja2 permiten renderizar contenido HTML de forma dinámica basándose en condiciones específicas. Esta funcionalidad es fundamental para crear interfaces web que se adapten a diferentes estados de la aplicación y datos del usuario.
La sintaxis de condicionales en Jinja2 utiliza bloques que comienzan con {% if %}
y terminan con {% endif %}
. Esta estructura nos permite evaluar expresiones y mostrar contenido diferente según el resultado.
Sintaxis básica del condicional if
El condicional más simple evalúa una condición y muestra contenido solo si es verdadera:
{% if user.is_authenticated %}
<p>Bienvenido, {{ user.name }}!</p>
{% endif %}
En este ejemplo, el párrafo de bienvenida solo se renderiza si el usuario está autenticado. La expresión condicional puede evaluar cualquier variable o expresión que resulte en un valor verdadero o falso.
{% if products %}
<h3>Productos disponibles:</h3>
{% endif %}
{% if user.age >= 18 %}
<div class="content-adult">
<p>Contenido para mayores de edad</p>
</div>
{% endif %}
Uso de else para casos alternativos
La cláusula else nos permite especificar qué contenido mostrar cuando la condición no se cumple:
{% if user.is_premium %}
<div class="premium-badge">
<span>Usuario Premium</span>
</div>
{% else %}
<div class="upgrade-notice">
<a href="/upgrade">Actualizar a Premium</a>
</div>
{% endif %}
Este patrón es especialmente útil para mostrar diferentes estados de la interfaz. Por ejemplo, en una aplicación FastAPI que gestiona productos:
{% if product.stock > 0 %}
<button class="btn btn-primary">Comprar ahora</button>
<span class="stock-info">{{ product.stock }} disponibles</span>
{% else %}
<button class="btn btn-secondary" disabled>Agotado</button>
<span class="out-of-stock">Producto no disponible</span>
{% endif %}
Condiciones múltiples con elif
Cuando necesitamos evaluar múltiples condiciones en secuencia, utilizamos elif
(else if). Esta estructura permite crear lógica más compleja sin anidar múltiples condicionales:
{% if user.role == 'admin' %}
<nav class="admin-menu">
<a href="/admin/users">Gestionar usuarios</a>
<a href="/admin/products">Gestionar productos</a>
</nav>
{% elif user.role == 'editor' %}
<nav class="editor-menu">
<a href="/editor/content">Editar contenido</a>
</nav>
{% elif user.role == 'viewer' %}
<nav class="viewer-menu">
<a href="/dashboard">Dashboard</a>
</nav>
{% else %}
<nav class="guest-menu">
<a href="/login">Iniciar sesión</a>
<a href="/register">Registrarse</a>
</nav>
{% endif %}
Operadores de comparación y lógicos
Jinja2 soporta todos los operadores de comparación estándar para crear condiciones más específicas:
{% if product.price > 100 %}
<span class="expensive-item">Producto premium</span>
{% elif product.price >= 50 %}
<span class="medium-price">Precio moderado</span>
{% else %}
<span class="budget-item">Precio económico</span>
{% endif %}
Los operadores lógicos and
, or
y not
permiten combinar múltiples condiciones:
{% if user.is_authenticated and user.has_permission %}
<div class="secure-content">
<h2>Área protegida</h2>
<p>Contenido confidencial</p>
</div>
{% endif %}
{% if not user.email_verified %}
<div class="alert alert-warning">
<p>Por favor, verifica tu dirección de email</p>
<a href="/verify-email">Enviar verificación</a>
</div>
{% endif %}
Ejemplo práctico con FastAPI
Veamos cómo implementar condicionales en una aplicación FastAPI completa que muestra diferentes estados de un producto:
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/product/{product_id}", response_class=HTMLResponse)
async def show_product(request: Request, product_id: int):
# Simulamos datos de producto
product = {
"id": product_id,
"name": "Smartphone Pro",
"price": 599.99,
"stock": 15,
"category": "electronics",
"is_featured": True,
"discount_percentage": 10
}
user = {
"is_authenticated": True,
"is_premium": False,
"age": 25
}
return templates.TemplateResponse(
"product_detail.html",
{"request": request, "product": product, "user": user}
)
El template correspondiente utilizaría condicionales para mostrar diferentes elementos:
<div class="product-card">
<h1>{{ product.name }}</h1>
{% if product.is_featured %}
<div class="featured-badge">
<span>⭐ Producto destacado</span>
</div>
{% endif %}
{% if product.discount_percentage > 0 %}
<div class="price-section">
<span class="original-price">${{ product.price }}</span>
<span class="discounted-price">
${{ "%.2f"|format(product.price * (1 - product.discount_percentage/100)) }}
</span>
<span class="discount-badge">-{{ product.discount_percentage }}%</span>
</div>
{% else %}
<div class="price-section">
<span class="price">${{ product.price }}</span>
</div>
{% endif %}
{% if product.stock > 10 %}
<div class="stock-high">
<span class="text-success">En stock ({{ product.stock }} unidades)</span>
</div>
{% elif product.stock > 0 %}
<div class="stock-low">
<span class="text-warning">Últimas {{ product.stock }} unidades</span>
</div>
{% else %}
<div class="stock-empty">
<span class="text-danger">Producto agotado</span>
</div>
{% endif %}
</div>
Buenas prácticas para condicionales
Mantén la lógica simple en los templates. Si necesitas lógica compleja, es mejor procesarla en el código Python y pasar el resultado al template:
# En lugar de lógica compleja en el template
@app.get("/dashboard")
async def dashboard(request: Request):
user_status = determine_user_status(user) # Lógica en Python
return templates.TemplateResponse(
"dashboard.html",
{"request": request, "user_status": user_status}
)
Usa nombres descriptivos para las variables booleanas que faciliten la lectura:
{% if can_edit_product %}
<button class="edit-btn">Editar producto</button>
{% endif %}
{% if should_show_discount %}
<div class="discount-banner">¡Oferta especial!</div>
{% endif %}
Los condicionales en Jinja2 proporcionan la flexibilidad necesaria para crear interfaces dinámicas que respondan a los datos y el estado de la aplicación, mantieniendo el código HTML limpio y semánticamente correcto.
Bucles for y while
Los bucles en Jinja2 nos permiten iterar sobre colecciones de datos y generar contenido HTML repetitivo de forma eficiente. Esta funcionalidad es esencial para mostrar listas de elementos como productos, usuarios, comentarios o cualquier conjunto de datos dinámicos en nuestras aplicaciones FastAPI.
Bucle for básico
El bucle for es la estructura de iteración más utilizada en Jinja2. Su sintaxis comienza con {% for %}
y termina con {% endfor %}
, permitiendo iterar sobre listas, tuplas, diccionarios y otros objetos iterables:
<ul class="product-list">
{% for product in products %}
<li>{{ product.name }} - ${{ product.price }}</li>
{% endfor %}
</ul>
Este ejemplo básico itera sobre una lista de productos y genera un elemento <li>
para cada uno. La variable de iteración product
está disponible dentro del bucle y puede acceder a todos los atributos del objeto.
Iteración sobre diccionarios
Cuando trabajamos con diccionarios, podemos iterar sobre las claves, valores, o ambos simultáneamente:
<!-- Iterar solo sobre valores -->
<div class="user-info">
{% for value in user_data.values() %}
<span>{{ value }}</span>
{% endfor %}
</div>
<!-- Iterar sobre clave-valor -->
<dl class="product-specs">
{% for key, value in product.specifications.items() %}
<dt>{{ key|title }}</dt>
<dd>{{ value }}</dd>
{% endfor %}
</dl>
Variables especiales del bucle
Jinja2 proporciona una variable especial loop que contiene información útil sobre la iteración actual:
<table class="data-table">
{% for item in inventory %}
<tr class="{% if loop.index % 2 == 0 %}even{% else %}odd{% endif %}">
<td>{{ loop.index }}</td>
<td>{{ item.name }}</td>
<td>{{ item.quantity }}</td>
{% if loop.first %}
<td class="first-row">Primer elemento</td>
{% elif loop.last %}
<td class="last-row">Último elemento</td>
{% else %}
<td>{{ loop.index }} de {{ loop.length }}</td>
{% endif %}
</tr>
{% endfor %}
</table>
Las propiedades más útiles de la variable loop incluyen:
loop.index
: índice actual (comienza en 1)loop.index0
: índice actual (comienza en 0)loop.first
: verdadero si es la primera iteraciónloop.last
: verdadero si es la última iteraciónloop.length
: número total de elementosloop.revindex
: índice inverso (último elemento es 1)
Bucles anidados
Los bucles anidados permiten iterar sobre estructuras de datos complejas, como listas de categorías que contienen productos:
<div class="category-grid">
{% for category in categories %}
<div class="category-section">
<h3>{{ category.name }}</h3>
<div class="products-grid">
{% for product in category.products %}
<div class="product-card">
<h4>{{ product.name }}</h4>
<p class="price">${{ product.price }}</p>
<small>Categoría {{ loop.index }} - Producto {{ loop.index }}</small>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
En bucles anidados, podemos acceder al bucle padre usando loop.index
del nivel superior dentro del contexto apropiado.
Manejo de listas vacías con else
Una característica importante de los bucles for en Jinja2 es la capacidad de manejar colecciones vacías usando la cláusula else
:
<div class="search-results">
<h2>Resultados de búsqueda</h2>
{% for result in search_results %}
<div class="result-item">
<h3>{{ result.title }}</h3>
<p>{{ result.description }}</p>
</div>
{% else %}
<div class="no-results">
<p>No se encontraron resultados para tu búsqueda</p>
<a href="/search" class="btn btn-primary">Nueva búsqueda</a>
</div>
{% endfor %}
</div>
La sección else se ejecuta únicamente cuando la colección está vacía, proporcionando una forma elegante de mostrar contenido alternativo.
Filtros en bucles
Los filtros pueden aplicarse durante la iteración para transformar o filtrar datos:
<div class="recent-posts">
{% for post in posts|sort(attribute='date')|reverse %}
<article class="post">
<h2>{{ post.title }}</h2>
<time>{{ post.date|strftime('%d/%m/%Y') }}</time>
<p>{{ post.content|truncate(150) }}</p>
</article>
{% endfor %}
</div>
<!-- Mostrar solo elementos únicos -->
<div class="tags">
{% for tag in article.tags|unique %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
Limitación de bucles con slice
Para controlar el número de elementos mostrados, podemos usar el filtro slice:
<!-- Mostrar solo los primeros 5 productos -->
<div class="featured-products">
<h2>Productos destacados</h2>
{% for product in products|slice(5) %}
<div class="featured-item">
<img src="{{ product.image_url }}" alt="{{ product.name }}">
<h3>{{ product.name }}</h3>
<p class="price">${{ product.price }}</p>
</div>
{% endfor %}
</div>
<!-- Paginación básica -->
<div class="product-page">
{% for product in products|slice(page_size, (page-1)*page_size) %}
<div class="product-item">{{ product.name }}</div>
{% endfor %}
</div>
Bucle while (limitado)
Es importante mencionar que Jinja2 no tiene bucle while tradicional como otros lenguajes de programación. Sin embargo, podemos simular comportamientos similares usando recursión con macros o procesando la lógica en Python:
# En el código Python de FastAPI
@app.get("/countdown")
async def countdown_page(request: Request, start: int = 10):
# Generamos la secuencia en Python en lugar de usar while en Jinja2
countdown_numbers = list(range(start, 0, -1))
return templates.TemplateResponse(
"countdown.html",
{"request": request, "numbers": countdown_numbers}
)
<!-- En el template -->
<div class="countdown">
{% for number in numbers %}
<div class="count-item">{{ number }}</div>
{% endfor %}
<div class="final">¡Despegue!</div>
</div>
Ejemplo práctico con FastAPI
Veamos un ejemplo completo que integra bucles en una aplicación FastAPI para mostrar un catálogo de productos:
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/catalog", response_class=HTMLResponse)
async def product_catalog(request: Request):
# Datos simulados de productos por categorías
catalog_data = {
"electronics": [
{"name": "Smartphone", "price": 599.99, "stock": 15, "rating": 4.5},
{"name": "Laptop", "price": 1299.99, "stock": 8, "rating": 4.8},
{"name": "Tablet", "price": 399.99, "stock": 0, "rating": 4.2}
],
"clothing": [
{"name": "Camiseta", "price": 29.99, "stock": 50, "rating": 4.0},
{"name": "Jeans", "price": 79.99, "stock": 25, "rating": 4.3}
],
"books": [
{"name": "Python FastAPI", "price": 45.00, "stock": 12, "rating": 4.9},
{"name": "Web Development", "price": 38.50, "stock": 7, "rating": 4.4}
]
}
return templates.TemplateResponse(
"catalog.html",
{"request": request, "catalog": catalog_data}
)
El template correspondiente utilizaría bucles para mostrar la información:
<div class="catalog-container">
<h1>Catálogo de productos</h1>
{% for category_name, products in catalog.items() %}
<section class="category-section">
<h2 class="category-title">{{ category_name|title }}</h2>
{% for product in products %}
<div class="product-card {% if loop.first %}first-product{% endif %}">
<div class="product-header">
<h3>{{ product.name }}</h3>
<div class="rating">
{% for star in range(5) %}
{% if star < product.rating %}
<span class="star filled">★</span>
{% else %}
<span class="star empty">☆</span>
{% endif %}
{% endfor %}
</div>
</div>
<div class="product-info">
<span class="price">${{ "%.2f"|format(product.price) }}</span>
{% if product.stock > 0 %}
<span class="stock available">{{ product.stock }} disponibles</span>
<button class="btn btn-primary">Agregar al carrito</button>
{% else %}
<span class="stock unavailable">Agotado</span>
<button class="btn btn-secondary" disabled>No disponible</button>
{% endif %}
</div>
<small class="product-meta">
Producto {{ loop.index }} de {{ loop.length }} en {{ category_name }}
</small>
</div>
{% else %}
<p class="no-products">No hay productos en esta categoría</p>
{% endfor %}
</section>
{% else %}
<div class="empty-catalog">
<h2>Catálogo vacío</h2>
<p>No hay productos disponibles en este momento</p>
</div>
{% endfor %}
</div>
Optimización y buenas prácticas
Procesa datos complejos en Python antes de pasarlos al template. Los bucles en Jinja2 deben ser simples y enfocados en la presentación:
# Mejor: preparar datos en Python
@app.get("/dashboard")
async def dashboard(request: Request):
# Procesar y organizar datos
processed_data = organize_dashboard_data(raw_data)
return templates.TemplateResponse("dashboard.html", {
"request": request,
"data": processed_data
})
Limita el tamaño de las colecciones para evitar problemas de rendimiento. Para listas grandes, implementa paginación:
from fastapi import Query
@app.get("/products")
async def list_products(request: Request, page: int = Query(1, ge=1)):
page_size = 20
offset = (page - 1) * page_size
products = get_products_paginated(offset, page_size)
return templates.TemplateResponse("products.html", {
"request": request,
"products": products,
"page": page,
"has_next": len(products) == page_size
})
Los bucles en Jinja2 proporcionan la herramienta fundamental para crear interfaces dinámicas que muestran colecciones de datos de manera estructurada y visualmente atractiva, manteniendo la separación clara entre la lógica de negocio y la presentación.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en FastAPI
Documentación oficial de FastAPI
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, FastAPI es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de FastAPI
Explora más contenido relacionado con FastAPI y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender la sintaxis y uso de condicionales if, elif y else en Jinja2.
- Aprender a utilizar operadores de comparación y lógicos en plantillas.
- Dominar la iteración con bucles for sobre listas y diccionarios, incluyendo bucles anidados.
- Conocer el manejo de colecciones vacías con la cláusula else en bucles.
- Aplicar buenas prácticas para mantener la lógica simple y eficiente en plantillas Jinja2.