El libro de Django 1.0

4.7. Herencia de plantillas

Nuestras plantillas de ejemplo hasta el momento han sido fragmentos de HTML, pero en el mundo real, usarás el sistema de plantillas de Django para crear páginas HTML enteras. Esto conduce a un problema común del desarrollo web: ¿Cómo reducimos la duplicación y redundancia de las áreas comunes de las páginas, como por ejemplo, los paneles de navegación?

Una forma clásica de solucionar este problema es usar includes, insertando dentro de las páginas HTML a "incluir" una página dentro de otra. Es más, Django admite esta aproximación, con la etiqueta {% include %} anteriormente descrita. Pero la mejor forma de solucionar este problema con Django es usar una estrategia más elegante llamada herencia de plantillas.

En esencia, la herencia de plantillas te deja construir una plantilla base "esqueleto" que contenga todas las partes comunes de tu sitio y definir "bloques" que los hijos puedan sobreescribir.

Veamos un ejemplo de esto creando una plantilla completa para nuestra vista current_datetime, editando el archivo current_datetime.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>The current time</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    <p>It is now {{ current_date }}.</p>

    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

Esto se ve bien, pero ¿Qué sucede cuando queremos crear una plantilla para otra vista — digamos, ¿La vista hours_ahead del Capítulo 3? Si queremos hacer nuevamente una agradable, válida, y completa plantilla HTML, crearíamos algo como:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>Future time</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>

    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

Claramente, estaríamos duplicando una cantidad de código HTML. Imagina si tendríamos más sitios típicos, incluyendo barra de navegación, algunas hojas de estilo, quizás algo de JavaScript — terminaríamos poniendo todo tipo de HTML redundante en cada plantilla.

La solución a este problema usando includes en el servidor es sacar factor común de ambas plantillas y guardarlas en recortes de plantillas separados, que luego son incluidos en cada plantilla. Quizás quieras guardar la parte superior de la plantilla en un archivo llamado header.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>

Y quizás quieras guardar la parte inferior en un archivo llamado footer.html:

<hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

Con una estrategia basada en includes, la cabecera y la parte de abajo son fáciles. Es el medio el que queda desordenado. En este ejemplo, ambas páginas contienen un título — <h1>My helpful timestamp site</h1> — pero ese título no puede encajar dentro de header.html porque <title> en las dos páginas es diferente. Si incluimos <h1> en la cabecera, tendríamos que incluir <title>, lo cual no permitiría personalizar este en cada página. ¿Ves a dónde queremos llegar?

El sistema de herencia de Django soluciona estos problemas. Lo puedes pensar a esto como la versión contraria a la del lado del servidor. En vez de definir los pedazos que son comunes, defines los pedazos que son diferentes.

El primer paso es definir una plantilla base — un "esqueleto" de tu página que las plantillas hijas llenaran luego. Aquí hay una platilla para nuestro ejemplo actual:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    {% block content %}{% endblock %}
    {% block footer %}
    <hr>
    <p>Thanks for visiting my site.</p>
    {% endblock %}
</body>
</html>

Esta plantilla, que llamamos base.html, define un documento esqueleto HTML simple que usaremos para todas las páginas del sitio. Es trabajo de las plantillas hijas sobreescribir, agregar, dejar vacío el contenido de los bloques. (Si estás lo siguiendo desde casa, guarda este archivo en tu directorio de plantillas).

Usamos una etiqueta de plantilla aquí que no hemos visto antes: la etiqueta {% block %}. Todas las etiquetas {% block %} le indican al motor de plantillas que una plantilla hijo quizás sobreescriba esa porción de la plantilla.

Ahora que tenemos una plantilla base, podemos modificar nuestra plantilla existente current_datetime.html para usar esto:

{% extends "base.html" %}

{% block title %}The current time{% endblock %}

{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}

