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.
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:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'plantillas'],
'APP_DIRS': True,
# ...
},
]<!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#
{% 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 %}<!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:
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{% 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:
{% 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:
{% include "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.
extends | include | |
|---|---|---|
| Quién manda | El padre pone la estructura | La plantilla de fuera |
| Cuántos por plantilla | Uno, y en la primera línea | Los que hagan falta, en cualquier sitio |
| Para qué | El esqueleto del sitio | Un 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:
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:
{% 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.