Skip to content

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.

{% component "button" disabled=(not editable) %}

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¤

{% component "button" disabled=(not editable) / %}

Conditional expressions¤

{% component "button"
    variant=(user.is_admin and 'danger' or 'primary')
/ %}

Method calls¤

{% component "button" text=(name.upper()) / %}

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¤

  1. Use for simple transformations: Python expressions are best for simple conditionals, negations, and basic operations.

  2. Keep complex logic in Python: Complex business logic should still be in get_template_data() or views, not in templates.

  3. Context access: Python expressions have access to the template context, so you can use any variables available in the context.

  4. Performance: Expressions are cached for performance, so repeated evaluations of the same expression are fast.

  5. 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)

urls.py¤

from django.urls import path

from docs.examples.python_expressions.page import PythonExpressionsPage

urlpatterns = [
    path("examples/python_expressions", PythonExpressionsPage.as_view(), name="python_expressions"),
]