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:
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.
Usage:
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:
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:
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:
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: