Renderizado de plantillas Flask
Concatenar cadenas HTML en código Python es tedioso y peligroso (fácilmente produce vulnerabilidades XSS). Flask integra el motor de plantillas Jinja2 para resolver este problema.
Las plantillas te permiten separar la lógica de negocio de la presentación de la página, permitiéndote escribir código más seguro y más fácil de mantener.
Conceptos básicos de render_template
render_template()Es la función de renderizado de plantillas más utilizada en Flask.
Su primer parámetro es el nombre del archivo de plantilla; los parámetros de palabra clave siguientes se pasan como variables a la plantilla.
Ejemplo
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
# Renderiza la plantilla templates/index.html, pasando las variables title y name
return render_template("index.html", title="Inicio de EJEMPLO", name="World")
@app.route("/user/<username>")
def profile(username):
# Pasa el username de la URL a la plantilla
return render_template("user.html", username=username, posts=[
{"title": "Introducción a Flask", "date": "2026-05-01"},
{"title": "Plantillas Jinja2", "date": "2026-05-10"},
])
Los archivos de plantilla se colocan en el directoriotemplates/del proyecto:
ejemplo-flask-test/
├── app.py
└── templates/
├── index.html
└── user.html
El contenido de index.html es:
Ejemplo (index.html)
<!DOCTYPE html>
<html>
<head>
<title>{{ title }} - EJEMPLO</title>
</head>
<body>
<h1>{{ title }} </h1>
<p>Hello {{ name }}。</p>
</body>
</html>
Visitahttp://127.0.0.1:5000/, el resultado es:

