Skip to content

Writing custom templatetags

in order to use class-based templatetags, you will need to import and extend yak.tags.TemplateTag.

Most of the time your logic will fit in the render method of your TemplateTag class.

By default, any TemplateTag supports: - assignment: add as varname=value to the call in the template. This must always come last - local context: add with var1=val1 var2=val2 ... to the call int the template. This must come after regular (kw)args and before an optional as - accepting context: If your tag requires to be passed the template context, make sure your render method takes context as first argument (ie: render(self, context):)

Let's look at a few examples

Simple tag

from django.template import Library
from yak.tags import TemplateTag

register = Library()

class Hello(TemplateTag):
  def render(self):
    return 'Hello world'

# by default, the tag name will be the name of the TemplateTag class lower-cased
register.tag(Hello.as_tag())

# if you'd rather be explicit, you can also use this version
# register.tag('hello', Hello.as_tag())

Usage:

{% hello %}

For brevity, we will not be including the imports or register bit in further examples.

Accepting parameters

TemplateTag uses the same tools as Django's simple_tag to map method parameters to the actual templatetag. You can define as many required or optional arguments as you'd like on the render method.

class Hello(TemplateTag):
  def render(self, name='world'):
    return f'Hello {name}'

Usage:

{% hello %}{% hello name='you' %}

Inclusion tag

To make an inclusion_tag, you'll need to set a template_name attribute on your TemplateTag. The return value of render will then be expected to be a dict that will be passed to the template as context

class Hello(TemplateTag):
  template_name = 'hello.html'

  def render(self, name='world'):
    return {
      'name': name,
    }    

Usage:

{% hello %}{% hello name='you' %}

Block tag

When speaking of block tags this documentation refers to tags that encompass a block of template in the form of {% tag %} ...something.. {% endtag %}.

In order to declare that a TemplateTag is a block tag, you'll need to set the is_block_node attribute.

The node list of the inside of the tag will be available in the render method on the object itself as self.nodelist. Node that this node list is not rendered, so you will probably have to render it yourself. This means you will most likely need to take in context.

class Upper(TemplateTag):
  is_block_node = True

  def render(self, context):
    inner_text = self.nodelist.render(context)
    return inner_text.upper()

Usage:

{% upper %}hello world{% end_upper %}

Using "with" context

When speaking of "with" context or local context, this documentation refers to the variables passed to the TemplateTag using the with keyword in the template.

As, what is inside that "with" context may have to be interpreted (reference to other variables, etc), you will need to accept context in order to be able to use the local context.

In order to accept "with" context, you'll need to set the accepts_with_context to True

You can obtain the local context inside the render method by calling self.get_with_context and passing it a context (most likely the template context that you accepted)

class Hello(TemplateTag):
  accepts_with_context = True

  def render(self, context):
    local_context = self.get_with_context(context)
    return f'Hello {local_context.get("name", "world")}'

Usage:

{% hello %}{% hello with name='you' %}

Accepting arbitrary keywords as arguments

If you'd like to accept arbitrary keywords as arguments, this is also possible. By arbitrary keywords, we mean a non-quoted string that cannot be resolved (ie: not the name of a variable available in the context)

For this example, we'll accept the keyword upper. When the tag is called with upper ({% hello upper %}) the output of the TemplateTag will be upper-cased.

The detection of this keyword will have to happen in the clean_bits method. As it is not a suitable argument to be resolved, it should be "cleaned" (removed) from the bits as well.

bits are the raw parts of the template string as returned by Django's token.split_token()

class Hello(TemplateTag):

  upper = False

  def clean_bits(self, bits):
    # make sure to call super() as it is what takes care of `as` and `with`
    super().clean_bits(bits)

    if 'upper' in bits:
      this.upper = True
      pos = bite.index('upper')

      # this is the cleaning part where the unparsable bit is removed from the list
      del bits[pos]

  def render(self, name='world'):
    output = f'Hello {name}'
    if this.upper:
      output = output.upper()
    return output

Usage:

{% hello %}{% hello name='you' %}{% hello name='you' upper %}{% hello upper name='you' %}{% hello upper %}