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:
- 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. - Crear una plantilla
base_SECTION.html
para cada "sección" de tu sitio (por ej.base_photos.html
ybase_forum.html
). Esas plantillas heredan debase.html
e incluyen secciones específicas de estilo/diseño. - 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 queget_template()
. Esto es, el nombre de la plantilla es agregado a la variableTEMPLATE_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.