Custom template tags
Template tags introduced by django-components, such as {% component %}
and {% slot %}
, offer additional features over the default Django template tags:
- Self-closing tags
{% mytag / %}
- Allowing the use of
:
,-
(and more) in keys - Spread operator
...
- Using template tags as inputs to other template tags
- Flat definition of dictionaries
attr:key=val
- Function-like input validation
You too can easily create custom template tags that use the above features.
Defining template tags with @template_tag
¤
The simplest way to create a custom template tag is using the template_tag
decorator. This decorator allows you to define a template tag by just writing a function that returns the rendered content.
from django.template import Context, Library
from django_components import BaseNode, template_tag
library = Library()
@template_tag(
library,
tag="mytag",
end_tag="endmytag",
allowed_flags=["required"]
)
def mytag(node: BaseNode, context: Context, name: str, **kwargs) -> str:
return f"Hello, {name}!"
This will allow you to use the tag in your templates like this:
{% mytag name="John" %}
{% endmytag %}
{# or with self-closing syntax #}
{% mytag name="John" / %}
{# or with flags #}
{% mytag name="John" required %}
{% endmytag %}
Parameters¤
The @template_tag
decorator accepts the following parameters:
library
: The Django template library to register the tag withtag
: The name of the template tag (e.g."mytag"
for{% mytag %}
)end_tag
: Optional. The name of the end tag (e.g."endmytag"
for{% endmytag %}
)allowed_flags
: Optional. List of flags that can be used with the tag (e.g.["required"]
for{% mytag required %}
)
Function signature¤
The function decorated with @template_tag
must accept at least two arguments:
node
: The node instance (we'll explain this in detail in the next section)context
: The Django template context
Any additional parameters in your function's signature define what inputs your template tag accepts. For example:
@template_tag(library, tag="greet")
def greet(
node: BaseNode,
context: Context,
name: str, # required positional argument
count: int = 1, # optional positional argument
*, # keyword-only arguments marker
msg: str, # required keyword argument
mode: str = "default", # optional keyword argument
) -> str:
return f"{msg}, {name}!" * count
This allows the tag to be used like:
{# All parameters #}
{% greet "John" count=2 msg="Hello" mode="custom" %}
{# Only required parameters #}
{% greet "John" msg="Hello" %}
{# Missing required parameter - will raise error #}
{% greet "John" %} {# Error: missing 'msg' #}
When you pass input to a template tag, it behaves the same way as if you passed the input to a function:
- If required parameters are missing, an error is raised
- If unexpected parameters are passed, an error is raised
To accept keys that are not valid Python identifiers (e.g. data-id
), or would conflict with Python keywords (e.g. is
), you can use the **kwargs
syntax:
@template_tag(library, tag="greet")
def greet(
node: BaseNode,
context: Context,
**kwargs,
) -> str:
attrs = kwargs.copy()
is_var = attrs.pop("is", None)
attrs_str = " ".join(f'{k}="{v}"' for k, v in attrs.items())
return mark_safe(f"""
<div {attrs_str}>
Hello, {is_var}!
</div>
""")
This allows you to use the tag like this:
Defining template tags with BaseNode
¤
For more control over your template tag, you can subclass BaseNode
directly instead of using the decorator. This gives you access to additional features like the node's internal state and parsing details.
from django_components import BaseNode
class GreetNode(BaseNode):
tag = "greet"
end_tag = "endgreet"
allowed_flags = ["required"]
def render(self, context: Context, name: str, **kwargs) -> str:
# Access node properties
if self.flags["required"]:
return f"Required greeting: Hello, {name}!"
return f"Hello, {name}!"
# Register the node
GreetNode.register(library)
Node properties¤
When using BaseNode
, you have access to several useful properties:
node_id
: A unique identifier for this node instanceflags
: Dictionary of flag values (e.g.{"required": True}
)params
: List of raw parameters passed to the tagnodelist
: The template nodes between the start and end tagsactive_flags
: List of flags that are currently set to True
This is what the node
parameter in the @template_tag
decorator gives you access to - it's the instance of the node class that was automatically created for your template tag.
Rendering content between tags¤
When your tag has an end tag, you can access and render the content between the tags using nodelist
:
class WrapNode(BaseNode):
tag = "wrap"
end_tag = "endwrap"
def render(self, context: Context, tag: str = "div", **attrs) -> str:
# Render the content between tags
inner = self.nodelist.render(context)
attrs_str = " ".join(f'{k}="{v}"' for k, v in attrs.items())
return f"<{tag} {attrs_str}>{inner}</{tag}>"
# Usage:
{% wrap tag="section" class="content" %}
Hello, world!
{% endwrap %}
Unregistering nodes¤
You can unregister a node from a library using the unregister
method:
This is particularly useful in testing when you want to clean up after registering temporary tags.