
* feat: on_render * docs: fix typos * refactor: fix linter errors * refactor: make `error` in on_render_after optional to fix benchmarks * refactor: benchmark attempt 2 * refactor: fix linter errors * refactor: fix formatting
18 KiB
When a component recieves input through {% component %}
tag,
or the Component.render()
or Component.render_to_response()
methods, you can define how the input is handled, and what variables will be available to the template, JavaScript and CSS.
Overview
Django Components offers three key methods for passing variables to different parts of your component:
get_template_data()
- Provides variables to your HTML templateget_js_data()
- Provides variables to your JavaScript codeget_css_data()
- Provides variables to your CSS styles
These methods let you pre-process inputs before they're used in rendering.
Each method handles the data independently - you can define different data for the template, JS, and CSS.
class ProfileCard(Component):
class Kwargs(NamedTuple):
user_id: int
show_details: bool
class Defaults:
show_details = True
def get_template_data(self, args, kwargs: Kwargs, slots, context):
user = User.objects.get(id=kwargs.user_id)
return {
"user": user,
"show_details": kwargs.show_details,
}
def get_js_data(self, args, kwargs: Kwargs, slots, context):
return {
"user_id": kwargs.user_id,
}
def get_css_data(self, args, kwargs: Kwargs, slots, context):
text_color = "red" if kwargs.show_details else "blue"
return {
"text_color": text_color,
}
Template variables
The get_template_data()
method is the primary way to provide variables to your HTML template. It receives the component inputs and returns a dictionary of data that will be available in the template.
If get_template_data()
returns None
, an empty dictionary will be used.
class ProfileCard(Component):
template_file = "profile_card.html"
class Kwargs(NamedTuple):
user_id: int
show_details: bool
def get_template_data(self, args, kwargs: Kwargs, slots, context):
user = User.objects.get(id=kwargs.user_id)
# Process and transform inputs
return {
"user": user,
"show_details": kwargs.show_details,
"user_joined_days": (timezone.now() - user.date_joined).days,
}
In your template, you can then use these variables:
<div class="profile-card">
<h2>{{ user.username }}</h2>
{% if show_details %}
<p>Member for {{ user_joined_days }} days</p>
<p>Email: {{ user.email }}</p>
{% endif %}
</div>
Legacy get_context_data()
The get_context_data()
method is the legacy way to provide variables to your HTML template. It serves the same purpose as get_template_data()
- it receives the component inputs and returns a dictionary of data that will be available in the template.
However, get_context_data()
has a few drawbacks:
- It does NOT receive the
slots
andcontext
parameters. - The
args
andkwargs
parameters are given as variadic*args
and**kwargs
parameters. As such, they cannot be typed.
class ProfileCard(Component):
template_file = "profile_card.html"
def get_context_data(self, user_id, show_details=False, *args, **kwargs):
user = User.objects.get(id=user_id)
return {
"user": user,
"show_details": show_details,
}
There is a slight difference between get_context_data()
and get_template_data()
when rendering a component with the {% component %}
tag.
For example if you have component that accepts kwarg date
:
class MyComponent(Component):
def get_context_data(self, date, *args, **kwargs):
return {
"date": date,
}
def get_template_data(self, args, kwargs, slots, context):
return {
"date": kwargs["date"],
}
The difference is that:
-
With
get_context_data()
, you can passdate
either as arg or kwarg:✅ {% component "my_component" date=some_date %} {% component "my_component" some_date %}
-
But with
get_template_data()
,date
MUST be passed as kwarg:✅ {% component "my_component" date=some_date %} ❌ {% component "my_component" some_date %}
!!! warning
[`get_template_data()`](../../../reference/api/#django_components.Component.get_template_data)
and [`get_context_data()`](../../../reference/api/#django_components.Component.get_context_data)
are mutually exclusive.
If both methods return non-empty dictionaries, an error will be raised.
!!! note
The `get_context_data()` method will be removed in v2.
Accessing component inputs
The component inputs are available in 3 ways:
Function arguments
The data methods receive the inputs as parameters directly.
class ProfileCard(Component):
# Access inputs directly as parameters
def get_template_data(self, args, kwargs, slots, context):
return {
"user_id": args[0],
"show_details": kwargs["show_details"],
}
!!! info
By default, the `args` parameter is a list, while `kwargs` and `slots` are dictionaries.
If you add typing to your component with
[`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
the respective inputs will be given as instances of these classes.
Learn more about [Component typing](../../fundamentals/typing_and_validation).
```py
class ProfileCard(Component):
class Args(NamedTuple):
user_id: int
class Kwargs(NamedTuple):
show_details: bool
# Access inputs directly as parameters
def get_template_data(self, args: Args, kwargs: Kwargs, slots, context):
return {
"user_id": args.user_id,
"show_details": kwargs.show_details,
}
```
args
, kwargs
, slots
properties
In other methods, you can access the inputs via
self.args
,
self.kwargs
,
and self.slots
properties:
class ProfileCard(Component):
def on_render_before(self, context: Context, template: Optional[Template]):
# Access inputs via self.args, self.kwargs, self.slots
self.args[0]
self.kwargs.get("show_details", False)
self.slots["footer"]
!!! info
These properties work the same way as `args`, `kwargs`, and `slots` parameters in the data methods:
By default, the `args` property is a list, while `kwargs` and `slots` are dictionaries.
If you add typing to your component with
[`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
the respective inputs will be given as instances of these classes.
Learn more about [Component typing](../../fundamentals/typing_and_validation).
```py
class ProfileCard(Component):
class Args(NamedTuple):
user_id: int
class Kwargs(NamedTuple):
show_details: bool
def get_template_data(self, args: Args, kwargs: Kwargs, slots, context):
return {
"user_id": self.args.user_id,
"show_details": self.kwargs.show_details,
}
```
input
property (low-level)
The previous two approaches allow you to access only the most important inputs.
There are additional settings that may be passed to components.
If you need to access these, you can use self.input
property
for a low-level access to all the inputs.
The input
property contains all the inputs passed to the component (instance of ComponentInput
).
This includes:
input.args
- List of positional argumentsinput.kwargs
- Dictionary of keyword argumentsinput.slots
- Dictionary of slots. Values are normalized toSlot
instancesinput.context
-Context
object that should be used to render the componentinput.type
- The type of the component (document, fragment)input.render_dependencies
- Whether to render dependencies (CSS, JS)
For more details, see Component inputs.
class ProfileCard(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access positional arguments
user_id = self.input.args[0] if self.input.args else None
# Access keyword arguments
show_details = self.input.kwargs.get("show_details", False)
# Render component differently depending on the type
if self.input.type == "fragment":
...
return {
"user_id": user_id,
"show_details": show_details,
}
!!! info
Unlike the parameters passed to the data methods, the `args`, `kwargs`, and `slots` in `self.input` property are always lists and dictionaries,
regardless of whether you added typing classes to your component (like [`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots)).
Default values
You can use Defaults
class to provide default values for your inputs.
These defaults will be applied either when:
- The input is not provided at rendering time
- The input is provided as
None
When you then access the inputs in your data methods, the default values will be already applied.
Read more about Component Defaults.
from django_components import Component, Default, register
@register("profile_card")
class ProfileCard(Component):
class Kwargs(NamedTuple):
show_details: bool
class Defaults:
show_details = True
# show_details will be set to True if `None` or missing
def get_template_data(self, args, kwargs: Kwargs, slots, context):
return {
"show_details": kwargs.show_details,
}
...
!!! warning
When typing your components with [`Args`](../../../reference/api/#django_components.Component.Args),
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
you may be inclined to define the defaults in the classes.
```py
class ProfileCard(Component):
class Kwargs(NamedTuple):
show_details: bool = True
```
This is **NOT recommended**, because:
- The defaults will NOT be applied to inputs when using [`self.input`](../../../reference/api/#django_components.Component.input) property.
- The defaults will NOT be applied when a field is given but set to `None`.
Instead, define the defaults in the [`Defaults`](../../../reference/api/#django_components.Component.Defaults) class.
Accessing Render API
All three data methods have access to the Component's Render API, which includes:
self.args
- The positional arguments for the current render callself.kwargs
- The keyword arguments for the current render callself.slots
- The slots for the current render callself.context
- The context for the current render callself.input
- All the component inputsself.id
- The unique ID for the current render callself.request
- The request objectself.context_processors_data
- Data from Django's context processorsself.inject()
- Inject data into the componentself.registry
- TheComponentRegistry
instanceself.registered_name
- The name under which the component was registeredself.outer_context
- The context outside of the{% component %}
tag
Type hints
Typing inputs
You can add type hints for the component inputs to ensure that the component logic is correct.
For this, define the Args
,
Kwargs
,
and Slots
classes,
and then add type hints to the data methods.
This will also validate the inputs at runtime, as the type classes will be instantiated with the inputs.
Read more about Component typing.
from typing import NamedTuple, Optional
from django_components import Component, SlotInput
class Button(Component):
class Args(NamedTuple):
name: str
class Kwargs(NamedTuple):
surname: str
maybe_var: Optional[int] = None # May be omitted
class Slots(NamedTuple):
my_slot: Optional[SlotInput] = None
footer: SlotInput
# Use the above classes to add type hints to the data method
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
# The parameters are instances of the classes we defined
assert isinstance(args, Button.Args)
assert isinstance(kwargs, Button.Kwargs)
assert isinstance(slots, Button.Slots)
!!! note
The data available via [`self.input`](../../../reference/api/#django_components.Component.input) property is NOT typed.
Typing data
In the same fashion, you can add types and validation for the data that should be RETURNED from each data method.
For this, set the TemplateData
,
JsData
,
and CssData
classes on the component class.
For each data method, you can either return a plain dictionary with the data, or an instance of the respective data class.
from typing import NamedTuple
from django_components import Component
class Button(Component):
class TemplateData(NamedTuple):
data1: str
data2: int
class JsData(NamedTuple):
js_data1: str
js_data2: int
class CssData(NamedTuple):
css_data1: str
css_data2: int
def get_template_data(self, args, kwargs, slots, context):
return Button.TemplateData(
data1="...",
data2=123,
)
def get_js_data(self, args, kwargs, slots, context):
return Button.JsData(
js_data1="...",
js_data2=123,
)
def get_css_data(self, args, kwargs, slots, context):
return Button.CssData(
css_data1="...",
css_data2=123,
)
Pass-through kwargs
It's best practice to explicitly define what args and kwargs a component accepts.
However, if you want a looser setup, you can easily write components that accept any number of kwargs, and pass them all to the template (similar to django-cotton).
To do that, simply return the kwargs
dictionary itself from get_template_data()
:
class MyComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return kwargs
You can do the same for get_js_data()
and get_css_data()
, if needed:
class MyComponent(Component):
def get_js_data(self, args, kwargs, slots, context):
return kwargs
def get_css_data(self, args, kwargs, slots, context):
return kwargs