Como estamos en este tema, vamos a crear una plantilla para la vista hours_ahead del Capítulo 3. (Si lo estás siguiendo junto con el código, te dejamos cambiar hours_ahead para usar el sistema de plantilla). Así sería el resultado:

{% extends "base.html" %}

{% block title %}Future time{% endblock %}

{% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}

¿No es hermoso? Cada plantilla contiene sólo el código que es único para esa plantilla. No necesita redundancia. Si necesitas hacer un cambio grande en el diseño del sitio, sólo cambia base.html, y todas las otras plantillas reflejarán el efecto inmediatamente.

Veamos cómo trabaja. Cuando cargamos una plantilla current_datetime.html, el motor de plantillas ve la etiqueta {% extends %}, nota que esta plantilla es la hija de otra. El motor inmediatamente carga la plantilla padre — en este caso, base.html.

Hasta este punto, el motor de la plantilla nota las tres etiquetas {% block %} en base.html y reemplaza estos bloques por el contenido de la plantilla hija. Entonces, el título que definimos en {% block title %} será usado, así como {% block content %}.

Nota que desde la plantilla hija no definimos el bloque footer, entonces el sistema de plantillas usa el valor desde la plantilla padre. El contenido de la etiqueta {% block %} en la plantilla padre es siempre usado como un plan alternativo.

La herencia no afecta el funcionamiento del contexto, y puedes usar tantos niveles de herencia como necesites. Una forma común de utilizar la herencia es el siguiente enfoque de tres niveles:

  1. Crear una plantilla base.html que contenga el aspecto principal de tu sitio. Esto es lo que rara vez cambiará, si es que alguna vez cambia.
  2. Crear una plantilla base_SECTION.html para cada "sección" de tu sitio (por ej. base_photos.html y base_forum.html). Esas plantillas heredan de base.html e incluyen secciones específicas de estilo/diseño.
  3. Crear una plantilla individual para cada tipo de página, tales como páginas de formulario o galería de fotos. Estas plantillas heredan de la plantilla de la sección apropiada.

Esta aproximación maximiza la reutilización de código y hace fácil el agregado de elementos para compartir áreas, como puede ser un navegador de sección.

Aquí hay algunos consejos para el trabajo con herencia de plantillas:

  • Si usas {% extends %} en la plantilla, esta debe ser la primer etiqueta de esa plantilla. En otro caso, la herencia no funcionará.
  • Generalmente, cuanto más etiquetas {% block %} tengas en tus plantillas, mejor. Recuerda, las plantillas hijas no tienen que definir todos los bloques del padre, entonces puedes rellenar un número razonable de bloques por omisión, y luego definir sólo lo que necesiten las plantillas hijas. Es mejor tener más conexiones que menos.
  • Si encuentras código duplicado en un número de plantillas, esto probablemente signifique que debes mover ese código a un {% block %} en la plantilla padre.
  • Si necesitas obtener el contenido de un bloque desde la plantilla padre, la variable {{ block.super }} hará este truco. Esto es útil si quieres agregar contenido del bloque padre en vez de sobreescribirlo completamente.
  • No puedes definir múltiples etiquetas {% block %} con el mismo nombre en la misma plantilla. Esta limitación existe porque una etiqueta bloque trabaja en ambas direcciones. Esto es, una etiqueta bloque no sólo provee un agujero a llenar, sino que también define el contenido que llenará ese agujero en el padre. Si hay dos nombres similares de etiquetas {% block %} en una plantilla, el padre de esta plantilla puede no saber cual de los bloques usar.
  • El nombre de plantilla pasado a {% extends %} es cargado usando el mismo método que get_template(). Esto es, el nombre de la plantilla es agregado a la variable TEMPLATE_DIRS.
  • En la mayoría de los casos, el argumento para {% extends %} será un string, pero también puede ser una variable, si no sabes el nombre de la plantilla padre hasta la ejecución. Esto te permite hacer cosas divertidas, dinámicas.