Sintaxis básica de Jinja2
En las plantillas Jinja2 hay tres tipos de sintaxis principales:
| Sintaxis | Uso | Ejemplo |
|---|---|---|
| {{ ... }} | Muestra el valor de la variable, escapa HTML automáticamente | {{ username }} |
| {% ... %} | Sentencias de control (if, for, block, etc.) | {% if user %} |
| {# ... #} | Comentarios, no aparecen en el resultado renderizado | {# Esto es un comentario #} |
Ejemplo de plantilla
Ejemplo (user.html)
<!DOCTYPE html>
<html>
<head>
<title>{{ username }} - EJEMPLO</title>
</head>
<body>
<h1>Página personal de {{ username }}</h1>
<!-- Condición: si la lista posts está vacía, se muestra un mensaje -->
{% if posts %}
<h2>Artículos recientes:</h2>
<ul>
<!-- Recorre la lista posts con un bucle -->
{% for post in posts %}
<!-- loop.index es una variable integrada de Jinja2, se cuenta desde 1 -->
<li>{{ loop.index }}. {{ post.title }} ({{ post.date }})</li>
{% endfor %}
</ul>
{% else %}
<p>No hay artículos por ahora</p>
{% endif %}
<!-- Usa el filtro: upper convierte el texto a mayúsculas -->
<p>Nombre de usuario en mayúsculas: {{ username|upper }}</p>
</body>
</html>
Filtros comunes
Los filtros se usan para modificar el formato de salida de una variable, se invocan con el operador de tubería|para invocar.
| Filtro | Función | Ejemplo | Salida |
|---|---|---|---|
| upper | Poner en mayúsculas | {{ "hello"|upper }} | HELLO |
| lower | Poner en minúsculas | {{ "HELLO"|lower }} | hello |
| title | Capitalizar primera letra | {{ "hello world"|title }} | Hello World |
| length | Obtener longitud | {{ [1,2,3]|length }} | 3 |
| default | Establecer valor predeterminado | {{ name|default("Anónimo") }} | Anónimo (cuando name está vacío) |
| safe | Marcar como HTML seguro (sin escapar) | {{ "Negrita"|safe }} | Se renderiza como texto en negrita |
| join | Unir lista | {{ ["a","b"]|join(",") }} | a,b |
Advertencia de seguridad:safeLos filtros desactivan el escape automático; úsalos solo cuando confíes absolutamente en la fuente de datos. Para contenido ingresado por el usuario, nunca usessafe。
Escape automático — Protección XSS
Jinja2, por defecto, escapa con HTML la salida de todas las variables; esto es una línea de defensa clave para la seguridad web.
Por ejemplo, si el nombre de usuario enviado por el usuario es<script>alert("xss")</script>, la plantilla lo convertirá automáticamente a texto seguro al renderizar:
<!-- 模板中直接使用变量 -->
<p>{{ username }}</p>
<!-- 渲染结果(HTML 特殊字符已被转义) -->
<p>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</p>
El escape se aplica a.html、.htm、.xml、.xhtml、.svglos archivos de plantilla que terminan en .html.
Herencia de plantillas — Eliminar código repetitivo
La herencia de plantillas es una de las características más potentes de Jinja2; te permite definir un «diseño base» y luego llenar el contenido usando plantillas hijas.
Esto evita tener que escribir repetidamente partes comunes como el encabezado, la navegación y el pie de página en cada página.
Plantilla base (plantilla padre)
Ejemplo
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Tutorial Flask de EJEMPLO{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<header>
<h1>Tutorial Flask de EJEMPLO</h1>
<nav>
<a href="/">Inicio</a> |
<a href="/about">Acerca de</a>
</nav>
</header>
<main>
<!-- block es una zona de marcador de posición; la plantilla hija puede rellenarla o sobrescribirla -->
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2026 example.com</p>
</footer>
</body>
</html>
Plantilla hija (hereda de la plantilla padre)
Ejemplo
{% extends "base.html" %}
<!-- Sobrescribe el bloque title de la plantilla padre -->
{% block title %}Tutorial EJEMPLO - Inicio{% endblock %}
<!-- Rellena el bloque content de la plantilla padre -->
{% block content %}
<h2>Bienvenido a EJEMPLO</h2>
<p>{{ greeting }}</p>
{% if user %}
<p>Usuario actual: {{ user }}</p>
{% else %}
<p><a href="/login">Inicia sesión primero</a></p>
{% endif %}
{% endblock %}
| Etiqueta | Función |
|---|---|
| {% extends "base.html" %} | Declara que la plantilla actual hereda de base.html (debe ir en la primera línea) |
| {% block name %}...{% endblock %} | Define una zona que puede ser sobrescrita por la plantilla hija |
| {{ super() }} | En el block de la plantilla hija, llama al contenido del block con el mismo nombre de la plantilla padre |
Con la herencia de plantillas, solo necesitas modificarbase.htmlun lugar, y las partes comunes de todas las páginas (como la navegación, el pie de página) se actualizan automáticamente.
Objetos integrados en las plantillas
Los siguientes objetos de Flask se pueden usar directamente en las plantillas, sin necesidad derender_template()pasarlos:
| Objeto | Descripción | Ejemplo de uso en plantilla |
|---|---|---|
| request | Objeto de solicitud actual | {{ request.path }} |
| session | Datos de la sesión actual | {{ session.get("username") }} |
| g | Variable global a nivel de solicitud | {{ g.user }} |
| config | Configuración de la aplicación | {{ config["APP_NAME"] }} |
| url_for() | Función generadora de URLs | {{ url_for("index") }} |
| get_flashed_messages() | Obtener mensajes flash | {% for msg in get_flashed_messages() %}... |
Incluir otras plantillas — include
Usando{% include %}se puede incrustar una plantilla dentro de otra:
Ejemplo
<nav style="background:#f0f0f0;padding:10px;">
<a href="/">Inicio</a> |
<a href="/posts">Artículos</a> |
<a href="/about">Acerca de</a>
</nav>
Ejemplo
<!DOCTYPE html>
<html>
<head><title>Página</title></head>
<body>
{% include "_navbar.html" %}
<h1>Contenido de la página</h1>
<p>Este es el contenido principal de la página.</p>
</body>
</html>
Mejores prácticas: la herencia de plantillas (extends) se usa para la reutilización del diseño a nivel de página,includese usa para la reutilización de código a nivel de componentes. Los componentes reutilizables como la barra de navegación y la barra lateral son adecuados para usar include.
Ejemplo completo: Portada del blog
Integra los conocimientos anteriores en un escenario real:
Ejemplo
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
# Simula los datos de artículos en la base de datos
articles = [
{"id": 1, "title": "Guía de introducción a Flask", "author": "ejemplo", "views": 1024},
{"id": 2, "title": "Explicación detallada de plantillas Jinja2", "author": "admin", "views": 512},
{"id": 3, "title": "Diseño de API RESTful", "author": "ejemplo", "views": 256},
]
return render_template("blog.html", articles=articles)
Ejemplo
{% extends "base.html" %}
{% block title %}Blog EJEMPLO{% endblock %}
{% block content %}
<h2>Artículos recientes</h2>
{% if articles %}
{% for article in articles %}
<!-- Agrega diferentes estilos según el número de visitas -->
<div class="article" {% if article.views > 1000 %}style="background:#fff3cd;"{% endif %}>
<h3>{{ article.title }}</h3>
<p>Autor: {{ article.author }} | Vistas: {{ article.views }}</p>
<!-- Usar la variable loop para implementar la línea separadora -->
{% if not loop.last %}
<hr>
{% endif %}
</div>
{% endfor %}
{% else %}
<p>Por el momento no hay artículos, por favor vuelva más tarde.</p>
{% endif %}
{% endblock %}
Variables especiales de Jinja2
| Variable | Descripción |
|---|---|
| loop.index | Índice de bucle actual, comienza en 1 |
| loop.index0 | Índice de bucle actual, comienza en 0 |
| loop.first | Si es la primera iteración |
| loop.last | Si es la última iteración |
| loop.length | Longitud total de la secuencia |