Templates

Template tag to set variables

Example usage:

{% set foo="bar" a=1 %}
...
Hello everyone, foo is {{ foo }} and a is {{ a }}.

Here’s the code:

from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def set(context, **kwargs):
    context.update(kwargs)
    return ''

Working block tag with arguments

Here’s an example of a working block tag. Usage is {% detail_link arg1=1 arg2=2 %}...{% end_detail_link %} and what ends up in the output is <a arg1="1" arg2="2" target="_blank">...</a>.

The code:

from django import template
from django.template.base import token_kwargs, TemplateSyntaxError
from django.utils.html import format_html, format_html_join
from django.utils.safestring import mark_safe

register = template.Library()


def do_detail_link(parser, token):
    """
    Block tag to help render links to detail pages consistently
    with an option to open in a new tab or window.

    {% detail_link href="xxxx" arg1="yyy" ... %} what to display {% end_detail_link %}

    is rendered as

    <a href="xxxx" arg1="yyy" ... target="_blank" %} what to display </a>

    This adds `target="_blank"` to open the link in a new tab or window.
    That's the main purpose of this block tag (and so we can disable that in
    one place, here, if we ever want to).  But you can override it by specifying
    another value for `target` if you want.
    """
    # This is called each time a detail_link tag is encountered while parsing
    # a template.

    # Split the contents of the tag itself
    args = token.split_contents()  # e.g. ["detail_link", "arg1='foo'", "arg2=bar"]
    tag_name = args.pop(0)

    # Parse out the arg1=foo arg2=bar ... arguments from the arg list into a dictionary.
    # kwargs will have "arg1" etc as keys, while the values will be
    # template thingies that can later be rendered using different contexts
    # to get their value at different times.
    kwargs = token_kwargs(args, parser)

    # If there are any args left, we have a problem; this tag only
    # accepts kwargs.
    if args:
        raise TemplateSyntaxError("%r only accepts named kwargs" % tag_name)

    # Open in new tab unless otherwise told (by setting target to something else).
    if 'target' not in kwargs:
        kwargs['target'] = parser.compile_filter('"_blank"')

    # Parse inside of block *until* we're looking at {% end_detail_link %},
    # then we don't care about end_detail_link, so discard it.
    # When we return, the parsing will then continue after our end tag.
    nodelist = parser.parse(('end_detail_link',))
    parser.delete_first_token()

    # Now return a node for the parsed template
    return DetailLinkNode(nodelist, tag_name, kwargs)


register.tag('detail_link', do_detail_link)


class DetailLinkNode(template.Node):
    """
    Stores info about one occurrence of detail_link in a template.

    See also `do_detail_link`.
    """
    def __init__(self, nodelist, tag_name, kwargs):
        self.nodelist = nodelist
        self.tag_name = tag_name
        self.kwargs = kwargs

    def render(self, context):
        """Turn this node into text using the given context."""

        # Start with the part inside the block
        innerds = self.nodelist.render(context)

        # Now work out the <a> wrapper.
        args = format_html_join(
            ' ',
            '{}="{}"',
            ((name, value.resolve(context)) for name, value in self.kwargs.items())
        )
        result = format_html(
            mark_safe("<a {}>{}</a>"),
            args,
            mark_safe(innerds)
        )
        return result

Debugging template syntax errors during tests

The normal error message when a view fails rendering a template during testing gives no clue where the error is.

You can get a better idea by temporarily editing your local Django installation. Find the file django/template/base.py. Around line 194 (in Django 1.8.x), in the __init__ method of the Template class, look for this code:

self.nodelist = engine.compile_string(template_string, origin)

and change it to:

try:
    self.nodelist = engine.compile_string(template_string, origin)
except TemplateSyntaxError:
    print("ERROR COMPILING %r" % origin.name)
    raise

TODO: would be nice to get a line number too (this just gives a filename, which is often enough in combination with the error message).