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

# Ruta de archivo: app.py
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)

<!-- Ruta de archivo: templates/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)

<!-- Ruta de archivo: templates/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 特殊字符已被转义) -->
&lt;p&gt;&amp;lt;script&amp;gt;alert(&amp;quot;xss&amp;quot;)&amp;lt;/script&amp;gt;&lt;/p&gt;

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

<!-- Ruta de archivo: templates/base.html -->
<!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>&copy; 2026 example.com</p>
    </footer>
</body>
</html>

Plantilla hija (hereda de la plantilla padre)

Ejemplo

<!-- Ruta de archivo: templates/index.html -->
{% 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

<!-- Ruta de archivo: templates/_navbar.html (el guion bajo al inicio indica que es una plantilla parcial) -->
<nav style="background:#f0f0f0;padding:10px;">
    <a href="/">Inicio</a> |
    <a href="/posts">Artículos</a> |
    <a href="/about">Acerca de</a>
</nav>

Ejemplo

<!-- Incluye la barra de navegación en cualquier plantilla -->
<!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

# Ruta de archivo: app.py
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

<!-- Ruta de archivo: templates/blog.html -->
{% 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
Otras extensiones