La lengua de plantillas de Django viene con una amplia variedad de etiquetas y filtros integrados diseñados para satisfacer las necesidades lógicas de presentación de tu aplicación. Sin embargo, es posible que necesites funcionalidad que no está cubierta por el conjunto básico de primitivas de plantilla. Puedes extender el motor de plantillas definiendo etiquetas y filtros personalizados utilizando Python, y luego hacerlos disponibles en tus plantillas mediante la {% carga %} etiqueta.
El lugar más común para especificar etiquetas y filtros de plantilla personalizados es dentro de una aplicación Django. Si se relacionan con una aplicación existente, tiene sentido agruparlas allí; en caso contrario, pueden agregarse a una nueva aplicación. Cuando una aplicación Django se agrega a INSTALLED_APPS, cualquier etiqueta que defina en la ubicación convencional descrita a continuación se hace automáticamente disponible para cargar dentro de las plantillas.
La aplicación debe contener un directorio templatetags, al mismo nivel que models.py, views.py, etc. Si este no existe, créalo - no te olvides del archivo __init__.py para asegurarte de que el directorio se trate como un paquete Python.
El servidor de desarrollo no se reiniciará automáticamente
Después de agregar el módulo templatetags, necesitarás reiniciar tu servidor antes de poder utilizar las etiquetas o filtros en plantillas.
Las etiquetas y filtros personalizados vivirán en un módulo dentro del directorio templatetags. El nombre del archivo del módulo es el nombre que usarás para cargar las etiquetas más tarde, así que ten cuidado al elegir un nombre que no entre en conflicto con etiquetas y filtros personalizados de otra aplicación.
Por ejemplo, si tus etiquetas/filtros personalizados están en un archivo llamado poll_extras.py, la estructura de tu aplicación podría ser como esta:
polls/
__init__.py
models.py
templatetags/
__init__.py
poll_extras.py
views.py
Y en tu plantilla utilizarías lo siguiente:
{% load poll_extras %}
La aplicación que contiene las etiquetas personalizadas debe estar en INSTALLED_APPS para que la etiqueta {% load %} funcione. Esto es una característica de seguridad: permite hospedar código Python para muchas bibliotecas de plantillas en una sola máquina host sin permitir acceso a todas ellas para cada instalación de Django.
No hay límite en cuanto al número de módulos que se pueden incluir en el paquete templatetags. Ten en cuenta que una declaración {% load %} cargará etiquetas/filtros con el nombre del módulo Python dado, no con el nombre de la aplicación.
Para que una biblioteca de etiquetas sea válida, el módulo debe contener una variable de nivel de módulo llamada register que es una instancia de template.Library, en la cual se registran todas las etiquetas y filtros. Así que, cerca de la parte superior de tu módulo, coloca lo siguiente:
from django import template
register = template.Library()
Alternativamente, los módulos de etiquetas de plantilla se pueden registrar mediante el argumento 'libraries' en DjangoTemplates. Esto es útil si deseas utilizar un etiqueta diferente del nombre del módulo de etiquetas de plantilla al cargar etiquetas de plantilla. También te permite registrar etiquetas sin instalar una aplicación.
Detrás del escenario
Para un montón de ejemplos, lee el código fuente de los filtros y etiquetas predeterminados de Django. Están en :source:`django/template/defaultfilters.py y :source:`django/template/defaulttags.py, respectivamente.
Para obtener más información sobre la etiqueta cargar, lee su documentación.
Filtros personalizados son funciones de Python que toman uno o dos argumentos:
El valor de la variable (entrada) – no necesariamente una cadena.
El valor del argumento – esto puede tener un valor por defecto, o simplemente omitirse.
Por ejemplo, en el filtro {{ var|foo:"bar" }}, el filtro foo se pasaría la variable var y el argumento "bar".
Dado que el lenguaje de plantillas no proporciona manejo de excepciones, cualquier excepción levantada desde un filtro de plantilla se expondrá como un error del servidor. Por lo tanto, las funciones de los filtros deben evitar levantar excepciones si hay un valor de fallback razonable para devolver. En caso de entrada que represente una clara falla en una plantilla, levantar una excepción puede ser mejor que el silencio, ya que oculta la falla.
Aquí tienes un ejemplo de definición de filtro:
def cut(value, arg):
"""Removes all values of arg from the given string"""
return value.replace(arg, "")
Y aquí tienes un ejemplo de cómo utilizaría ese filtro.
{{ somevariable|cut:"0" }}
La mayoría de los filtros no tienen argumentos. En este caso, omite el argumento en tu función:
def lower(value): # Only one argument.
"""Converts a string into all lowercase"""
return value.lower()
Una vez que hayas escrito la definición de tu filtro, debes registrarla con tu instancia Library, para hacerla disponible al lenguaje de plantillas de Django:
register.filter("cut", cut)
register.filter("lower", lower)
El método Library.filter() toma dos argumentos:
Los textos traducidos son:
La función de compilación – una función de Python (no el nombre de la función como una cadena).
Puedes usar register.filter() como decorador en lugar de:
@register.filter(name="cut")
def cut(value, arg):
return value.replace(arg, "")
@register.filter
def lower(value):
return value.lower()
Si omites el argumento name, como se muestra en el segundo ejemplo anterior, Django utilizará el nombre de la función como el nombre del filtro.
Finalmente, register.filter() también acepta tres argumentos de palabra clave: is_safe, needs_autoescape y expects_localtime. Estos argumentos se describen en filtros y escape automático y filtros y zonas horarias a continuación.
Si estás escribiendo un filtro de plantilla que solo espera una cadena como primer argumento, debes usar el decorador stringfilter. De esta manera, se convertirá el objeto a su valor de cadena antes de pasarla a tu función:
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
@register.filter
@stringfilter
def lower(value):
return value.lower()
De esta forma, podrás pasar, por ejemplo, un entero a este filtro y no causará un AttributeError (porque los enteros no tienen métodos lower()).
Al escribir un filtro personalizado, dedica algún tiempo a pensar en cómo interactuará el filtro con el comportamiento de escape automático de Django. Ten en cuenta que dos tipos de cadenas pueden pasar por dentro del código de la plantilla:
Cadenas crudas son las cadenas nativas de Python. A la salida se escapan si está en efecto el escape automático y se presentan sin cambios, de lo contrario.
Cadenas seguras son cadenas que han sido marcadas como seguras desde cualquier escape adicional en tiempo de salida. Se ha realizado cualquier necesaria escapada. Se utilizan comúnmente para la salida que contiene HTML crudo que se pretende interpretar tal cual en el lado del cliente.
Internamente, estas cadenas son del tipo SafeString. Puedes probarlas utilizando código como:
from django.utils.safestring import SafeString
if isinstance(value, SafeString):
# Do something with the "safe" string.
...
El código de los filtros de plantilla cae en una de dos situaciones:
Tu filtro no introduce caracteres HTML-ineficientes (<, >, ', " o &) en el resultado que ya no estaban presentes. En este caso, puedes dejar que Django se encargue de todo el manejo del escape automático por ti. Solo necesitas establecer la bandera is_safe a True cuando registres tu función de filtro, como se muestra a continuación:
@register.filter(is_safe=True)
def myfilter(value):
return value
Esta bandera le dice a Django que si una cadena «segura» se pasa a tu filtro, el resultado seguirá siendo «seguro» y si una cadena no segura se pasa en, Django escapará automáticamente si es necesario.
Puedes pensar en esto como significando «este filtro es seguro – no introduce ninguna posibilidad de HTML ineficiente».
La razón por la que is_safe es necesaria es porque hay muchas operaciones de cadenas normales que convertirán un objeto SafeData nuevamente en una cadena normal y, en lugar de intentar atraparlos todos, lo cual sería muy difícil, Django repara el daño después de que el filtro se haya completado.
Por ejemplo, supongamos que tienes un filtro que agrega la cadena xx al final de cualquier entrada. Dado que esto introduce ninguna cadena HTML peligrosa en el resultado (además de las que ya estaban presentes), debes marcar tu filtro con is_safe:
@register.filter(is_safe=True)
def add_xx(value):
return "%sxx" % value
Cuando se utiliza este filtro en una plantilla donde está habilitado el escape automático, Django escapará la salida siempre y cuando la entrada no esté marcada como «segura».
Por defecto, is_safe es False, y puedes omitirlo de cualquier filtro donde no se requiere.
Ten cuidado al decidir si tu filtro realmente deja cadenas seguras como seguras. Si estás eliminando caracteres, podrías dejar inadvertidamente etiquetas HTML o entidades desequilibradas en el resultado. Por ejemplo, eliminar un > del input podría convertir <a> en <a, lo que necesitaría escaparse en la salida para evitar causar problemas. De manera similar, eliminar un punto y coma (;) puede convertir & en &, que ya no es una entidad válida y por tanto necesita escaparse aún más. La mayoría de los casos no serán tan complicados, pero ten cuidado con cualquier problema como ese al revisar tu código.
Marcar un filtro is_safe convertirá el valor de retorno del filtro a una cadena. Si tu filtro debe devolver un valor booleano o otro valor no cadenoso, marcarlo is_safe probablemente tendrá consecuencias no deseadas (como convertir False en la cadena “False”).
Alternativamente, el código de tu filtro puede manejar manualmente cualquier escapada necesaria. Esto es necesario cuando estás introduciendo nuevo marcado HTML en el resultado. Quieres marcar la salida como segura para evitar que se escape aún más, por lo que debes manejar el input tú mismo.
Para marcar la salida como una cadena segura, utiliza django.utils.safestring.mark_safe().
Ten cuidado, aunque. Necesitas hacer más que solo marcar la salida como segura. Debes asegurarte de que realmente lo es, y lo que hagas depende de si está en efecto la escapada automática. La idea es escribir filtros que puedan operar en plantillas donde la escapada automática esté encendida o apagada para hacer las cosas más fáciles para tus autores de plantilla.
Para que tu filtro sepa el estado actual de la escapada automática, establece la bandera needs_autoescape en True cuando registres tu función de filtro. (Si no especificas esta bandera, es False por defecto). Esta bandera le dice a Django que tu función de filtro quiere ser pasada un argumento clave adicional llamado autoescape, que es True si la escapada automática está en efecto y False en caso contrario. Se recomienda establecer el valor predeterminado del parámetro autoescape en True, para que si llamas a la función desde código Python tenga escapada habilitada por defecto.
Por ejemplo, escribamos un filtro que destaque el primer carácter de una cadena:
from django import template
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
register = template.Library()
@register.filter(needs_autoescape=True)
def initial_letter_filter(text, autoescape=True):
first, other = text[0], text[1:]
if autoescape:
esc = conditional_escape
else:
esc = lambda x: x
result = "<strong>%s</strong>%s" % (esc(first), esc(other))
return mark_safe(result)
La bandera needs_autoescape y el argumento clave autoescape significan que nuestra función sabrá si la escapada automática está en efecto cuando se llame al filtro. Usamos autoescape para decidir si los datos de entrada necesitan pasar por django.utils.html.conditional_escape o no. (En el último caso, usamos la función identidad como la «función escape».) La función conditional_escape() es como escape(), excepto que solo escapa los datos de entrada que no son instancias de SafeData. Si se pasa una instancia de SafeData a conditional_escape(), los datos se devuelven sin cambios.
Finalmente, en el ejemplo anterior, recordamos marcar el resultado como seguro para que nuestro HTML se inserte directamente en la plantilla sin escapada adicional.
No hay necesidad de preocuparse por la bandera is_safe en este caso (aunque incluirla no haría nada malo). Cada vez que manejes manualmente las cuestiones de escape automático y devuelvas una cadena segura, la bandera is_safe tampoco cambiará nada de ninguna manera.
Advertencia
Evitar vulnerabilidades XSS al reutilizar filtros integrados
Los filtros integrados de Django tienen autoescape=True por defecto para obtener el comportamiento de escape automático adecuado y evitar una vulnerabilidad de script en sitios cruzados.
En versiones antiguas de Django, ten cuidado al reutilizar los filtros integrados de Django ya que autoescape se establece en None por defecto. Deberás pasar autoescape=True para obtener el escape automático.
Por ejemplo, si deseabas escribir un filtro personalizado llamado urlize_and_linebreaks que combinara los filtros urlize y linebreaksbr, el filtro tendría la siguiente apariencia:
from django.template.defaultfilters import linebreaksbr, urlize
@register.filter(needs_autoescape=True)
def urlize_and_linebreaks(text, autoescape=True):
return linebreaksbr(urlize(text, autoescape=autoescape), autoescape=autoescape)
Entonces:
{{ comment|urlize_and_linebreaks }}
sería equivalente a:
{{ comment|urlize|linebreaksbr }}
Si escribes un filtro personalizado que opera sobre objetos datetime, generalmente lo registrarás con la bandera expects_localtime establecida en True:
@register.filter(expects_localtime=True)
def businesshours(value):
try:
return 9 <= value.hour < 17
except AttributeError:
return ""
Cuando esta bandera está configurada, si el primer argumento de tu filtro es una fecha y hora consciente de zona horaria, Django la convertirá a la zona horaria actual antes de pasarla a tu filtro cuando sea apropiado, según las reglas para conversiones de zonas horarias en plantillas <time-zones-in-templates>.
Las etiquetas son más complejas que los filtros porque las etiquetas pueden hacer cualquier cosa. Django proporciona una serie de atajos que facilitan la escritura de la mayoría de tipos de etiquetas. Primero exploraremos esos atajos, luego explicaremos cómo escribir una etiqueta desde cero para aquellos casos en que los atajos no son lo suficientemente poderosos.
Muchas etiquetas de plantilla toman un número de argumentos – cadenas o variables de plantilla – y devuelven un resultado después de realizar algún procesamiento basado únicamente en los argumentos de entrada y alguna información externa. Por ejemplo, una etiqueta current_time podría aceptar una cadena de formato y devolver el tiempo como una cadena formateada según corresponda.
Para facilitar la creación de estas tipos de etiquetas, Django proporciona una función auxiliar llamada simple_tag. Esta función, que es un método de django.template.Library, toma una función que acepte cualquier número de argumentos, la envuelve en una función render y los demás bits necesarios mencionados anteriormente y la registra con el sistema de plantilla.
Nuestra función current_time podría escribirse así:
import datetime
from django import template
register = template.Library()
@register.simple_tag
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
Algunas cosas a tener en cuenta sobre la función auxiliar simple_tag:
Ya se ha verificado que se han proporcionado los argumentos necesarios, etc., por lo que no es necesario hacerlo nosotros.
Las comillas alrededor del argumento (si las hay) ya se han eliminado, por lo que recibimos una cadena plana.
Si el argumento era una variable de plantilla, nuestra función recibe el valor actual de la variable, no la variable misma.
A diferencia de otras utilidades de etiquetas, simple_tag pasa su salida a través de conditional_escape() si el contexto del template está en modo autoescape, para asegurar HTML correcto y protegerte contra vulnerabilidades XSS.
Si no se desea escapar adicionalmente, deberás utilizar mark_safe() si estás absolutamente seguro de que tu código no contiene vulnerabilidades XSS. Para construir pequeños snippets HTML, se recomienda fuertemente el uso de format_html() en lugar de mark_safe().
Si la etiqueta del template necesita acceder al contexto actual, puedes utilizar el argumento takes_context cuando registres tu etiqueta:
@register.simple_tag(takes_context=True)
def current_time(context, format_string):
timezone = context["timezone"]
return your_get_current_time_method(timezone, format_string)
Ten en cuenta que el primer argumento debe llamarse context.
Para más información sobre cómo funciona la opción takes_context, consulta la sección sobre etiquetas de inclusión.
Si necesitas renombrar tu etiqueta, puedes proporcionar un nombre personalizado para ella:
register.simple_tag(lambda x: x - 1, name="minusone")
@register.simple_tag(name="minustwo")
def some_function(value):
return value - 2
Las funciones simple_tag pueden aceptar cualquier número de argumentos posicionales o por palabra clave. Por ejemplo:
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs["warning"]
profile = kwargs["profile"]
...
return ...
Luego en el template, se pueden pasar cualquier número de argumentos separados por espacios a la etiqueta del template. Al igual que en Python, los valores para las palabras clave se establecen utilizando el signo de igualdad (»=») y deben proporcionarse después de los argumentos posicionales. Por ejemplo:
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
Es posible almacenar los resultados de la etiqueta en una variable del template en lugar de directamente mostrarla. Esto se hace utilizando el argumento as seguido del nombre de la variable. Al hacerlo, puedes mostrar el contenido tú mismo donde lo veas conveniente:
{% current_time "%Y-%m-%d %I:%M %p" as the_time %}
<p>The time is {{ the_time }}.</p>
When una sección de plantilla renderizada necesita ser pasada a un etiqueta personalizada, Django proporciona la función auxiliar simple_block_tag para lograr esto. De manera similar a simple_tag(), esta función acepta una función de etiqueta personalizada, pero con el argumento adicional content, que contiene el contenido renderizado tal como se define dentro de la etiqueta. Esto permite incorporar fácilmente secciones de plantilla dinámicas en etiquetas personalizadas.
Por ejemplo, una etiqueta de bloque personalizada que crea un gráfico podría verse así:
from django import template
from myapp.charts import render_chart
register = template.Library()
@register.simple_block_tag
def chart(content):
return render_chart(source=content)
El argumento content contiene todo lo que hay entre las etiquetas {% chart %} y {% endchart %}.
{% chart %}
digraph G {
label = "Chart for {{ request.user }}"
A -> {B C}
}
{% endchart %}
Si hay otras etiquetas de plantilla o variables dentro del bloque content, se renderizarán antes de ser pasadas a la función de la etiqueta. En el ejemplo anterior, request.user se resolverá cuando se llame a render_chart.
Las etiquetas de bloque se cierran con end{name} (por ejemplo, endchart). Esto puede personalizarse con el parámetro end_name:
@register.simple_block_tag(end_name="endofchart")
def chart(content):
return render_chart(source=content)
Lo que requeriría una definición de plantilla como esta:
{% chart %}
digraph G {
label = "Chart for {{ request.user }}"
A -> {B C}
}
{% endofchart %}
Algunas cosas a tener en cuenta sobre simple_block_tag:
El primer argumento debe llamarse content, y contendrá el contenido de la etiqueta de plantilla como cadena renderizada.
Las variables pasadas a la etiqueta no se incluyen en el contexto de rendering del contenido, tal como sucedería al usar la etiqueta {% with %}.
Justo como simple_tag, simple_block_tag:
Verifica la cantidad y calidad de los argumentos.
Elimina las comillas de los argumentos si es necesario.
Evita la salida según corresponda.
Soporta pasar takes_context=True en el momento de registro para acceder al contexto. Ten en cuenta que en este caso, el primer argumento a la función personalizada debe llamarse context, y content debe seguir.
Soporta renombrar la etiqueta pasando el argumento name cuando se registra.
Soporta la aceptación de cualquier número de argumentos posicionales o de palabra clave.
Soporta almacenar el resultado en una variable de plantilla utilizando la variante as.
Escape de contenido
simple_block_tag se comporta de manera similar a simple_tag en cuanto a la auto-escapada. Para detalles sobre escapar y seguridad, consulta simple_tag. Dado que el argumento content ya ha sido renderizado por Django, ya está escapado.
Considera una etiqueta de plantilla personalizada que genera una caja de mensajes que admite múltiples niveles de mensaje y contenido más allá de una frase simple. Esto podría implementarse utilizando un simple_block_tag de la siguiente manera:
from django import template
from django.utils.html import format_html
register = template.Library()
@register.simple_block_tag(takes_context=True)
def msgbox(context, content, level):
format_kwargs = {
"level": level.lower(),
"level_title": level.capitalize(),
"content": content,
"open": " open" if level.lower() == "error" else "",
"site": context.get("site", "My Site"),
}
result = """
<div class="msgbox {level}">
<details{open}>
<summary>
<strong>{level_title}</strong>: Please read for <i>{site}</i>
</summary>
<p>
{content}
</p>
</details>
</div>
"""
return format_html(result, **format_kwargs)
Cuando se combina con una vista mínima y un plantilla correspondiente, como se muestra aquí:
from django.shortcuts import render
def simpleblocktag_view(request):
return render(request, "test.html", context={"site": "Important Site"})
{% extends "base.html" %}
{% load testapptags %}
{% block content %}
{% msgbox level="error" %}
Please fix all errors. Further documentation can be found at
<a href="http://example.com">Docs</a>.
{% endmsgbox %}
{% msgbox level="info" %}
More information at: <a href="http://othersite.com">Other Site</a>/
{% endmsgbox %}
{% endblock %}
El siguiente HTML se produce como salida renderizada:
<div class="msgbox error">
<details open>
<summary>
<strong>Error</strong>: Please read for <i>Important Site</i>
</summary>
<p>
Please fix all errors. Further documentation can be found at
<a href="http://example.com">Docs</a>.
</p>
</details>
</div>
<div class="msgbox info">
<details>
<summary>
<strong>Info</strong>: Please read for <i>Important Site</i>
</summary>
<p>
More information at: <a href="http://othersite.com">Other Site</a>
</p>
</details>
</div>
Otra clase común de etiquetas de plantilla es el tipo que muestra algunos datos al renderizar otra plantilla. Por ejemplo, la interfaz administrativa de Django utiliza etiquetas de plantilla personalizadas para mostrar los botones en la parte inferior de las páginas del formulario «add/change». Los botones siempre tienen el mismo aspecto, pero los objetivos de los enlaces cambian dependiendo del objeto que se está editando – por lo tanto son un caso perfecto para utilizar una pequeña plantilla que se llena con detalles del objeto actual. (En el caso del administrador, esta es la etiqueta submit_row.)
Estos tipos de etiquetas se llaman «etiquetas de inclusión».
Escribiendo etiquetas de inclusión probablemente se demuestra mejor con un ejemplo. Vamos a escribir una etiqueta que imprima una lista de opciones para un objeto Poll dado, como el creado en la tutorials. La usaremos así:
{% show_results poll %}
…y la salida será algo como esto:
<ul>
<li>First choice</li>
<li>Second choice</li>
<li>Third choice</li>
</ul>
Primero, define la función que recibe el argumento y produce un diccionario de datos para el resultado. El punto importante aquí es que solo necesitamos devolver un diccionario, no nada más complejo. Esto se utilizará como contexto de plantilla para el fragmento de plantilla. Ejemplo:
def show_results(poll):
choices = poll.choice_set.all()
return {"choices": choices}
Crea luego el plantilla utilizada para renderizar la salida del etiqueta. Esta plantilla es una característica fija de la etiqueta: el escritor de la etiqueta la especifica, no el diseñador de plantillas. Siguiendo nuestro ejemplo, la plantilla es muy corta:
<ul>
{% for choice in choices %}
<li> {{ choice }} </li>
{% endfor %}
</ul>
Ahora, crea y registra la etiqueta de inclusión llamando al método inclusion_tag() en un objeto Library. Siguiendo nuestro ejemplo, si el template anterior está en un archivo llamado results.html en una carpeta que es buscada por el cargador de plantillas, se registraría la etiqueta de esta manera:
# Here, register is a django.template.Library instance, as before
@register.inclusion_tag("results.html")
def show_results(poll): ...
Alternativamente, es posible registrar la etiqueta de inclusión utilizando una instancia de django.template.Template.
from django.template.loader import get_template
t = get_template("results.html")
register.inclusion_tag(t)(show_results)
cuando se crea por primera vez la función.
A veces, tus etiquetas de inclusión pueden requerir un gran número de argumentos, lo que puede ser un dolor para los autores de plantillas pasar todos los argumentos y recordar su orden. Para solucionar esto, Django proporciona una opción takes_context para las etiquetas de inclusión. Si especificas takes_context al crear una etiqueta de plantilla, la etiqueta no tendrá argumentos requeridos, y la función Python subyacente tendrá un argumento – el contexto de la plantilla en el momento en que se llamó a la etiqueta.
Por ejemplo, supongamos que estás escribiendo una etiqueta de inclusión que siempre se utilizará en un contexto que contiene las variables home_link y home_title que apuntan hacia la página principal. Aquí está cómo luciría la función Python:
@register.inclusion_tag("link.html", takes_context=True)
def jump_link(context):
return {
"link": context["home_link"],
"title": context["home_title"],
}
Ten en cuenta que el primer parámetro de la función debe llamarse context.
En esa línea register.inclusion_tag(), especificamos takes_context=True y el nombre de la plantilla. Aquí está lo que podría ser la plantilla link.html:
Jump directly to <a href="{{ link }}">{{ title }}</a>.
Luego, cualquier vez que desees utilizar esa etiqueta personalizada, carga su biblioteca y llama a ella sin argumentos, de la siguiente manera:
{% jump_link %}
Ten en cuenta que cuando estás utilizando takes_context=True, no es necesario pasar argumentos al etiqueta de plantilla. Automáticamente obtiene acceso al contexto.
El parámetro takes_context tiene como valor por defecto False. Cuando se establece en True, el etiqueta recibe el objeto de contexto, tal y como se muestra en este ejemplo. Esa es la única diferencia entre este caso y el ejemplo anterior de inclusion_tag.
Las funciones inclusion_tag pueden aceptar cualquier número de argumentos posicionales o de palabra clave. Por ejemplo:
@register.inclusion_tag("my_template.html")
def my_tag(a, b, *args, **kwargs):
warning = kwargs["warning"]
profile = kwargs["profile"]
...
return ...
Luego en el template, se pueden pasar cualquier número de argumentos separados por espacios a la etiqueta del template. Al igual que en Python, los valores para las palabras clave se establecen utilizando el signo de igualdad (»=») y deben proporcionarse después de los argumentos posicionales. Por ejemplo:
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
A veces las características básicas para la creación de etiquetas de plantilla personalizadas no son suficientes. No te preocupes, Django te da acceso completo a los internos necesarios para construir una etiqueta de plantilla desde cero.
El sistema de plantillas funciona en un proceso de dos pasos: compilación y renderizado. Para definir una etiqueta de plantilla personalizada, especificas cómo funciona la compilación y cómo funciona el renderizado.
Cuando Django compila un template, divide el texto del template bruto en “”nodos””. Cada nodo es una instancia de django.template.Node y tiene un método render(). Un template compilado es una lista de objetos Node. Cuando llamas a render() en un objeto de plantilla compilada, la plantilla llama a render() en cada Node de su lista de nodos, con el contexto dado. Los resultados se concatenan todos juntos para formar la salida del template.
Así, para definir una plantilla de etiqueta personalizada, especificas cómo la etiqueta de plantilla bruta se convierte en un Node (la función de compilación) y qué hace el método render() del nodo.
Para cada etiqueta de plantilla que encuentra el parser de plantillas, llama a una función de Python con los contenidos de la etiqueta y el objeto del parser en sí mismo. Esta función es responsable de devolver un instancia de Node basada en los contenidos de la etiqueta.
Por ejemplo, escribamos una implementación completa de nuestra etiqueta de plantilla, {% current_time %}, que muestra la fecha/hora actual, formateada según el parámetro dado en la etiqueta, en sintaxis strftime(). Es una buena idea decidir la sintaxis de la etiqueta antes de nada. En nuestro caso, digamos que la etiqueta se utiliza de esta manera:
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
El parser para esta función debería obtener el parámetro y crear un objeto Node:
from django import template
def do_current_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, format_string = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires a single argument" % token.contents.split()[0]
)
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError(
"%r tag's argument should be in quotes" % tag_name
)
return CurrentTimeNode(format_string[1:-1])
Notas:
parser es el objeto del parser de plantillas. No lo necesitamos en este ejemplo.
token.contents es una cadena con los contenidos brutos de la etiqueta. En nuestro ejemplo, es 'current_time "%Y-%m-%d %I:%M %p"'.
La función token.split_contents() separa los argumentos en espacios mientras mantiene las cadenas citadas juntas. La forma más directa token.contents.split() no sería tan robusta, ya que dividiría todos los espacios, incluidos aquellos dentro de las cadenas citadas. Es una buena idea utilizar siempre token.split_contents().
Esta función es responsable de levantar django.template.TemplateSyntaxError, con mensajes útiles, para cualquier error de sintaxis.
Las excepciones de errores de sintaxis de plantilla utilizan la variable tag_name. No codifiques el nombre de la etiqueta en tus mensajes de error, ya que eso acopla el nombre de la etiqueta a tu función. token.contents.split()[0] siempre será el nombre de tu etiqueta – incluso cuando la etiqueta no tiene argumentos.
The function returns a CurrentTimeNode con todo lo que el nodo necesita saber sobre esta etiqueta. En este caso, pasa el argumento – "%Y-%m-%d %I:%M %p". Los comillas iniciales y finales del tag de plantilla se eliminan en format_string[1:-1].
La parsificación es muy baja nivel. Los desarrolladores de Django han experimentado con escribir pequeñas frameworks encima de este sistema de parsificación, utilizando técnicas como gramáticas EBNF, pero esos experimentos hicieron que el motor de plantillas fuera demasiado lento. Es baja nivel porque eso es lo más rápido.
El segundo paso en escribir etiquetas personalizadas es definir una clase Node que tenga un método render().
Continuando con el ejemplo anterior, necesitamos definir CurrentTimeNode:
import datetime
from django import template
class CurrentTimeNode(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
return datetime.datetime.now().strftime(self.format_string)
Notas:
__init__() obtiene la format_string de do_current_time(). Siempre pasa cualquier opción/parámetro/argumento a un Node a través de su __init__().
El método render() es donde realmente ocurre el trabajo.
render() debe fallar silenciosamente en general, particularmente en un entorno de producción. En algunos casos, sin embargo, especialmente si context.template.engine.debug es True, este método puede levantar una excepción para hacer más fácil la depuración. Por ejemplo, varias etiquetas de núcleo levantan django.template.TemplateSyntaxError si reciben el número o tipo incorrectos de argumentos.
Finalmente, esta desacoplamientos de compilación y renderizado resulta en un sistema de plantillas eficiente, porque una plantilla puede renderizar múltiples contextos sin tener que ser parsificada varias veces.
El texto traducido es el siguiente:
Si el método render() de tu etiqueta de plantilla almacena el resultado en una variable del contexto (en lugar de devolver el resultado como cadena), debe asegurarse de llamar a mark_safe() si es apropiado. Cuando la variable se renderiza finalmente, estará afectada por la configuración de auto-escapado que esté en vigor en ese momento, por lo que el contenido que debería estar seguro contra futuras escapadas necesita ser marcado como tal.
También, si tu etiqueta de plantilla crea un nuevo contexto para realizar alguna sub-renderización, establezca la atributo auto-escapado al valor del contexto actual. El método __init__ de la clase Context toma un parámetro llamado autoescape que puedes utilizar para este propósito. Por ejemplo:
from django.template import Context
def render(self, context):
# ...
new_context = Context({"var": obj}, autoescape=context.autoescape)
# ... Do something with new_context ...
Esto no es una situación muy común, pero es útil si estás renderizando una plantilla tú mismo. Por ejemplo:
def render(self, context):
t = context.template.engine.get_template("small_fragment.html")
return t.render(Context({"var": obj}, autoescape=context.autoescape))
Si hubiéramos omitido pasar el valor actual de context.autoescape a nuestro nuevo contexto en este ejemplo, los resultados habrían sido siempre automáticamente escapados, lo que puede no ser el comportamiento deseado si la etiqueta de plantilla se utiliza dentro de un bloque {% autoescape off %}.
Una vez que un nodo ha sido analizado, su método render puede ser llamado cualquier número de veces. Dado que Django a veces se ejecuta en entornos multi-hilos, un solo nodo puede estar renderizando con diferentes contextos al mismo tiempo en respuesta a dos solicitudes separadas. Por lo tanto, es importante asegurarse de que tus etiquetas de plantilla sean seguras para hilos.
Para asegurarte de que tus etiquetas de plantilla son seguras para hilos, nunca debes almacenar información de estado en el nodo mismo. Por ejemplo, Django proporciona una etiqueta de plantilla builtin cycle que cíclica entre una lista de cadenas dadas cada vez que se renderiza:
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
Una implementación ingenua de CycleNode podría verse algo así:
import itertools
from django import template
class CycleNode(template.Node):
def __init__(self, cyclevars):
self.cycle_iter = itertools.cycle(cyclevars)
def render(self, context):
return next(self.cycle_iter)
Pero, supongamos que tenemos dos plantillas renderizando el snippet de plantilla desde arriba al mismo tiempo:
Thread 1 realiza su primera iteración de bucle, CycleNode.render() devuelve “row1”
Thread 2 realiza su primera iteración de bucle, CycleNode.render() devuelve “row2”
Thread 1 realiza su segunda iteración de bucle, CycleNode.render() devuelve “row1”
Thread 2 realiza su segunda iteración de bucle, CycleNode.render() devuelve “row2”
El nodo Cycle está iterando, pero está iterando globalmente. Para Thread 1 y Thread 2, siempre devuelve el mismo valor. Esto no es lo que queremos!
Para abordar este problema, Django proporciona un render_context asociado con el context del template que se está renderizando actualmente. El render_context comporta como un diccionario de Python y debe utilizarse para almacenar el estado del Node entre las invocaciones del método render.
Vamos a refactorizar nuestra implementación de CycleNode para utilizar el render_context:
class CycleNode(template.Node):
def __init__(self, cyclevars):
self.cyclevars = cyclevars
def render(self, context):
if self not in context.render_context:
context.render_context[self] = itertools.cycle(self.cyclevars)
cycle_iter = context.render_context[self]
return next(cycle_iter)
Tenga en cuenta que es perfectamente seguro almacenar información global que no cambie durante la vida del Node como una atributo. En el caso de CycleNode, el argumento cyclevars no cambia después de que se instancie el Node, por lo que no necesitamos ponerlo en el render_context. Sin embargo, información de estado específica del template que se está renderizando actualmente, como la iteración actual del CycleNode, debe almacenarse en el render_context.
Nota
Nota cómo utilizamos self para escopar la información específica del nodo CycleNode dentro del render_context. Es posible que haya varios nodos CycleNodes en un template dado, por lo que debemos ser cuidadosos de no clobber el estado de otra nodo. La forma más fácil de hacer esto es siempre utilizar self como la clave para acceder al render_context. Si estás manteniendo varias variables de estado, haz que render_context[self] sea un diccionario.
Finalmente, registra el tag con la instancia de tu módulo Library, como se explica en <howto-writing-custom-template-tags> arriba. Ejemplo:
register.tag("current_time", do_current_time)
La función tag() toma dos argumentos:
El nombre de la etiqueta de plantilla — una cadena. Si se omite esto, se utilizará el nombre de la función de compilación.
La función de compilación – una función de Python (no el nombre de la función como una cadena).
Con la misma lógica de registro de filtros, también es posible utilizar esto como un decorador:
@register.tag(name="current_time")
def do_current_time(parser, token): ...
@register.tag
def shout(parser, token): ...
Si omites el argumento name, como en el segundo ejemplo anterior, Django utilizará el nombre de la función como el nombre del etiqueta.
Aunque puedes pasar cualquier número de argumentos a una etiqueta de plantilla utilizando token.split_contents(), los argumentos se desempaquenan como literales de cadena. Se requiere un poco más de trabajo para pasar contenido dinámico (una variable de plantilla) a una etiqueta de plantilla como argumento.
Mientras que los ejemplos anteriores han formateado la hora actual en una cadena y devuelto esa cadena, supongamos que deseas pasar un DateTimeField desde un objeto y tener el marcador de plantilla formato esa fecha-hora:
<p>This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.</p>
Inicialmente, token.split_contents() devolverá tres valores:
El nombre de la etiqueta format_time.
La traducción de los textos es la siguiente:
La cadena de formato '"%Y-%m-%d %I:%M %p"'. El valor devuelto por split_contents() incluirá las comillas que rodean los literales de cadena como este.
Ahora tu etiqueta debería comenzar a parecerse a esto:
from django import template
def do_format_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, date_to_be_formatted, format_string = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires exactly two arguments" % token.contents.split()[0]
)
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError(
"%r tag's argument should be in quotes" % tag_name
)
return FormatTimeNode(date_to_be_formatted, format_string[1:-1])
También debes cambiar el renderizador para obtener el contenido real de la propiedad date_updated del objeto blog_entry. Esto se puede lograr utilizando la clase Variable() en django.template.
Para utilizar la clase Variable, instántiala con el nombre de la variable a resolver, y luego llama a variable.resolve(context). Por ejemplo:
class FormatTimeNode(template.Node):
def __init__(self, date_to_be_formatted, format_string):
self.date_to_be_formatted = template.Variable(date_to_be_formatted)
self.format_string = format_string
def render(self, context):
try:
actual_date = self.date_to_be_formatted.resolve(context)
return actual_date.strftime(self.format_string)
except template.VariableDoesNotExist:
return ""
La resolución de variables lanzará una excepción VariableDoesNotExist si no puede resolver la cadena pasada a ella en el contexto actual de la página.
Los ejemplos anteriores devuelven un valor. Generalmente, es más flexible que las etiquetas de plantilla establezcan variables de plantilla en lugar de devolver valores. De esta manera, los autores de plantillas pueden reutilizar los valores creados por las etiquetas de plantilla.
Para establecer una variable en el contexto, utiliza la asignación de diccionario en el objeto de contexto en el método render(). Aquí tienes una versión actualizada de CurrentTimeNode que establece la variable de plantilla current_time en lugar de devolverla:
import datetime
from django import template
class CurrentTimeNode2(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
context["current_time"] = datetime.datetime.now().strftime(self.format_string)
return ""
Ten en cuenta que render() devuelve la cadena vacía. render() siempre debe devolver salida como string. Si todo lo que hace la etiqueta de plantilla es establecer una variable, render() debe devolver la cadena vacía.
La forma en que utilizarías esta nueva versión de la etiqueta es:
{% current_time "%Y-%m-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>
Ámbito de variables en contexto
Cualquier variable establecida en el contexto solo estará disponible en el mismo bloque del template en el que se asignó. Este comportamiento es intencional; proporciona un ámbito para las variables para que no entren en conflicto con el contexto de otros bloques.
Pero, hay un problema con CurrentTimeNode2: El nombre de la variable current_time está hard-codificado. Esto significa que deberás asegurarte de que tu template no utilice {{ current_time }} en ninguna otra parte, porque el {% current_time %} lo sobrescribirá sin más. Una solución más limpia es hacer que la etiqueta del template especifique el nombre de la variable de salida, como se muestra a continuación:
{% current_time "%Y-%m-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
Para lograr eso, deberás refactorizar tanto la función de compilación como la clase Node, como se muestra a continuación:
import re
class CurrentTimeNode3(template.Node):
def __init__(self, format_string, var_name):
self.format_string = format_string
self.var_name = var_name
def render(self, context):
context[self.var_name] = datetime.datetime.now().strftime(self.format_string)
return ""
def do_current_time(parser, token):
# This version uses a regular expression to parse tag contents.
try:
# Splitting by None == splitting by spaces.
tag_name, arg = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires arguments" % token.contents.split()[0]
)
m = re.search(r"(.*?) as (\w+)", arg)
if not m:
raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name)
format_string, var_name = m.groups()
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError(
"%r tag's argument should be in quotes" % tag_name
)
return CurrentTimeNode3(format_string[1:-1], var_name)
La diferencia aquí es que do_current_time() obtiene la cadena de formato y el nombre de la variable, pasando ambos a CurrentTimeNode3.
Finalmente, si solo necesitas tener una sintaxis simple para tu etiqueta del template personalizada que actualiza el contexto, considera utilizar la función corta simple_tag() , que admite asignar los resultados de la etiqueta a una variable del template.
Las etiquetas del template pueden trabajar en conjunto. Por ejemplo, la etiqueta estándar {% comment %} oculta todo hasta {% endcomment %}. Para crear una etiqueta del template como esta, utiliza parser.parse() en tu función de compilación.
Aquí tienes cómo podría implementarse una versión simplificada de la etiqueta {% comment %}
def do_comment(parser, token):
nodelist = parser.parse(("endcomment",))
parser.delete_first_token()
return CommentNode()
class CommentNode(template.Node):
def render(self, context):
return ""
Nota
La traducción de los textos es la siguiente:
parser.parse() toma una tupla de nombres de etiquetas de bloque “”para parsear hasta””. Devuelve una instancia de django.template.NodeList, que es una lista de todos los objetos Node que el parser encontró “”antes”” de encontrar cualquier de las etiquetas nombradas en la tupla.
En "nodelist = parser.parse(('endcomment',))" en el ejemplo anterior, nodelist es una lista de todos los nodos entre {% comment %} y {% endcomment %}, sin contar con {% comment %} y {% endcomment %} ellos mismos.
Después de que se llama a parser.parse(), el parser aún no ha «consumido» la etiqueta {% endcomment %}, por lo que el código necesita llamar explícitamente a parser.delete_first_token().
CommentNode.render() devuelve una cadena vacía. Cualquier cosa entre {% comment %} y {% endcomment %} se ignora.
En el ejemplo anterior, do_comment() descartó todo lo que había entre {% comment %} y {% endcomment %}. En lugar de eso, es posible hacer algo con el código entre las etiquetas de bloque.
Por ejemplo, aquí hay una etiqueta de plantilla personalizada, {% upper %}, que capitaliza todo lo que está entre ella misma y {% endupper %}.
Uso:
{% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %}
Como en el ejemplo anterior, usaremos parser.parse(). Pero esta vez, pasamos la lista resultante de nodelist a Node:
def do_upper(parser, token):
nodelist = parser.parse(("endupper",))
parser.delete_first_token()
return UpperNode(nodelist)
class UpperNode(template.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context):
output = self.nodelist.render(context)
return output.upper()
La traducción de los textos es la siguiente:
Para más ejemplos de renderizado complejo, consulte el código fuente de {% for %} en django/template/defaulttags.py y {% if %} en django/template/smartif.py.
may 31, 2026