import sys
from dataclasses import dataclass
from inspect import signature
from types import MethodType
from typing import (
Any,
Callable,
ClassVar,
Dict,
Generator,
List,
Mapping,
NamedTuple,
Optional,
Tuple,
Type,
Union,
cast,
)
from weakref import ReferenceType, WeakValueDictionary, finalize
from django.forms.widgets import Media as MediaCls
from django.http import HttpRequest, HttpResponse
from django.template.base import NodeList, Parser, Template, Token
from django.template.context import Context, RequestContext
from django.template.loader_tags import BLOCK_CONTEXT_KEY, BlockContext
from django.test.signals import template_rendered
from django.views import View
from django_components.app_settings import ContextBehavior
from django_components.component_media import ComponentMediaInput, ComponentMediaMeta
from django_components.component_registry import ComponentRegistry
from django_components.component_registry import registry as registry_
from django_components.constants import COMP_ID_PREFIX
from django_components.context import _COMPONENT_CONTEXT_KEY, COMPONENT_IS_NESTED_KEY, make_isolated_context_copy
from django_components.dependencies import (
DependenciesStrategy,
cache_component_css,
cache_component_css_vars,
cache_component_js,
cache_component_js_vars,
insert_component_dependencies_comment,
)
from django_components.dependencies import render_dependencies as _render_dependencies
from django_components.dependencies import (
set_component_attrs_for_js_and_css,
)
from django_components.extension import (
OnComponentClassCreatedContext,
OnComponentClassDeletedContext,
OnComponentDataContext,
OnComponentInputContext,
OnComponentRenderedContext,
extensions,
)
from django_components.extensions.cache import ComponentCache
from django_components.extensions.debug_highlight import ComponentDebugHighlight
from django_components.extensions.defaults import ComponentDefaults
from django_components.extensions.view import ComponentView, ViewFn
from django_components.node import BaseNode
from django_components.perfutil.component import (
ComponentRenderer,
OnComponentRenderedResult,
component_context_cache,
component_post_render,
)
from django_components.perfutil.provide import register_provide_reference, unregister_provide_reference
from django_components.provide import get_injected_context_var
from django_components.slots import (
Slot,
SlotIsFilled,
SlotName,
SlotResult,
_is_extracting_fill,
normalize_slot_fills,
resolve_fills,
)
from django_components.template import cache_component_template_file, prepare_component_template
from django_components.util.context import gen_context_processors_data, snapshot_context
from django_components.util.exception import component_error_message
from django_components.util.logger import trace_component_msg
from django_components.util.misc import default, gen_id, hash_comp_cls, to_dict
from django_components.util.template_tag import TagAttr
from django_components.util.weakref import cached_ref
# TODO_REMOVE_IN_V1 - Users should use top-level import instead
# isort: off
from django_components.component_registry import AlreadyRegistered as AlreadyRegistered # NOQA
from django_components.component_registry import ComponentRegistry as ComponentRegistry # NOQA
from django_components.component_registry import NotRegistered as NotRegistered # NOQA
from django_components.component_registry import register as register # NOQA
from django_components.component_registry import registry as registry # NOQA
# isort: on
COMP_ONLY_FLAG = "only"
# NOTE: `ReferenceType` is NOT a generic pre-3.9
if sys.version_info >= (3, 9):
AllComponents = List[ReferenceType[Type["Component"]]]
CompHashMapping = WeakValueDictionary[str, Type["Component"]]
else:
AllComponents = List[ReferenceType]
CompHashMapping = WeakValueDictionary
OnRenderGenerator = Generator[
Optional[SlotResult],
Tuple[Optional[SlotResult], Optional[Exception]],
Optional[SlotResult],
]
"""
This is the signature of the [`Component.on_render()`](../api/#django_components.Component.on_render)
method if it yields (and thus returns a generator).
When `on_render()` is a generator then it:
- Yields a rendered template (string or `None`)
- Receives back a tuple of `(final_output, error)`.
The final output is the rendered template that now has all its children rendered too.
May be `None` if you yielded `None` earlier.
The error is `None` if the rendering was successful. Otherwise the error is set
and the output is `None`.
- At the end it may return a new string to override the final rendered output.
**Example:**
```py
from django_components import Component, OnRenderGenerator
class MyTable(Component):
def on_render(
self,
context: Context,
template: Optional[Template],
) -> OnRenderGenerator:
# Do something BEFORE rendering template
# Same as `Component.on_render_before()`
context["hello"] = "world"
# Yield rendered template to receive fully-rendered template or error
html, error = yield template.render(context)
# Do something AFTER rendering template, or post-process
# the rendered template.
# Same as `Component.on_render_after()`
return html + "
Hello
"
```
"""
# Keep track of all the Component classes created, so we can clean up after tests
ALL_COMPONENTS: AllComponents = []
def all_components() -> List[Type["Component"]]:
"""Get a list of all created [`Component`](../api#django_components.Component) classes."""
components: List[Type["Component"]] = []
for comp_ref in ALL_COMPONENTS:
comp = comp_ref()
if comp is not None:
components.append(comp)
return components
# NOTE: Initially, we fetched components by their registered name, but that didn't work
# for multiple registries and unregistered components.
#
# To have unique identifiers that works across registries, we rely
# on component class' module import path (e.g. `path.to.my.MyComponent`).
#
# But we also don't want to expose the module import paths to the outside world, as
# that information could be potentially exploited. So, instead, each component is
# associated with a hash that's derived from its module import path, ensuring uniqueness,
# consistency and privacy.
#
# E.g. `path.to.my.secret.MyComponent` -> `ab01f32`
#
# For easier debugging, we then prepend the hash with the component class name, so that
# we can easily identify the component class by its hash.
#
# E.g. `path.to.my.secret.MyComponent` -> `MyComponent_ab01f32`
#
# The associations are defined as WeakValue map, so deleted components can be garbage
# collected and automatically deleted from the dict.
comp_cls_id_mapping: CompHashMapping = WeakValueDictionary()
def get_component_by_class_id(comp_cls_id: str) -> Type["Component"]:
"""
Get a component class by its unique ID.
Each component class is associated with a unique hash that's derived from its module import path.
E.g. `path.to.my.secret.MyComponent` -> `MyComponent_ab01f32`
This hash is available under [`class_id`](../api#django_components.Component.class_id)
on the component class.
Raises `KeyError` if the component class is not found.
NOTE: This is mainly intended for extensions.
"""
return comp_cls_id_mapping[comp_cls_id]
# TODO_v1 - Remove with `Component.input`
@dataclass(frozen=True)
class ComponentInput:
"""
Deprecated. Will be removed in v1.
Object holding the inputs that were passed to [`Component.render()`](../api#django_components.Component.render)
or the [`{% component %}`](../template_tags#component) template tag.
This object is available only during render under [`Component.input`](../api#django_components.Component.input).
Read more about the [Render API](../../concepts/fundamentals/render_api).
"""
context: Context
"""
Django's [`Context`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context)
passed to `Component.render()`
"""
args: List
"""Positional arguments (as list) passed to `Component.render()`"""
kwargs: Dict
"""Keyword arguments (as dict) passed to `Component.render()`"""
slots: Dict[SlotName, Slot]
"""Slots (as dict) passed to `Component.render()`"""
deps_strategy: DependenciesStrategy
"""Dependencies strategy passed to `Component.render()`"""
# TODO_v1 - Remove, superseded by `deps_strategy`
type: DependenciesStrategy
"""Deprecated. Will be removed in v1. Use `deps_strategy` instead."""
# TODO_v1 - Remove, superseded by `deps_strategy`
render_dependencies: bool
"""Deprecated. Will be removed in v1. Use `deps_strategy="ignore"` instead."""
class ComponentVars(NamedTuple):
"""
Type for the variables available inside the component templates.
All variables here are scoped under `component_vars.`, so e.g. attribute
`kwargs` on this class is accessible inside the template as:
```django
{{ component_vars.kwargs }}
```
"""
args: Any
"""
The `args` argument as passed to
[`Component.get_template_data()`](../api/#django_components.Component.get_template_data).
This is the same [`Component.args`](../api/#django_components.Component.args)
that's available on the component instance.
If you defined the [`Component.Args`](../api/#django_components.Component.Args) class,
then the `args` property will return an instance of that class.
Otherwise, `args` will be a plain list.
**Example:**
With `Args` class:
```djc_py
from django_components import Component, register
@register("table")
class Table(Component):
class Args(NamedTuple):
page: int
per_page: int
template = '''
Table
Page: {{ component_vars.args.page }}
Per page: {{ component_vars.args.per_page }}
'''
```
Without `Args` class:
```djc_py
from django_components import Component, register
@register("table")
class Table(Component):
template = '''
Table
Page: {{ component_vars.args.0 }}
Per page: {{ component_vars.args.1 }}
'''
```
"""
kwargs: Any
"""
The `kwargs` argument as passed to
[`Component.get_template_data()`](../api/#django_components.Component.get_template_data).
This is the same [`Component.kwargs`](../api/#django_components.Component.kwargs)
that's available on the component instance.
If you defined the [`Component.Kwargs`](../api/#django_components.Component.Kwargs) class,
then the `kwargs` property will return an instance of that class.
Otherwise, `kwargs` will be a plain dict.
**Example:**
With `Kwargs` class:
```djc_py
from django_components import Component, register
@register("table")
class Table(Component):
class Kwargs(NamedTuple):
page: int
per_page: int
template = '''
Table
Page: {{ component_vars.kwargs.page }}
Per page: {{ component_vars.kwargs.per_page }}
'''
```
Without `Kwargs` class:
```djc_py
from django_components import Component, register
@register("table")
class Table(Component):
template = '''
Table
Page: {{ component_vars.kwargs.page }}
Per page: {{ component_vars.kwargs.per_page }}
'''
```
"""
slots: Any
"""
The `slots` argument as passed to
[`Component.get_template_data()`](../api/#django_components.Component.get_template_data).
This is the same [`Component.slots`](../api/#django_components.Component.slots)
that's available on the component instance.
If you defined the [`Component.Slots`](../api/#django_components.Component.Slots) class,
then the `slots` property will return an instance of that class.
Otherwise, `slots` will be a plain dict.
**Example:**
With `Slots` class:
```djc_py
from django_components import Component, SlotInput, register
@register("table")
class Table(Component):
class Slots(NamedTuple):
footer: SlotInput
template = '''
'''
```
"""
# TODO_v1 - Remove, superseded by `component_vars.slots`
is_filled: Dict[str, bool]
"""
Deprecated. Will be removed in v1. Use [`component_vars.slots`](../template_vars#django_components.component.ComponentVars.slots) instead.
Note that `component_vars.slots` no longer escapes the slot names.
Dictonary describing which component slots are filled (`True`) or are not (`False`).
New in version 0.70
Use as `{{ component_vars.is_filled }}`
Example:
```django
{# Render wrapping HTML only if the slot is defined #}
{% if component_vars.is_filled.my_slot %}
{% slot "my_slot" / %}
{% endif %}
```
This is equivalent to checking if a given key is among the slot fills:
```py
class MyTable(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"my_slot_filled": "my_slot" in slots
}
```
""" # noqa: E501
def _gen_component_id() -> str:
return COMP_ID_PREFIX + gen_id()
def _get_component_name(cls: Type["Component"], registered_name: Optional[str] = None) -> str:
return default(registered_name, cls.__name__)
# Descriptor to pass getting/setting of `template_name` onto `template_file`
class ComponentTemplateNameDescriptor:
def __get__(self, instance: Optional["Component"], cls: Type["Component"]) -> Any:
obj = default(instance, cls)
return obj.template_file # type: ignore[attr-defined]
def __set__(self, instance_or_cls: Union["Component", Type["Component"]], value: Any) -> None:
cls = instance_or_cls if isinstance(instance_or_cls, type) else instance_or_cls.__class__
cls.template_file = value
class ComponentMeta(ComponentMediaMeta):
def __new__(mcs, name: str, bases: Tuple[Type, ...], attrs: Dict) -> Type:
# If user set `template_name` on the class, we instead set it to `template_file`,
# because we want `template_name` to be the descriptor that proxies to `template_file`.
if "template_name" in attrs:
attrs["template_file"] = attrs.pop("template_name")
attrs["template_name"] = ComponentTemplateNameDescriptor()
cls = cast(Type["Component"], super().__new__(mcs, name, bases, attrs))
# If the component defined `template_file`, then associate this Component class
# with that template file path.
# This way, when we will be instantiating `Template` in order to load the Component's template,
# and its template_name matches this path, then we know that the template belongs to this Component class.
if "template_file" in attrs and attrs["template_file"]:
cache_component_template_file(cls)
# TODO_V1 - Remove. This is only for backwards compatibility with v0.139 and earlier,
# where `on_render_after` had 4 parameters.
on_render_after_sig = signature(cls.on_render_after)
if len(on_render_after_sig.parameters) == 4:
orig_on_render_after = cls.on_render_after
def on_render_after_wrapper(
self: Component,
context: Context,
template: Template,
result: str,
error: Optional[Exception],
) -> Optional[SlotResult]:
return orig_on_render_after(self, context, template, result) # type: ignore[call-arg]
cls.on_render_after = on_render_after_wrapper # type: ignore[assignment]
return cls
# This runs when a Component class is being deleted
def __del__(cls) -> None:
# Skip if `extensions` was deleted before this registry
if not extensions:
return
comp_cls = cast(Type["Component"], cls)
extensions.on_component_class_deleted(OnComponentClassDeletedContext(comp_cls))
# Internal data that are made available within the component's template
@dataclass
class ComponentContext:
component: "Component"
component_path: List[str]
template_name: Optional[str]
default_slot: Optional[str]
outer_context: Optional[Context]
# When we render a component, the root component, together with all the nested Components,
# shares this dictionary for storing callbacks that are called from within `component_post_render`.
# This is so that we can pass them all in when the root component is passed to `component_post_render`.
post_render_callbacks: Dict[str, Callable[[Optional[str], Optional[Exception]], OnComponentRenderedResult]]
class Component(metaclass=ComponentMeta):
# #####################################
# PUBLIC API (Configurable by users)
# #####################################
Args: ClassVar[Optional[Type]] = None
"""
Optional typing for positional arguments passed to the component.
If set and not `None`, then the `args` parameter of the data methods
([`get_template_data()`](../api#django_components.Component.get_template_data),
[`get_js_data()`](../api#django_components.Component.get_js_data),
[`get_css_data()`](../api#django_components.Component.get_css_data))
will be the instance of this class:
```py
from typing import NamedTuple
from django_components import Component
class Table(Component):
class Args(NamedTuple):
color: str
size: int
def get_template_data(self, args: Args, kwargs, slots, context):
assert isinstance(args, Table.Args)
return {
"color": args.color,
"size": args.size,
}
```
The constructor of this class MUST accept positional arguments:
```py
Args(*args)
```
As such, a good starting point is to set this field to a subclass of
[`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple).
Use `Args` to:
- Validate the input at runtime.
- Set type hints for the positional arguments for data methods like
[`get_template_data()`](../api#django_components.Component.get_template_data).
- Document the component inputs.
You can also use `Args` to validate the positional arguments for
[`Component.render()`](../api#django_components.Component.render):
```py
Table.render(
args=Table.Args(color="red", size=10),
)
```
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
"""
Kwargs: ClassVar[Optional[Type]] = None
"""
Optional typing for keyword arguments passed to the component.
If set and not `None`, then the `kwargs` parameter of the data methods
([`get_template_data()`](../api#django_components.Component.get_template_data),
[`get_js_data()`](../api#django_components.Component.get_js_data),
[`get_css_data()`](../api#django_components.Component.get_css_data))
will be the instance of this class:
```py
from typing import NamedTuple
from django_components import Component
class Table(Component):
class Kwargs(NamedTuple):
color: str
size: int
def get_template_data(self, args, kwargs: Kwargs, slots, context):
assert isinstance(kwargs, Table.Kwargs)
return {
"color": kwargs.color,
"size": kwargs.size,
}
```
The constructor of this class MUST accept keyword arguments:
```py
Kwargs(**kwargs)
```
As such, a good starting point is to set this field to a subclass of
[`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
or a [dataclass](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass).
Use `Kwargs` to:
- Validate the input at runtime.
- Set type hints for the keyword arguments for data methods like
[`get_template_data()`](../api#django_components.Component.get_template_data).
- Document the component inputs.
You can also use `Kwargs` to validate the keyword arguments for
[`Component.render()`](../api#django_components.Component.render):
```py
Table.render(
kwargs=Table.Kwargs(color="red", size=10),
)
```
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
"""
Slots: ClassVar[Optional[Type]] = None
"""
Optional typing for slots passed to the component.
If set and not `None`, then the `slots` parameter of the data methods
([`get_template_data()`](../api#django_components.Component.get_template_data),
[`get_js_data()`](../api#django_components.Component.get_js_data),
[`get_css_data()`](../api#django_components.Component.get_css_data))
will be the instance of this class:
```py
from typing import NamedTuple
from django_components import Component, Slot, SlotInput
class Table(Component):
class Slots(NamedTuple):
header: SlotInput
footer: Slot
def get_template_data(self, args, kwargs, slots: Slots, context):
assert isinstance(slots, Table.Slots)
return {
"header": slots.header,
"footer": slots.footer,
}
```
The constructor of this class MUST accept keyword arguments:
```py
Slots(**slots)
```
As such, a good starting point is to set this field to a subclass of
[`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
or a [dataclass](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass).
Use `Slots` to:
- Validate the input at runtime.
- Set type hints for the slots for data methods like
[`get_template_data()`](../api#django_components.Component.get_template_data).
- Document the component inputs.
You can also use `Slots` to validate the slots for
[`Component.render()`](../api#django_components.Component.render):
```py
Table.render(
slots=Table.Slots(
header="HELLO IM HEADER",
footer=Slot(lambda ctx: ...),
),
)
```
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
!!! info
Components can receive slots as strings, functions, or instances of [`Slot`](../api#django_components.Slot).
Internally these are all normalized to instances of [`Slot`](../api#django_components.Slot).
Therefore, the `slots` dictionary available in data methods (like
[`get_template_data()`](../api#django_components.Component.get_template_data))
will always be a dictionary of [`Slot`](../api#django_components.Slot) instances.
To correctly type this dictionary, you should set the fields of `Slots` to
[`Slot`](../api#django_components.Slot) or [`SlotInput`](../api#django_components.SlotInput):
[`SlotInput`](../api#django_components.SlotInput) is a union of `Slot`, string, and function types.
"""
template_file: ClassVar[Optional[str]] = None
"""
Filepath to the Django template associated with this component.
The filepath must be either:
- Relative to the directory where the Component's Python file is defined.
- Relative to one of the component directories, as set by
[`COMPONENTS.dirs`](../settings#django_components.app_settings.ComponentsSettings.dirs)
or
[`COMPONENTS.app_dirs`](../settings#django_components.app_settings.ComponentsSettings.app_dirs)
(e.g. `/components/`).
- Relative to the template directories, as set by Django's `TEMPLATES` setting (e.g. `/templates/`).
!!! warning
Only one of [`template_file`](../api#django_components.Component.template_file),
[`get_template_name`](../api#django_components.Component.get_template_name),
[`template`](../api#django_components.Component.template)
or [`get_template`](../api#django_components.Component.get_template) must be defined.
**Example:**
Assuming this project layout:
```txt
|- components/
|- table/
|- table.html
|- table.css
|- table.js
```
Template name can be either relative to the python file (`components/table/table.py`):
```python
class Table(Component):
template_file = "table.html"
```
Or relative to one of the directories in
[`COMPONENTS.dirs`](../settings#django_components.app_settings.ComponentsSettings.dirs)
or
[`COMPONENTS.app_dirs`](../settings#django_components.app_settings.ComponentsSettings.app_dirs)
(`components/`):
```python
class Table(Component):
template_file = "table/table.html"
```
"""
# NOTE: This attribute is managed by `ComponentTemplateNameDescriptor` that's set in the metaclass.
# But we still define it here for documenting and type hinting.
template_name: ClassVar[Optional[str]]
"""
Alias for [`template_file`](../api#django_components.Component.template_file).
For historical reasons, django-components used `template_name` to align with Django's
[TemplateView](https://docs.djangoproject.com/en/5.2/ref/class-based-views/base/#django.views.generic.base.TemplateView).
`template_file` was introduced to align with
[`js`](../api#django_components.Component.js)/[`js_file`](../api#django_components.Component.js_file)
and [`css`](../api#django_components.Component.css)/[`css_file`](../api#django_components.Component.css_file).
Setting and accessing this attribute is proxied to
[`template_file`](../api#django_components.Component.template_file).
"""
# TODO_v1 - Remove
def get_template_name(self, context: Context) -> Optional[str]:
"""
DEPRECATED: Use instead [`Component.template_file`](../api#django_components.Component.template_file),
[`Component.template`](../api#django_components.Component.template) or
[`Component.on_render()`](../api#django_components.Component.on_render).
Will be removed in v1.
Same as [`Component.template_file`](../api#django_components.Component.template_file),
but allows to dynamically resolve the template name at render time.
See [`Component.template_file`](../api#django_components.Component.template_file)
for more info and examples.
!!! warning
The context is not fully populated at the point when this method is called.
If you need to access the context, either use
[`Component.on_render_before()`](../api#django_components.Component.on_render_before) or
[`Component.on_render()`](../api#django_components.Component.on_render).
!!! warning
Only one of
[`template_file`](../api#django_components.Component.template_file),
[`get_template_name()`](../api#django_components.Component.get_template_name),
[`template`](../api#django_components.Component.template)
or
[`get_template()`](../api#django_components.Component.get_template)
must be defined.
Args:
context (Context): The Django template\
[`Context`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.Context)\
in which the component is rendered.
Returns:
Optional[str]: The filepath to the template.
"""
return None
template: Optional[str] = None
"""
Inlined Django template (as a plain string) associated with this component.
!!! warning
Only one of
[`template_file`](../api#django_components.Component.template_file),
[`template`](../api#django_components.Component.template),
[`get_template_name()`](../api#django_components.Component.get_template_name),
or
[`get_template()`](../api#django_components.Component.get_template)
must be defined.
**Example:**
```python
class Table(Component):
template = '''
{{ my_var }}
'''
```
**Syntax highlighting**
When using the inlined template, you can enable syntax highlighting
with `django_components.types.django_html`.
Learn more about [syntax highlighting](../../concepts/fundamentals/single_file_components/#syntax-highlighting).
```djc_py
from django_components import Component, types
class MyComponent(Component):
template: types.django_html = '''
{{ my_var }}
'''
```
"""
# TODO_v1 - Remove
def get_template(self, context: Context) -> Optional[Union[str, Template]]:
"""
DEPRECATED: Use instead [`Component.template_file`](../api#django_components.Component.template_file),
[`Component.template`](../api#django_components.Component.template) or
[`Component.on_render()`](../api#django_components.Component.on_render).
Will be removed in v1.
Same as [`Component.template`](../api#django_components.Component.template),
but allows to dynamically resolve the template at render time.
The template can be either plain string or
a [`Template`](https://docs.djangoproject.com/en/5.1/topics/templates/#template) instance.
See [`Component.template`](../api#django_components.Component.template) for more info and examples.
!!! warning
Only one of
[`template`](../api#django_components.Component.template)
[`template_file`](../api#django_components.Component.template_file),
[`get_template_name()`](../api#django_components.Component.get_template_name),
or
[`get_template()`](../api#django_components.Component.get_template)
must be defined.
!!! warning
The context is not fully populated at the point when this method is called.
If you need to access the context, either use
[`Component.on_render_before()`](../api#django_components.Component.on_render_before) or
[`Component.on_render()`](../api#django_components.Component.on_render).
Args:
context (Context): The Django template\
[`Context`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.Context)\
in which the component is rendered.
Returns:
Optional[Union[str, Template]]: The inlined Django template string or\
a [`Template`](https://docs.djangoproject.com/en/5.1/topics/templates/#template) instance.
"""
return None
# TODO_V2 - Remove this in v2
def get_context_data(self, *args: Any, **kwargs: Any) -> Optional[Mapping]:
"""
DEPRECATED: Use [`get_template_data()`](../api#django_components.Component.get_template_data) instead.
Will be removed in v2.
Use this method to define variables that will be available in the template.
Receives the args and kwargs as they were passed to the Component.
This method has access to the [Render API](../../concepts/fundamentals/render_api).
Read more about [Template variables](../../concepts/fundamentals/html_js_css_variables).
**Example:**
```py
class MyComponent(Component):
def get_context_data(self, name, *args, **kwargs):
return {
"name": name,
"id": self.id,
}
template = "Hello, {{ name }}!"
MyComponent.render(name="World")
```
!!! warning
`get_context_data()` and [`get_template_data()`](../api#django_components.Component.get_template_data)
are mutually exclusive.
If both methods return non-empty dictionaries, an error will be raised.
"""
return None
def get_template_data(self, args: Any, kwargs: Any, slots: Any, context: Context) -> Optional[Mapping]:
"""
Use this method to define variables that will be available in the template.
This method has access to the [Render API](../../concepts/fundamentals/render_api).
Read more about [Template variables](../../concepts/fundamentals/html_js_css_variables).
**Example:**
```py
class MyComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"name": kwargs["name"],
"id": self.id,
}
template = "Hello, {{ name }}!"
MyComponent.render(name="World")
```
**Args:**
- `args`: Positional arguments passed to the component.
- `kwargs`: Keyword arguments passed to the component.
- `slots`: Slots passed to the component.
- `context`: [`Context`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context)
used for rendering the component template.
**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](https://github.com/wrabit/django-cotton)).
To do that, simply return the `kwargs` dictionary itself from `get_template_data()`:
```py
class MyComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return kwargs
```
**Type hints:**
To get type hints for the `args`, `kwargs`, and `slots` parameters,
you can define the [`Args`](../api#django_components.Component.Args),
[`Kwargs`](../api#django_components.Component.Kwargs), and
[`Slots`](../api#django_components.Component.Slots) classes on the component class,
and then directly reference them in the function signature of `get_template_data()`.
When you set these classes, the `args`, `kwargs`, and `slots` parameters will be
given as instances of these (`args` instance of `Args`, etc).
When you omit these classes, or set them to `None`, then the `args`, `kwargs`, and `slots`
parameters will be given as plain lists / dictionaries, unmodified.
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
**Example:**
```py
from typing import NamedTuple
from django.template import Context
from django_components import Component, SlotInput
class MyComponent(Component):
class Args(NamedTuple):
color: str
class Kwargs(NamedTuple):
size: int
class Slots(NamedTuple):
footer: SlotInput
def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
assert isinstance(args, MyComponent.Args)
assert isinstance(kwargs, MyComponent.Kwargs)
assert isinstance(slots, MyComponent.Slots)
return {
"color": args.color,
"size": kwargs.size,
"id": self.id,
}
```
You can also add typing to the data returned from
[`get_template_data()`](../api#django_components.Component.get_template_data)
by defining the [`TemplateData`](../api#django_components.Component.TemplateData)
class on the component class.
When you set this class, you can return either the data as a plain dictionary,
or an instance of [`TemplateData`](../api#django_components.Component.TemplateData).
If you return plain dictionary, the data will be validated against the
[`TemplateData`](../api#django_components.Component.TemplateData) class
by instantiating it with the dictionary.
**Example:**
```py
class MyComponent(Component):
class TemplateData(NamedTuple):
color: str
size: int
def get_template_data(self, args, kwargs, slots, context):
return {
"color": kwargs["color"],
"size": kwargs["size"],
}
# or
return MyComponent.TemplateData(
color=kwargs["color"],
size=kwargs["size"],
)
```
!!! warning
`get_template_data()` and [`get_context_data()`](../api#django_components.Component.get_context_data)
are mutually exclusive.
If both methods return non-empty dictionaries, an error will be raised.
"""
return None
TemplateData: ClassVar[Optional[Type]] = None
"""
Optional typing for the data to be returned from
[`get_template_data()`](../api#django_components.Component.get_template_data).
If set and not `None`, then this class will be instantiated with the dictionary returned from
[`get_template_data()`](../api#django_components.Component.get_template_data) to validate the data.
The constructor of this class MUST accept keyword arguments:
```py
TemplateData(**template_data)
```
You can also return an instance of `TemplateData` directly from
[`get_template_data()`](../api#django_components.Component.get_template_data)
to get type hints:
```py
from typing import NamedTuple
from django_components import Component
class Table(Component):
class TemplateData(NamedTuple):
color: str
size: int
def get_template_data(self, args, kwargs, slots, context):
return Table.TemplateData(
color=kwargs["color"],
size=kwargs["size"],
)
```
A good starting point is to set this field to a subclass of
[`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
or a [dataclass](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass).
Use `TemplateData` to:
- Validate the data returned from
[`get_template_data()`](../api#django_components.Component.get_template_data) at runtime.
- Set type hints for this data.
- Document the component data.
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
!!! info
If you use a custom class for `TemplateData`, this class needs to be convertable to a dictionary.
You can implement either:
1. `_asdict()` method
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def _asdict(self):
return {'x': self.x, 'y': self.y}
```
2. Or make the class dict-like with `__iter__()` and `__getitem__()`
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def __iter__(self):
return iter([('x', self.x), ('y', self.y)])
def __getitem__(self, key):
return getattr(self, key)
```
"""
js: Optional[str] = None
"""
Main JS associated with this component inlined as string.
!!! warning
Only one of [`js`](../api#django_components.Component.js) or
[`js_file`](../api#django_components.Component.js_file) must be defined.
**Example:**
```py
class MyComponent(Component):
js = "console.log('Hello, World!');"
```
**Syntax highlighting**
When using the inlined template, you can enable syntax highlighting
with `django_components.types.js`.
Learn more about [syntax highlighting](../../concepts/fundamentals/single_file_components/#syntax-highlighting).
```djc_py
from django_components import Component, types
class MyComponent(Component):
js: types.js = '''
console.log('Hello, World!');
'''
```
"""
js_file: ClassVar[Optional[str]] = None
"""
Main JS associated with this component as file path.
The filepath must be either:
- Relative to the directory where the Component's Python file is defined.
- Relative to one of the component directories, as set by
[`COMPONENTS.dirs`](../settings#django_components.app_settings.ComponentsSettings.dirs)
or
[`COMPONENTS.app_dirs`](../settings#django_components.app_settings.ComponentsSettings.app_dirs)
(e.g. `/components/`).
- Relative to the staticfiles directories, as set by Django's `STATICFILES_DIRS` setting (e.g. `/static/`).
When you create a Component class with `js_file`, these will happen:
1. If the file path is relative to the directory where the component's Python file is,
the path is resolved.
2. The file is read and its contents is set to [`Component.js`](../api#django_components.Component.js).
!!! warning
Only one of [`js`](../api#django_components.Component.js) or
[`js_file`](../api#django_components.Component.js_file) must be defined.
**Example:**
```js title="path/to/script.js"
console.log('Hello, World!');
```
```py title="path/to/component.py"
class MyComponent(Component):
js_file = "path/to/script.js"
print(MyComponent.js)
# Output: console.log('Hello, World!');
```
"""
def get_js_data(self, args: Any, kwargs: Any, slots: Any, context: Context) -> Optional[Mapping]:
"""
Use this method to define variables that will be available from within the component's JavaScript code.
This method has access to the [Render API](../../concepts/fundamentals/render_api).
The data returned from this method will be serialized to JSON.
Read more about [JavaScript variables](../../concepts/fundamentals/html_js_css_variables).
**Example:**
```py
class MyComponent(Component):
def get_js_data(self, args, kwargs, slots, context):
return {
"name": kwargs["name"],
"id": self.id,
}
js = '''
$onLoad(({ name, id }) => {
console.log(name, id);
});
'''
MyComponent.render(name="World")
```
**Args:**
- `args`: Positional arguments passed to the component.
- `kwargs`: Keyword arguments passed to the component.
- `slots`: Slots passed to the component.
- `context`: [`Context`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context)
used for rendering the component template.
**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 JavaScript code.
To do that, simply return the `kwargs` dictionary itself from `get_js_data()`:
```py
class MyComponent(Component):
def get_js_data(self, args, kwargs, slots, context):
return kwargs
```
**Type hints:**
To get type hints for the `args`, `kwargs`, and `slots` parameters,
you can define the [`Args`](../api#django_components.Component.Args),
[`Kwargs`](../api#django_components.Component.Kwargs), and
[`Slots`](../api#django_components.Component.Slots) classes on the component class,
and then directly reference them in the function signature of `get_js_data()`.
When you set these classes, the `args`, `kwargs`, and `slots` parameters will be
given as instances of these (`args` instance of `Args`, etc).
When you omit these classes, or set them to `None`, then the `args`, `kwargs`, and `slots`
parameters will be given as plain lists / dictionaries, unmodified.
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
**Example:**
```py
from typing import NamedTuple
from django.template import Context
from django_components import Component, SlotInput
class MyComponent(Component):
class Args(NamedTuple):
color: str
class Kwargs(NamedTuple):
size: int
class Slots(NamedTuple):
footer: SlotInput
def get_js_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
assert isinstance(args, MyComponent.Args)
assert isinstance(kwargs, MyComponent.Kwargs)
assert isinstance(slots, MyComponent.Slots)
return {
"color": args.color,
"size": kwargs.size,
"id": self.id,
}
```
You can also add typing to the data returned from
[`get_js_data()`](../api#django_components.Component.get_js_data)
by defining the [`JsData`](../api#django_components.Component.JsData)
class on the component class.
When you set this class, you can return either the data as a plain dictionary,
or an instance of [`JsData`](../api#django_components.Component.JsData).
If you return plain dictionary, the data will be validated against the
[`JsData`](../api#django_components.Component.JsData) class
by instantiating it with the dictionary.
**Example:**
```py
class MyComponent(Component):
class JsData(NamedTuple):
color: str
size: int
def get_js_data(self, args, kwargs, slots, context):
return {
"color": kwargs["color"],
"size": kwargs["size"],
}
# or
return MyComponent.JsData(
color=kwargs["color"],
size=kwargs["size"],
)
```
"""
return None
JsData: ClassVar[Optional[Type]] = None
"""
Optional typing for the data to be returned from
[`get_js_data()`](../api#django_components.Component.get_js_data).
If set and not `None`, then this class will be instantiated with the dictionary returned from
[`get_js_data()`](../api#django_components.Component.get_js_data) to validate the data.
The constructor of this class MUST accept keyword arguments:
```py
JsData(**js_data)
```
You can also return an instance of `JsData` directly from
[`get_js_data()`](../api#django_components.Component.get_js_data)
to get type hints:
```py
from typing import NamedTuple
from django_components import Component
class Table(Component):
class JsData(NamedTuple):
color: str
size: int
def get_js_data(self, args, kwargs, slots, context):
return Table.JsData(
color=kwargs["color"],
size=kwargs["size"],
)
```
A good starting point is to set this field to a subclass of
[`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
or a [dataclass](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass).
Use `JsData` to:
- Validate the data returned from
[`get_js_data()`](../api#django_components.Component.get_js_data) at runtime.
- Set type hints for this data.
- Document the component data.
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
!!! info
If you use a custom class for `JsData`, this class needs to be convertable to a dictionary.
You can implement either:
1. `_asdict()` method
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def _asdict(self):
return {'x': self.x, 'y': self.y}
```
2. Or make the class dict-like with `__iter__()` and `__getitem__()`
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def __iter__(self):
return iter([('x', self.x), ('y', self.y)])
def __getitem__(self, key):
return getattr(self, key)
```
"""
css: Optional[str] = None
"""
Main CSS associated with this component inlined as string.
!!! warning
Only one of [`css`](../api#django_components.Component.css) or
[`css_file`](../api#django_components.Component.css_file) must be defined.
**Example:**
```py
class MyComponent(Component):
css = \"\"\"
.my-class {
color: red;
}
\"\"\"
```
**Syntax highlighting**
When using the inlined template, you can enable syntax highlighting
with `django_components.types.css`.
Learn more about [syntax highlighting](../../concepts/fundamentals/single_file_components/#syntax-highlighting).
```djc_py
from django_components import Component, types
class MyComponent(Component):
css: types.css = '''
.my-class {
color: red;
}
'''
```
"""
css_file: ClassVar[Optional[str]] = None
"""
Main CSS associated with this component as file path.
The filepath must be either:
- Relative to the directory where the Component's Python file is defined.
- Relative to one of the component directories, as set by
[`COMPONENTS.dirs`](../settings#django_components.app_settings.ComponentsSettings.dirs)
or
[`COMPONENTS.app_dirs`](../settings#django_components.app_settings.ComponentsSettings.app_dirs)
(e.g. `/components/`).
- Relative to the staticfiles directories, as set by Django's `STATICFILES_DIRS` setting (e.g. `/static/`).
When you create a Component class with `css_file`, these will happen:
1. If the file path is relative to the directory where the component's Python file is,
the path is resolved.
2. The file is read and its contents is set to [`Component.css`](../api#django_components.Component.css).
!!! warning
Only one of [`css`](../api#django_components.Component.css) or
[`css_file`](../api#django_components.Component.css_file) must be defined.
**Example:**
```css title="path/to/style.css"
.my-class {
color: red;
}
```
```py title="path/to/component.py"
class MyComponent(Component):
css_file = "path/to/style.css"
print(MyComponent.css)
# Output:
# .my-class {
# color: red;
# };
```
"""
def get_css_data(self, args: Any, kwargs: Any, slots: Any, context: Context) -> Optional[Mapping]:
"""
Use this method to define variables that will be available from within the component's CSS code.
This method has access to the [Render API](../../concepts/fundamentals/render_api).
The data returned from this method will be serialized to string.
Read more about [CSS variables](../../concepts/fundamentals/html_js_css_variables).
**Example:**
```py
class MyComponent(Component):
def get_css_data(self, args, kwargs, slots, context):
return {
"color": kwargs["color"],
}
css = '''
.my-class {
color: var(--color);
}
'''
MyComponent.render(color="red")
```
**Args:**
- `args`: Positional arguments passed to the component.
- `kwargs`: Keyword arguments passed to the component.
- `slots`: Slots passed to the component.
- `context`: [`Context`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context)
used for rendering the component template.
**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 CSS code.
To do that, simply return the `kwargs` dictionary itself from `get_css_data()`:
```py
class MyComponent(Component):
def get_css_data(self, args, kwargs, slots, context):
return kwargs
```
**Type hints:**
To get type hints for the `args`, `kwargs`, and `slots` parameters,
you can define the [`Args`](../api#django_components.Component.Args),
[`Kwargs`](../api#django_components.Component.Kwargs), and
[`Slots`](../api#django_components.Component.Slots) classes on the component class,
and then directly reference them in the function signature of `get_css_data()`.
When you set these classes, the `args`, `kwargs`, and `slots` parameters will be
given as instances of these (`args` instance of `Args`, etc).
When you omit these classes, or set them to `None`, then the `args`, `kwargs`, and `slots`
parameters will be given as plain lists / dictionaries, unmodified.
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
**Example:**
```py
from typing import NamedTuple
from django.template import Context
from django_components import Component, SlotInput
class MyComponent(Component):
class Args(NamedTuple):
color: str
class Kwargs(NamedTuple):
size: int
class Slots(NamedTuple):
footer: SlotInput
def get_css_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context):
assert isinstance(args, MyComponent.Args)
assert isinstance(kwargs, MyComponent.Kwargs)
assert isinstance(slots, MyComponent.Slots)
return {
"color": args.color,
"size": kwargs.size,
}
```
You can also add typing to the data returned from
[`get_css_data()`](../api#django_components.Component.get_css_data)
by defining the [`CssData`](../api#django_components.Component.CssData)
class on the component class.
When you set this class, you can return either the data as a plain dictionary,
or an instance of [`CssData`](../api#django_components.Component.CssData).
If you return plain dictionary, the data will be validated against the
[`CssData`](../api#django_components.Component.CssData) class
by instantiating it with the dictionary.
**Example:**
```py
class MyComponent(Component):
class CssData(NamedTuple):
color: str
size: int
def get_css_data(self, args, kwargs, slots, context):
return {
"color": kwargs["color"],
"size": kwargs["size"],
}
# or
return MyComponent.CssData(
color=kwargs["color"],
size=kwargs["size"],
)
```
"""
return None
CssData: ClassVar[Optional[Type]] = None
"""
Optional typing for the data to be returned from
[`get_css_data()`](../api#django_components.Component.get_css_data).
If set and not `None`, then this class will be instantiated with the dictionary returned from
[`get_css_data()`](../api#django_components.Component.get_css_data) to validate the data.
The constructor of this class MUST accept keyword arguments:
```py
CssData(**css_data)
```
You can also return an instance of `CssData` directly from
[`get_css_data()`](../api#django_components.Component.get_css_data)
to get type hints:
```py
from typing import NamedTuple
from django_components import Component
class Table(Component):
class CssData(NamedTuple):
color: str
size: int
def get_css_data(self, args, kwargs, slots, context):
return Table.CssData(
color=kwargs["color"],
size=kwargs["size"],
)
```
A good starting point is to set this field to a subclass of
[`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
or a [dataclass](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass).
Use `CssData` to:
- Validate the data returned from
[`get_css_data()`](../api#django_components.Component.get_css_data) at runtime.
- Set type hints for this data.
- Document the component data.
Read more on [Typing and validation](../../concepts/fundamentals/typing_and_validation).
!!! info
If you use a custom class for `CssData`, this class needs to be convertable to a dictionary.
You can implement either:
1. `_asdict()` method
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def _asdict(self):
return {'x': self.x, 'y': self.y}
```
2. Or make the class dict-like with `__iter__()` and `__getitem__()`
```py
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def __iter__(self):
return iter([('x', self.x), ('y', self.y)])
def __getitem__(self, key):
return getattr(self, key)
```
"""
media: Optional[MediaCls] = None
"""
Normalized definition of JS and CSS media files associated with this component.
`None` if [`Component.Media`](../api#django_components.Component.Media) is not defined.
This field is generated from [`Component.media_class`](../api#django_components.Component.media_class).
Read more on [Accessing component's HTML / JS / CSS](../../concepts/fundamentals/defining_js_css_html_files/#accessing-components-media-files).
**Example:**
```py
class MyComponent(Component):
class Media:
js = "path/to/script.js"
css = "path/to/style.css"
print(MyComponent.media)
# Output:
#
#
```
""" # noqa: E501
media_class: ClassVar[Type[MediaCls]] = MediaCls
"""
Set the [Media class](https://docs.djangoproject.com/en/5.2/topics/forms/media/#assets-as-a-static-definition)
that will be instantiated with the JS and CSS media files from
[`Component.Media`](../api#django_components.Component.Media).
This is useful when you want to customize the behavior of the media files, like
customizing how the JS or CSS files are rendered into `