Python Expressions¤
Python expressions allow you to evaluate Python code directly in templates by wrapping the expression in parentheses. This provides a Vue/React-like experience for writing component templates.
How it works¤
When you use parentheses () in a template tag attribute, the content inside is treated as a Python expression and evaluated in the template context.
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)
/ %}
Comparison with alternatives¤
Without Python expressions¤
Without Python expressions, you would need to compute values in get_template_data():
@register("button")
class Button(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"disabled": not kwargs["editable"],
"variant": "danger" if kwargs["user"].is_admin else "primary",
}
With Python expressions¤
With Python expressions, you can evaluate directly in the template:
{% component "button"
disabled=(not editable)
variant=(user.is_admin and 'danger' or 'primary')
/ %}
This keeps the logic closer to where it's used and reduces boilerplate in get_template_data().
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().
Definition¤
from typing import Optional
from django_components import Component, register, types
DESCRIPTION = "Evaluate Python expressions directly in template using parentheses."
@register("button")
class Button(Component):
class Kwargs:
text: str
disabled: bool = False
variant: str = "primary"
size: Optional[str] = None
def get_template_data(self, args, kwargs, slots, context):
# Determine button classes based on variant and size
classes = ["px-4", "py-2", "rounded", "font-medium"]
if kwargs.variant == "primary":
classes.extend(["bg-blue-600", "text-white", "hover:bg-blue-700"])
elif kwargs.variant == "secondary":
classes.extend(["bg-gray-200", "text-gray-800", "hover:bg-gray-300"])
elif kwargs.variant == "danger":
classes.extend(["bg-red-600", "text-white", "hover:bg-red-700"])
if kwargs.size == "small":
classes.extend(["text-sm", "px-2", "py-1"])
elif kwargs.size == "large":
classes.extend(["text-lg", "px-6", "py-3"])
if kwargs.disabled:
classes.extend(["opacity-50", "cursor-not-allowed"])
return {
"text": kwargs.text,
"disabled": kwargs.disabled,
"classes": " ".join(classes),
}
template: types.django_html = """
<button
type="button"
class="{{ classes }}"
{% if disabled %}disabled{% endif %}
>
{{ text }}
</button>
"""
@register("user_card")
class UserCard(Component):
class Kwargs:
username: str
is_active: bool
is_admin: bool
score: int
def get_template_data(self, args, kwargs, slots, context):
return {
"username": kwargs.username,
"is_active": kwargs.is_active,
"is_admin": kwargs.is_admin,
"score": kwargs.score,
"status": "active" if kwargs.is_active else "inactive",
"badge_color": "green" if kwargs.is_admin else "blue",
}
template: types.django_html = """
<div class="p-4 border rounded-lg">
<h3 class="font-bold">{{ username }}</h3>
<p class="text-sm text-gray-600">Status: {{ status }}</p>
<p class="text-sm">Score: {{ score }}</p>
<span class="px-2 py-1 rounded text-xs bg-{{ badge_color }}-100 text-{{ badge_color }}-800">
{% if is_admin %}Admin{% else %}User{% endif %}
</span>
</div>
"""
@register("search_input")
class SearchInput(Component):
class Kwargs:
placeholder: str = "Search..."
required: bool = False
min_length: Optional[int] = None
def get_template_data(self, args, kwargs, slots, context):
attrs = {}
if kwargs.required:
attrs["required"] = True
if kwargs.min_length:
attrs["minlength"] = kwargs.min_length
return {
"placeholder": kwargs.placeholder,
"attrs": attrs,
}
template: types.django_html = """
<input
type="search"
placeholder="{{ placeholder }}"
class="px-4 py-2 border rounded"
{% for key, value in attrs.items %}
{{ key }}="{{ value }}"
{% endfor %}
>
"""
Example¤
To see the component in action, you can set up a view and a URL pattern as shown below.
views.py¤
from dataclasses import dataclass
from typing import List
from django.http import HttpRequest, HttpResponse
from django_components import Component, types
@dataclass
class User:
username: str
status: str
role: str
points: int
is_admin: bool = False
def __post_init__(self):
# Derive is_admin from role if not explicitly set to True
if not self.is_admin:
self.is_admin = self.role == "admin"
@dataclass
class Item:
title: str
class PythonExpressionsPage(Component):
@dataclass
class Kwargs:
editable: bool
my_user: User
bonus_points: int
name: str
items: List[Item]
config: dict
def get_template_data(self, args, kwargs: Kwargs, slots, context):
return {
"editable": kwargs.editable,
"my_user": kwargs.my_user,
"bonus_points": kwargs.bonus_points,
"name": kwargs.name,
"items": kwargs.items,
"items_len": len(kwargs.items),
"config": kwargs.config,
}
class Media:
js = ("https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,container-queries",)
template: types.django_html = """
{% load component_tags %}
<html>
<head>
<title>Python Expressions Example</title>
</head>
<body class="bg-gray-100 p-8">
<div class="max-w-4xl mx-auto bg-white p-6 rounded-lg shadow-md">
<h1 class="text-3xl font-bold mb-4">
Python Expressions in Template Tags
</h1>
<p class="text-gray-600 mb-6">
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.
</p>
<div class="mb-8">
<h2 class="text-2xl font-semibold mb-4">
Basic Examples
</h2>
<div class="mb-8">
<h3 class="text-lg font-medium mb-2">
Negating a boolean
</h3>
<div class="text-sm text-gray-500 mb-2">
<pre
class="text-sm bg-gray-800 text-gray-100 p-2 rounded overflow-x-auto"
><code>{% verbatim %}{% component "button"
text="Submit"
disabled=(not editable)
/ %}{% endverbatim %}</code></pre>
</div>
<div class="mb-2">
Evaluates to <code class="text-sm text-gray-500 mb-2">True</code>
when <code class="text-sm text-gray-500 mb-2">editable</code>
is <code class="text-sm text-gray-500 mb-2">False</code>
</div>
<div class="space-x-2">
{% component "button" text="Submit" disabled=(not editable) / %}
</div>
</div>
<div class="mb-8">
<h3 class="text-lg font-medium mb-2">
Conditional expressions
</h3>
<div class="text-sm text-gray-500 mb-2">
<pre
class="text-sm bg-gray-800 text-gray-100 p-2 rounded overflow-x-auto"
><code>{% verbatim %}{% component "button"
text="Delete"
variant=(my_user.is_admin and 'danger' or 'primary')
/ %}{% endverbatim %}</code></pre>
</div>
<div class="mb-2">
Ternary-like expression that evaluates to <code class="text-sm text-gray-500 mb-2">'danger'</code>
when <code class="text-sm text-gray-500 mb-2">my_user.is_admin</code> is <code class="text-sm text-gray-500 mb-2">True</code>,
otherwise <code class="text-sm text-gray-500 mb-2">'primary'</code>
</div>
<div class="space-x-2">
{% component "button" text="Delete" variant=(my_user.is_admin and 'danger' or 'primary') / %}
</div>
</div>
<div class="mb-8">
<h3 class="text-lg font-medium mb-2">
Method calls and attribute access
</h3>
<div class="text-sm text-gray-500 mb-2">
<pre
class="text-sm bg-gray-800 text-gray-100 p-2 rounded overflow-x-auto"
><code>{% verbatim %}{% component "button"
text="Click Me"
size=(name.upper() if name else 'medium')
/ %}{% endverbatim %}</code></pre>
</div>
<div class="mb-2">
Uses string methods (<code class="text-sm text-gray-500 mb-2">.upper()</code>) and conditionals
to transform the <code class="text-sm text-gray-500 mb-2">name</code> value
</div>
<div class="space-x-2">
{% component "button" text="Click Me" size=(name.upper() if name else 'medium') / %}
</div>
</div>
</div>
<div class="mb-8">
<h2 class="text-2xl font-semibold mb-4">
Complex Examples
</h2>
<div class="mb-8">
<h3 class="text-lg font-medium mb-2">
Multiple Python expressions
</h3>
<div class="text-sm text-gray-500 mb-2">
<pre
class="text-sm bg-gray-800 text-gray-100 p-2 rounded overflow-x-auto"
><code>{% verbatim %}{% component "user_card"
username=my_user.username
is_active=(my_user.status == 'active')
is_admin=(my_user.role == 'admin')
score=(my_user.points + bonus_points)
/ %}{% endverbatim %}</code></pre>
</div>
<div class="mb-2">
Using Python expressions for multiple attributes with comparisons and arithmetic
</div>
<div class="space-y-2">
{% component "user_card"
username=my_user.username
is_active=(my_user.status == 'active')
is_admin=(my_user.role == 'admin')
score=(my_user.points + bonus_points)
/ %}
</div>
</div>
<div class="mb-8">
<h3 class="text-lg font-medium mb-2">
List and dictionary operations
</h3>
<div class="text-sm text-gray-500 mb-2">
<pre
class="text-sm bg-gray-800 text-gray-100 p-2 rounded overflow-x-auto"
><code>{% verbatim %}{% component "button"
text=(items[0].title if items else 'No Items')
disabled=(items_len == 0)
variant=(config.get('button_style', 'primary'))
/ %}{% endverbatim %}</code></pre>
</div>
<div class="mb-2">
Python expressions work with lists, dicts, and other data structures
</div>
<div class="space-x-2">
{% component "button"
text=(items[0].title if items else 'No Items')
disabled=(items_len == 0)
variant=(config.get('button_style', 'primary'))
/ %}
</div>
</div>
</div>
<div class="mb-8">
<h2 class="text-2xl font-semibold mb-4">
Comparison with Alternatives
</h2>
<div class="mb-8 p-4 bg-gray-50 rounded">
<h3 class="text-lg font-medium mb-2">
Without Python expressions (verbose)
</h3>
<p class="text-sm text-gray-600 mb-2">
You would need to compute values in <code>get_template_data()</code>:
</p>
<pre
class="text-xs bg-gray-800 text-gray-100 p-2 rounded overflow-x-auto"
><code>{% verbatim %}def get_template_data(self, args, kwargs, slots, context):
return {
"disabled": not kwargs["editable"],
"variant": "danger" if kwargs["my_user"].is_admin else "primary",
}{% endverbatim %}</code></pre>
</div>
<div class="mb-8 p-4 bg-gray-50 rounded">
<h3 class="text-lg font-medium mb-2">
With Python expressions (concise)
</h3>
<p class="text-sm text-gray-600 mb-2">
Evaluate directly in the template:
</p>
<pre
class="text-xs bg-gray-800 text-gray-100 p-2 rounded overflow-x-auto"
><code>{% verbatim %}{% component "button"
disabled=(not editable)
variant=(my_user.is_admin and 'danger' or 'primary')
/ %}{% endverbatim %}</code></pre>
</div>
</div>
<div class="mb-8">
<h2 class="text-2xl font-semibold mb-4">
Best Practices
</h2>
<ul class="list-disc list-inside space-y-2 text-gray-700">
<li>Use Python expressions for simple transformations and conditionals</li>
<li>Keep complex business logic in <code>get_template_data()</code> or views</li>
<li>Python expressions have access to the template context</li>
<li>Expressions are cached for performance</li>
</ul>
</div>
</div>
</body>
</html>
""" # noqa: E501
class View:
def get(self, request: HttpRequest) -> HttpResponse:
# Set up example context data
kwargs = PythonExpressionsPage.Kwargs(
editable=False,
my_user=User(username="johndoe", status="active", role="admin", points=150),
bonus_points=25,
name="large",
items=[Item(title="First Item"), Item(title="Second Item")],
config={"button_style": "secondary"},
)
return PythonExpressionsPage.render_to_response(request=request, kwargs=kwargs)