Saltar al contenido
🐍 Curso de Python
/

Módulo 4 · Lección 18 de 45

Herencia de plantillas

Escribir la cabecera del sitio una vez, los tres niveles que aguantan un proyecto entero, y la diferencia entre heredar e incluir.

⏱ 14 min de lectura

Las dos plantillas que llevas escritas repiten el <!doctype html>, la etiqueta <head>, el juego de caracteres y el <body>. Con veinte páginas eso son veinte copias de lo mismo, y el día que haya que añadir una hoja de estilos o un menú hay que tocarlas todas.

La solución de Django no es incluir trozos: es heredar. Se escribe una plantilla base con la estructura del sitio y unos huecos con nombre, y cada página dice de cuál hereda y rellena los huecos que le interesan. Es la misma idea que la herencia de clases del módulo 1, aplicada a documentos.

La plantilla base#

Va en una carpeta del proyecto y no de ninguna aplicación, porque es de todas. Hay que decirle a Django dónde está, que es para lo que servía DIRS:

agenda/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'plantillas'],
        'APP_DIRS': True,
        # ...
    },
]
plantillas/base.html
<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <title>{% block titulo %}Agenda{% endblock %}</title>
</head>
<body>
  <nav>[navegación común]</nav>
  <main>
  {% block contenido %}
    <p>Sin contenido.</p>
  {% endblock %}
  </main>
  <footer>{% block pie %}Agenda de contactos{% endblock %}</footer>
</body>
</html>

Un {% block %} es un hueco con nombre, y lo que hay dentro es el valor por defecto: lo que sale si nadie lo sustituye. Eso importa: un bloque vacío y un bloque con contenido no son lo mismo, y aprovecharlo evita repetir en las páginas lo que casi siempre es igual.

Heredar#

contactos/templates/contactos/lista.html
{% extends "base.html" %}

{% block titulo %}Contactos — {{ block.super }}{% endblock %}

{% block contenido %}
  <h1>Contactos ({{ contactos|length }})</h1>
  <ul>
  {% for contacto in contactos %}
    <li>{{ contacto.nombre }} — {{ contacto.grupo|default:"sin grupo" }}</li>
  {% empty %}
    <li>No hay contactos.</li>
  {% endfor %}
  </ul>
{% endblock %}
Salida en pantalla
<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <title>Contactos — Agenda</title>
</head>
<body>
  <nav>[navegación común]</nav>
  <main>
  <h1>Contactos (5)</h1>
  <ul>
    <li>Ana Ruiz — Trabajo</li>
    ...
  </ul>
  </main>
  <footer>Agenda de contactos</footer>
</body>
</html>

La plantilla hija no tiene ni una etiqueta de estructura. Solo dice de quién hereda y rellena dos bloques; el tercero, pie, se ha quedado con su valor por defecto.

{{ block.super }} es lo que hace que el título sea Contactos — Agenda y no solo Contactos: inserta el contenido que tenía el bloque en la plantilla padre. Es el equivalente del super() de las clases, y sirve para ampliar en vez de sustituir. Se usa mucho en el título y en los bloques de estilos y guiones, donde la página quiere añadir lo suyo sin cargarse lo común.

Tres niveles#

Con veinte páginas, dos niveles se quedan cortos. La estructura que aguanta un proyecto entero es esta:

text
plantillas/
  base.html                      Nivel 1: el sitio entero
contactos/templates/contactos/
  base_contactos.html            Nivel 2: la sección
  lista.html                     Nivel 3: la página
  detalle.html
  formulario.html
contactos/templates/contactos/base_contactos.html
{% extends "base.html" %}

{% block contenido %}
  <div class="seccion-contactos">
    <aside>
      <a href="/contactos/">Todos</a>
      <a href="/contactos/nuevo/">Añadir</a>
    </aside>
    <section>
      {% block contenido_contactos %}{% endblock %}
    </section>
  </div>
{% endblock %}

Y las páginas heredan del nivel dos:

contactos/templates/contactos/lista.html
{% extends "contactos/base_contactos.html" %}

{% block contenido_contactos %}
  <h1>Contactos</h1>
  ...
{% endblock %}

El reparto es: el nivel uno es lo que comparten todas las páginas del sitio, el dos es lo que comparten las de una sección, y el tres es cada página. Más de tres niveles se vuelve difícil de seguir, porque para saber dónde acaba saliendo algo hay que abrir cuatro ficheros.

Incluir#

{% include %} mete otra plantilla en el punto donde está. Es lo contrario de heredar y sirve para otra cosa:

html
{% include "contactos/_fila.html" %}
contactos/templates/contactos/_fila.html
<li>{{ contacto.nombre }} — {{ contacto.grupo|default:"sin grupo" }}</li>

La convención de empezar el nombre por un guion bajo marca que es un trozo y no una página. No es obligatorio y se agradece al mirar la carpeta.

extendsinclude
Quién mandaEl padre pone la estructuraLa plantilla de fuera
Cuántos por plantillaUno, y en la primera líneaLos que hagan falta, en cualquier sitio
Para quéEl esqueleto del sitioUn trozo que se repite

La forma de elegir: si es "esta página es como las demás, pero con esto dentro", es herencia. Si es "este cacho aparece en varios sitios", es inclusión.

Qué ve la plantilla incluida#

Por defecto, todo el contexto de la que la incluye, que es cómodo y crea dependencias invisibles. Medido con una plantilla que escribe tres variables:

Salida en pantalla
1. include normal:     [titulo=Ficha] [contacto=Ana Ruiz] [extra=]
2. include con with:   [titulo=Ficha] [contacto=Ana Ruiz] [extra=puesto]
3. include con only:   [titulo=] [contacto=] [extra=puesto]

with añade variables. only corta el contexto heredado y deja solo lo que le pases.

Merece la pena usar only en los trozos reutilizables, aunque obligue a escribir más:

html
{% include "contactos/_fila.html" with contacto=c only %}

Sin only, el trozo funciona porque la plantilla de fuera tenía una variable con el nombre adecuado, y se rompe el día que se incluye desde otro sitio donde esa variable se llama distinto. Con only, lo que necesita está escrito en la propia línea del include, y eso es exactamente la diferencia entre una función con parámetros y una función que lee variables globales.