Jinja2 en Flask: Introducción

Publicado por Andrea Navarro en

Jinja2 es un motor de templates para Python que se encuentra configurado automáticamente en el framework Flask. La utilización de un motor facilita la escritura de vistas y permite separar la lógica de un sistema de su presentación. En este artículo se realizará una introducción a Jinja2 para la creación de templates en Flask.

Para devolver un template en Flask con Jinja2 se utiliza la función render_template. Esta toma por argumento el archivo del template correspondiente y la lista de variables que deberán mostrarse en el mismo.

@app.route('/')
def index():
   texto = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
   return render_template('template1.html' , texto = texto) 

El archivo template.html se encuentra dentro de una carpeta llamada templates dentro de la aplicación. Es posible pasar a la función tantas variables como sea necesario.

return render_template('template1.html' , titulo = titulo, subtitulo = subtitulo, texto = texto)

Templates en Jinja2

Los templates son archivos de texto que contienen variables y expresiones que Jinja2 reemplazará por las variables pasadas. Además cuenta con herramientas que permitirán manejar la lógica del mismo.

Para mostrar variables en el template se utilizan llaves dobles.

<h2>{{subtitulo}}</h2>

Para insertar comentarios se utiliza llaves y numeral.

{# Esto es un comentario #}

Es posible indicar a Jinja2 la presentación de diferentes secciones del template utilizando una sentencia condicional. Las sentencias se indican utilizando llaves y el signo de porcentaje.

{% if nombre != "" %}
   <span class="navbar-text">Hola {{nombre}}</span>
{% else %}
    <a class="btn btn-info">Iniciar sesion</a>
{% endif %}

Esta página nos mostrará un mensaje de saludo si la variable del nombre no está vacía. En caso contrario nos mostrará el botón correspondiente al inicio de sesión.

En el caso de listas o tablas es necesario que recorramos las listas que contienen sus datos para armar con ellos los elementos.

<ul class="list-group">
   {% for valor in lista %}
      <li class="list-group-item"> {{ valor }}</li>
   {% endfor %}
</ul>

El template anterior recorre la variable lista y en cada iteración el contenido se guarda en la variable valor. Cada uno de estos valores se muestra como elementos dentro de una lista.

<div class="row">
    <div class="col-md-4">
      <img src="https://via.placeholder.com/330x250">
    </div>
    <div class="col-md-5">
      <p>{{texto}}</p>
    </div>
    <div class="col-md-3">
      <ul class="list-group">
      {% for valor in lista %}
        <li class="list-group-item"> {{ valor }}</li>
      {% endfor %}
      </ul>
      <a href="#" class="btn btn-primary btn-block btn-lg">${{precio}}</a>
    </div>
  </div>

El ejemplo anterior da como resultado la siguiente vista:

En el caso de atributos de objetos y diccionarios es posible acceder a ellos utilizando tanto el índice entre corchetes o con un punto.

<h5 class="card-title">{{producto['titulo']}}</h5>
 <p class="card-text">{{producto.texto}}</p>

Herencia de templates en Jinja2

Cuando creamos una página web es una práctica común crear una estructura de la página que será compartida por todas las vistas del sistema. Esta estructura generalmente incluye la cabecera y el pie de la página. La herencia de templates nos permite la creación de templates padres que podrán ser heredados por cada vista particular.

Esta herencia se logra a través de la creación de bloques. Los bloques de Jinja2 tienen un nombre único designado y pueden o no tener contenido. Al crear un template hijo e indicar la herencia los bloques definidos ocuparan el espacio asignado a dicho bloque en el template padre.

Se define el comienzo de un bloque con la palabra block seguida de su nombre y se indica su final con la palabra endblock.

<!DOCTYPE html>
<html lang="es">
  <head>
      <meta charset="UTF-8">
      {% block head %}
      <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
      <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
      <title>Titulo</title>
      {% endblock %}
  </head>
  <body>

{% block menu %}
<nav class="navbar navbar-expand-md navbar-dark bg-dark navbar-fixed-top">
  <a class="navbar-brand" href="#">Título</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>

  <div class="collapse navbar-collapse" id="navbarSupportedContent">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item active">
        <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">Enlace</a>
      </li>
    </ul>
  </div>

  <div class="navbar-collapse collapse w-100 order-3 dual-collapse2">
       <ul class="navbar-nav ml-auto">
         <li class="nav-item">
           {% if nombre != "" %}
             <span class="navbar-text">Hola {{nombre}}</span>
           {% else %}
             <a class="btn btn-info">Iniciar sesion</a>
           {% endif %}
         </li>
       </ul>
   </div>
</nav>
{% endblock %}
  <br/>
  {% block contenido %}
  {% endblock %}
  <br/>

  {% block pie %}
  <footer class="footer">
    <div class="container-fluid text-center text-md-left">
      <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-2">
            <ul>
              <li>
                <a href="#">Enlace 1</a>
              </li>
              <li>
                <a href="#">Enlace 2</a>
              </li>
              <li>
                <a href="#">Enlace 3</a>
              </li>
              <li>
                <a href="#">Enlace 4</a>
              </li>
          </ul>
        </div>
        <div class="col-md-6">
            <p>Aenean purus mi, laoreet et aliquam quis, imperdiet non risus. Nulla id luctus enim, convallis laoreet urna. Curabitur iaculis dictum ante, eget sodales massa elementum quis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tempor facilisis nisi at commodo.</p>
        </div>

        <div class="col-md-2"></div>
      </div>
    </div>
  </footer>
 {% endblock %}

En este template se encuentran definidos los siguientes bloques:

  • head: Contiene los enlaces a hojas de estilos, js, etc
  • menú: Contiene el menú de navegación
  • contenido: Bloque vacío
  • pie: Contiene el pie de la página

Al crear un template hijo es necesario especificar el template del cual hereda utilizando la sentencia extends seguida del nombre del archivo padre. Luego es posible definir el contenido de cada bloque pudiendo :

  • Heredar el contenido del padre sin modificarlo
  • Heredar el contenido pero permitir el agregado de contenido propio del hijo a través de la función super
  • Generar contenido nuevo a partir de los bloques vacíos en el template padre.
{% extends 'main.html'%}  {# Herencia #}
{% block head %}
    {{ super() }} {# Heredar el contenido del bloque padre #}
    <link rel="stylesheet" href="{{url_for('.static', filename='css/style.css')}}"> {# Nuevo contenido del bloque #}
{% endblock %}

{% block contenido %}
<div class="container">
  <div class="row">
    <div class="col-md-12">
        <h2>{{subtitulo}}</h2>
    </div>
  </div>
  <div class="row">
    <div class="col-md-4">
      <img src="https://via.placeholder.com/330x250">
    </div>
    <div class="col-md-5">
      <p>{{texto}}</p>
    </div>
    <div class="col-md-3">
      <ul class="list-group">
      {% for valor in lista %}
        <li class="list-group-item"> {{ valor }}</li>
      {% endfor %}
      </ul>
      <a href="#" class="btn btn-primary btn-block btn-lg">${{precio}}</a>
    </div>
  </div>
</div>
{% endblock %}

Qué sucede con la herencia de los diferentes bloques en el template hijo:

  • head: Al utilizar la función super se hereda el contenido del template padre pero permite agregar contenido nuevo
  • menú: Al no hacer referencia a este bloque se hereda directamente del template padre
  • contenido: El template hijo genera todo el contenido
  • pie: Al no hacer referencia a este bloque se hereda directamente del template padre
El menú es heredado del template padre al igual que el pie. El contenido central es definido por el template hijo. El estilo del pie ha sido agregado al contenido de cabecera del padre.

Macros en Jinja2

En ocasiones es necesario repetir un mismo formato de vista en una página para diferentes datos (lista de productos, campos de un formulario, etc). Los macros de Jinja2 nos permiten definir un estilo de función de vista que evitará la repetición de código.

Para definir un macro se debe utilizar la palabra macro seguido por su nombre y sus atributos y se finaliza con la palabra endmacro.

{% macro mostrar_producto(producto) %}
  <div class="card" style="width: 18rem;">
    <img class="card-img-top" src="https://via.placeholder.com/330x250" alt="Card image cap">
    <div class="card-body">
      <h5 class="card-title">{{producto['titulo']}}</h5>
      <p class="card-text">{{producto['texto']}}</p>
    </div>
    <div class="card-body">
      <a href="#" class="card-link">{{producto['precio']}}</a>
    </div>
  </div>
{% endmacro %}

En este ejemplo el macro mostrar_producto recibirá un atributo llamado producto con el cual generará una sección de código HTML.

Para llamar a un macro desde un template necesitamos importarlo primero. Importamos utilizando la palabra import seguido por el nombre del archivo correspondiente y un alias definido con la palabra as. Este alias es la manera como será referenciado el macro dentro del template.

{% extends 'main.html' %}
{% import 'macros.html' as macros %} {# Importar el macro #}
{% block contenido %}
<div class="container">
  <div class="row">
  	<div class="col-md-12">
  		<h2>Productos</h2>
  	</div>
  </div>
  <div class="row">
  	{% for producto in productos %}
  	<div class="col-md-3">
  	{{ macros.mostrar_producto(producto) }}  {# Llamar al macro #}
  	</div>
  	{% endfor %}
  </div>
</div>
{% endblock %}

En este ejemplo el template recorre una lista de productos y llama al macro mostrar_producto para cada uno de ellos.

Conclusiones

En este artículo se ha mostrado una introducción a las características más básicas de Jinja2 para la creación de templates. En futuros artículos se comentará en más profundidad las características y alcances de sus funciones, filtros y lógica. El código utilizado para este artículo puede encontrarse en nuestro repositorio.


¿Preguntas? ¿Comentarios?

Si tenés dudas, o querés dejarnos tus comentarios y consultas, sumate al grupo de Telegram de la comunidad JuncoTIC!
¡Te esperamos!


Andrea Navarro

- Ingeniera en Informática - Docente universitaria - Investigadora