Template tag syntax
All template tags in django_component, like {% component %} or {% slot %}, and so on, support extra syntax that makes it possible to write components like in Vue or React (JSX).
Python expressions¤
New in version 0.146.0:
Python expressions allow you to evaluate Python code directly in template tag attributes by wrapping the expression in parentheses. This provides a Vue/React-like experience for writing component templates.
In the example above, (not editable) is evaluated as a Python expression, so if editable is False, then disabled will be True.
Basic syntax¤
Python expressions are simply Python code wrapped in parentheses:
{% component "button"
disabled=(not editable)
variant=(user.is_admin and 'danger' or 'primary')
size=(name.upper() if name else 'medium')
/ %}
Common use cases¤
Negating booleans:
Conditional expressions:
Method calls:
Complex expressions:
{% component "user_card"
is_active=(user.status == 'active')
score=(user.points + bonus_points)
/ %}
With filters:
You can apply Django filters to Python expressions:
In the example above, (user.name.upper()) is first evaluated as a Python expression, then the lower filter is applied to the result.
Best practices¤
-
Use for simple transformations: Python expressions are best for simple conditionals, negations, and basic operations.
-
Keep complex logic in Python: Complex business logic should still be in
get_template_data()or views, not in templates. -
Context access: Python expressions have access to the template context, so you can use any variables available in the context.
-
Performance: Expressions are cached for performance, so repeated evaluations of the same expression are fast.
-
Readability: Use Python expressions when they make the template more readable. If an expression becomes too complex, consider moving it to
get_template_data().
Literal lists and dictionaries¤
New in version 0.146.0:
You can pass literal lists and dictionaries directly in template tag attributes:
{% component "table"
headers=["Name", "Age", "Email"]
data=[
{"name": "John", "age": 30, "email": "john@example.com"},
{"name": "Jane", "age": 25, "email": "jane@example.com"},
]
/ %}
Lists and dictionaries can contain the same values as template tag attributes:
- Strings, numbers, booleans, and
None - Python expressions
- Template variables
- Nested lists and dictionaries
- Nested templates with
{{ }}and{% %}syntax
Each value can have filters applied to it.
Examples¤
Simple list:
List with template variables:
List with filters:
You can apply filters to individual list items:
You can also apply filters to the entire list:
Dictionary:
Nested structures:
{% component "table"
data=[
{
"name": "John",
"hobbies": ["reading", "coding"],
"meta": {"age": 30, "active": True}
}
]
/ %}
With Python expressions:
With filters on lists and dictionaries:
You can apply filters to entire lists or dictionaries:
{% component "table"
headers=["Name", "Age", "Email"]|slice:":2"
data=[{"name": "John"}, {"name": "Jane"}]|length
/ %}
You can also combine filters with Python expressions:
Self-closing tags¤
When you have a tag like {% component %} or {% slot %}, but it has no content, you can simply append a forward slash / at the end, instead of writing out the closing tags like {% endcomponent %} or {% endslot %}:
So this:
becomes
Spread operator¤
New in version 0.93:
Instead of passing keyword arguments one-by-one:
You can use a spread operator ...dict to apply key-value pairs from a dictionary:
This behaves similar to JSX's spread operator or Vue's v-bind.
Spread operators are treated as keyword arguments, which means that:
- Spread operators must come after positional arguments.
- You cannot use spread operators for positional-only arguments.
Other than that, you can use spread operators multiple times, and even put keyword arguments in-between or after them:
In a case of conflicts, the values added later (right-most) overwrite previous values.
Nested templates¤
New in version 0.93
When passing data around, sometimes you may need to do light transformations, like negating booleans or filtering lists.
Normally, what you would have to do is to define ALL the variables inside get_template_data(). But this can get messy if your components contain a lot of logic.
@register("calendar")
class Calendar(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"editable": kwargs["editable"],
"readonly": not kwargs["editable"],
"input_id": f"input-{kwargs['id']}",
"icon_id": f"icon-{kwargs['id']}",
...
}
Instead, template tags in django_components ({% component %}, {% slot %}, {% provide %}, etc) allow you to treat literal string values as templates:
{% component 'blog_post'
"As positional arg {# yay #}"
title="{{ person.first_name }} {{ person.last_name }}"
id="{% random_int 10 20 %}"
readonly="{{ editable|not }}"
author="John Wick {# TODO: parametrize #}"
/ %}
In the example above, the component receives:
- Positional argument
"As positional arg "(Comment omitted) title- passed asstr, e.g.John Doeid- passed asint, e.g.15readonly- passed asbool, e.g.Falseauthor- passed asstr, e.g.John Wick(Comment omitted)
This is inspired by django-cotton.
Passing data as string vs original values¤
In the example above, the kwarg id was passed as an integer, NOT a string.
When the string literal contains only a single template tag, with no extra text (and no extra whitespace), then the value is passed as the original type instead of a string.
Here, page is an integer:
Here, page is a string:
And same applies to the {{ }} variable tags:
Here, items is a list:
Here, items is a string:
Evaluating Python expressions in template¤
You can even go a step further and have a similar experience to Vue or React, where you can evaluate arbitrary code expressions:
Similar is possible with django-expr, which adds an expr tag and filter that you can use to evaluate Python expressions from within the template:
Note: Never use this feature to mix business logic and template logic. Business logic should still be in the view!
Flat dictionaries¤
New in version 0.74:
dict:key=value syntax, AKA defining a dictionary by its key-value pairs
Sometimes, a component may expect a dictionary as one of its inputs.
Most commonly, this happens when a component accepts a dictionary of HTML attributes (usually called attrs) to pass to the underlying template.
In such cases, we may want to define some HTML attributes statically, and other dynamically. But for that, we need to define this dictionary on Python side:
@register("my_comp")
class MyComp(Component):
template = """
{% component "other" attrs=attrs / %}
"""
def get_template_data(self, args, kwargs, slots, context):
attrs = {
"class": "pa-4 flex",
"data-some-id": kwargs["some_id"],
"@click.stop": "onClickHandler",
}
return {"attrs": attrs}
But as you can see in the case above, the event handler @click.stop and styling pa-4 flex are disconnected from the template. If the component grew in size and we moved the HTML to a separate file, we would have hard time reasoning about the component's template.
Luckily, there's a better way.
When we want to pass a dictionary to a component, we can define individual key-value pairs as component kwargs, so we can keep all the relevant information in the template. For that, we prefix the key with the name of the dict and :. So key class of input attrs becomes attrs:class. And our example becomes:
@register("my_comp")
class MyComp(Component):
template = """
{% component "other"
attrs:class="pa-4 flex"
attrs:data-some-id=some_id
attrs:@click.stop="onClickHandler"
/ %}
"""
def get_template_data(self, args, kwargs, slots, context):
return {"some_id": kwargs["some_id"]}
Sweet! Now all the relevant HTML is inside the template, and we can move it to a separate file with confidence:
{% component "other"
attrs:class="pa-4 flex"
attrs:data-some-id=some_id
attrs:@click.stop="onClickHandler"
/ %}
Note: It is NOT possible to define nested dictionaries, so
attrs:my_key:two=2would be interpreted as:
Special characters¤
New in version 0.71:
Keyword arguments can contain special characters # @ . - _, so keywords like so are still valid:
<body>
{% component "calendar" my-date="2015-06-19" @click.native=do_something #some_id=True / %}
</body>
These can then be accessed inside get_template_data so:
@register("calendar")
class Calendar(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"date": kwargs["my-date"],
"id": kwargs["#some_id"],
"on_click": kwargs["@click.native"]
}
Multiline tags¤
By default, Django expects a template tag to be defined on a single line.
However, this can become unwieldy if you have a component with a lot of inputs:
{% component "card" title="Joanne Arc" subtitle="Head of Kitty Relations" date_last_active="2024-09-03" ... %}
Instead, when you install django_components, it automatically configures Django to suport multi-line tags.
So we can rewrite the above as:
{% component "card"
title="Joanne Arc"
subtitle="Head of Kitty Relations"
date_last_active="2024-09-03"
...
%}
Much better!
To disable this behavior, set COMPONENTS.multiline_tag to False