mirror of
https://github.com/django-components/django-components.git
synced 2025-11-18 14:10:19 +00:00
refactor: Add Node metadata (#1229)
* refactor: `Slot.source` replaced with `Slot.fill_node`, new `Component.node` property, and `slot_node` available in `on_slot_rendered()` hook. * refactor: fix windows path error in tests
This commit is contained in:
parent
abc6be343e
commit
46e524e37d
17 changed files with 728 additions and 103 deletions
|
|
@ -18,6 +18,7 @@ from django_components.util.command import (
|
|||
from django_components.component import (
|
||||
Component,
|
||||
ComponentInput,
|
||||
ComponentNode,
|
||||
ComponentVars,
|
||||
all_components,
|
||||
get_component_by_class_id,
|
||||
|
|
@ -52,13 +53,16 @@ from django_components.extensions.debug_highlight import ComponentDebugHighlight
|
|||
from django_components.extensions.view import ComponentView, get_component_url
|
||||
from django_components.library import TagProtectedError
|
||||
from django_components.node import BaseNode, template_tag
|
||||
from django_components.provide import ProvideNode
|
||||
from django_components.slots import (
|
||||
FillNode,
|
||||
Slot,
|
||||
SlotContent,
|
||||
SlotContext,
|
||||
SlotFallback,
|
||||
SlotFunc,
|
||||
SlotInput,
|
||||
SlotNode,
|
||||
SlotRef,
|
||||
SlotResult,
|
||||
)
|
||||
|
|
@ -103,6 +107,7 @@ __all__ = [
|
|||
"ComponentInput",
|
||||
"ComponentMediaInput",
|
||||
"ComponentMediaInputPath",
|
||||
"ComponentNode",
|
||||
"ComponentRegistry",
|
||||
"ComponentVars",
|
||||
"ComponentView",
|
||||
|
|
@ -115,6 +120,7 @@ __all__ = [
|
|||
"DynamicComponent",
|
||||
"Empty",
|
||||
"ExtensionComponentConfig",
|
||||
"FillNode",
|
||||
"format_attributes",
|
||||
"get_component_by_class_id",
|
||||
"get_component_dirs",
|
||||
|
|
@ -131,6 +137,7 @@ __all__ = [
|
|||
"OnComponentUnregisteredContext",
|
||||
"OnRegistryCreatedContext",
|
||||
"OnRegistryDeletedContext",
|
||||
"ProvideNode",
|
||||
"register",
|
||||
"registry",
|
||||
"RegistrySettings",
|
||||
|
|
@ -142,6 +149,7 @@ __all__ = [
|
|||
"SlotFallback",
|
||||
"SlotFunc",
|
||||
"SlotInput",
|
||||
"SlotNode",
|
||||
"SlotRef",
|
||||
"SlotResult",
|
||||
"TagFormatterABC",
|
||||
|
|
|
|||
|
|
@ -1941,6 +1941,7 @@ class Component(metaclass=ComponentMeta):
|
|||
slots: Optional[Any] = None,
|
||||
deps_strategy: Optional[DependenciesStrategy] = None,
|
||||
request: Optional[HttpRequest] = None,
|
||||
node: Optional["ComponentNode"] = None,
|
||||
id: Optional[str] = None,
|
||||
):
|
||||
# TODO_v1 - Remove this whole block in v1. This is for backwards compatibility with pre-v0.140
|
||||
|
|
@ -2009,6 +2010,7 @@ class Component(metaclass=ComponentMeta):
|
|||
self.request = request
|
||||
self.outer_context: Optional[Context] = outer_context
|
||||
self.registry = default(registry, registry_)
|
||||
self.node = node
|
||||
|
||||
extensions._init_component_instance(self)
|
||||
|
||||
|
|
@ -2346,6 +2348,51 @@ class Component(metaclass=ComponentMeta):
|
|||
that was used to render the component.
|
||||
"""
|
||||
|
||||
node: Optional["ComponentNode"]
|
||||
"""
|
||||
The [`ComponentNode`](../api/#django_components.ComponentNode) instance
|
||||
that was used to render the component.
|
||||
|
||||
This will be set only if the component was rendered with the
|
||||
[`{% component %}`](../template_tags#component) tag.
|
||||
|
||||
Accessing the [`ComponentNode`](../api/#django_components.ComponentNode) is mostly useful for extensions,
|
||||
which can modify their behaviour based on the source of the Component.
|
||||
|
||||
```py
|
||||
class MyComponent(Component):
|
||||
def get_template_data(self, context, template):
|
||||
if self.node is not None:
|
||||
assert self.node.name == "my_component"
|
||||
```
|
||||
|
||||
For example, if `MyComponent` was used in another component - that is,
|
||||
with a `{% component "my_component" %}` tag
|
||||
in a template that belongs to another component - then you can use
|
||||
[`self.node.template_component`](../api/#django_components.ComponentNode.template_component)
|
||||
to access the owner [`Component`](../api/#django_components.Component) class.
|
||||
|
||||
```djc_py
|
||||
class Parent(Component):
|
||||
template: types.django_html = '''
|
||||
<div>
|
||||
{% component "my_component" / %}
|
||||
</div>
|
||||
'''
|
||||
|
||||
@register("my_component")
|
||||
class MyComponent(Component):
|
||||
def get_template_data(self, context, template):
|
||||
if self.node is not None:
|
||||
assert self.node.template_component == Parent
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
`Component.node` is `None` if the component is created by
|
||||
[`Component.render()`](../api/#django_components.Component.render)
|
||||
(but you can pass in the `node` kwarg yourself).
|
||||
"""
|
||||
# TODO_v1 - Remove, superseded by `Component.slots`
|
||||
is_filled: SlotIsFilled
|
||||
"""
|
||||
|
|
@ -2535,6 +2582,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# TODO_v2 - Remove `registered_name` and `registry`
|
||||
registry: Optional[ComponentRegistry] = None,
|
||||
registered_name: Optional[str] = None,
|
||||
node: Optional["ComponentNode"] = None,
|
||||
**response_kwargs: Any,
|
||||
) -> HttpResponse:
|
||||
"""
|
||||
|
|
@ -2603,6 +2651,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# TODO_v2 - Remove `registered_name` and `registry`
|
||||
registry=registry,
|
||||
registered_name=registered_name,
|
||||
node=node,
|
||||
)
|
||||
return cls.response_class(content, **response_kwargs)
|
||||
|
||||
|
|
@ -2623,6 +2672,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# TODO_v2 - Remove `registered_name` and `registry`
|
||||
registry: Optional[ComponentRegistry] = None,
|
||||
registered_name: Optional[str] = None,
|
||||
node: Optional["ComponentNode"] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Render the component into a string. This is the equivalent of calling
|
||||
|
|
@ -2830,6 +2880,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# TODO_v2 - Remove `registered_name` and `registry`
|
||||
registry=registry,
|
||||
registered_name=registered_name,
|
||||
node=node,
|
||||
)
|
||||
|
||||
# This is the internal entrypoint for the render function
|
||||
|
|
@ -2846,6 +2897,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# TODO_v2 - Remove `registered_name` and `registry`
|
||||
registry: Optional[ComponentRegistry] = None,
|
||||
registered_name: Optional[str] = None,
|
||||
node: Optional["ComponentNode"] = None,
|
||||
) -> str:
|
||||
component_name = _get_component_name(cls, registered_name)
|
||||
|
||||
|
|
@ -2862,6 +2914,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# TODO_v2 - Remove `registered_name` and `registry`
|
||||
registry=registry,
|
||||
registered_name=registered_name,
|
||||
node=node,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -2877,6 +2930,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# TODO_v2 - Remove `registered_name` and `registry`
|
||||
registry: Optional[ComponentRegistry] = None,
|
||||
registered_name: Optional[str] = None,
|
||||
node: Optional["ComponentNode"] = None,
|
||||
) -> str:
|
||||
######################################
|
||||
# 1. Handle inputs
|
||||
|
|
@ -2929,6 +2983,7 @@ class Component(metaclass=ComponentMeta):
|
|||
# TODO_v2 - Remove `registered_name` and `registry`
|
||||
registry=registry,
|
||||
registered_name=registered_name,
|
||||
node=node,
|
||||
)
|
||||
|
||||
# Allow plugins to modify or validate the inputs
|
||||
|
|
@ -3316,7 +3371,7 @@ class ComponentNode(BaseNode):
|
|||
[`@register()`](./api.md#django_components.register)
|
||||
decorator.
|
||||
|
||||
The `{% component %}` tag takes:
|
||||
The [`{% component %}`](../template_tags#component) tag takes:
|
||||
|
||||
- Component's registered name as the first positional argument,
|
||||
- Followed by any number of positional and keyword arguments.
|
||||
|
|
@ -3333,7 +3388,8 @@ class ComponentNode(BaseNode):
|
|||
### Inserting slot fills
|
||||
|
||||
If the component defined any [slots](../concepts/fundamentals/slots.md), you can
|
||||
"fill" these slots by placing the [`{% fill %}`](#fill) tags within the `{% component %}` tag:
|
||||
"fill" these slots by placing the [`{% fill %}`](../template_tags#fill) tags
|
||||
within the [`{% component %}`](../template_tags#component) tag:
|
||||
|
||||
```django
|
||||
{% component "my_table" rows=rows headers=headers %}
|
||||
|
|
@ -3343,7 +3399,7 @@ class ComponentNode(BaseNode):
|
|||
{% endcomponent %}
|
||||
```
|
||||
|
||||
You can even nest [`{% fill %}`](#fill) tags within
|
||||
You can even nest [`{% fill %}`](../template_tags#fill) tags within
|
||||
[`{% if %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#if),
|
||||
[`{% for %}`](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#for)
|
||||
and other tags:
|
||||
|
|
@ -3382,7 +3438,7 @@ class ComponentNode(BaseNode):
|
|||
}
|
||||
```
|
||||
|
||||
### Omitting the `component` keyword
|
||||
### Omitting the component keyword
|
||||
|
||||
If you would like to omit the `component` keyword, and simply refer to your
|
||||
components by their registered names:
|
||||
|
|
@ -3417,6 +3473,8 @@ class ComponentNode(BaseNode):
|
|||
nodelist: Optional[NodeList] = None,
|
||||
node_id: Optional[str] = None,
|
||||
contents: Optional[str] = None,
|
||||
template_name: Optional[str] = None,
|
||||
template_component: Optional[Type["Component"]] = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
params=params,
|
||||
|
|
@ -3424,6 +3482,8 @@ class ComponentNode(BaseNode):
|
|||
nodelist=nodelist,
|
||||
node_id=node_id,
|
||||
contents=contents,
|
||||
template_name=template_name,
|
||||
template_component=template_component,
|
||||
)
|
||||
|
||||
self.name = name
|
||||
|
|
@ -3499,6 +3559,7 @@ class ComponentNode(BaseNode):
|
|||
registered_name=self.name,
|
||||
outer_context=context,
|
||||
registry=self.registry,
|
||||
node=self,
|
||||
)
|
||||
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from django_components.util.routing import URLRoute
|
|||
if TYPE_CHECKING:
|
||||
from django_components import Component
|
||||
from django_components.component_registry import ComponentRegistry
|
||||
from django_components.slots import Slot, SlotResult
|
||||
from django_components.slots import Slot, SlotNode, SlotResult
|
||||
|
||||
|
||||
TCallable = TypeVar("TCallable", bound=Callable)
|
||||
|
|
@ -155,6 +155,8 @@ class OnSlotRenderedContext(NamedTuple):
|
|||
"""The Slot instance that was rendered"""
|
||||
slot_name: str
|
||||
"""The name of the `{% slot %}` tag"""
|
||||
slot_node: "SlotNode"
|
||||
"""The node instance of the `{% slot %}` tag"""
|
||||
slot_is_required: bool
|
||||
"""Whether the slot is required"""
|
||||
slot_is_default: bool
|
||||
|
|
@ -744,6 +746,26 @@ class ComponentExtension(metaclass=ExtensionMeta):
|
|||
# Append a comment to the slot's rendered output
|
||||
return ctx.result + "<!-- MyExtension comment -->"
|
||||
```
|
||||
|
||||
**Access slot metadata:**
|
||||
|
||||
You can access the [`{% slot %}` tag](../template_tags#slot)
|
||||
node ([`SlotNode`](../api#django_components.SlotNode)) and its metadata using `ctx.slot_node`.
|
||||
|
||||
For example, to find the [`Component`](../api#django_components.Component) class to which
|
||||
belongs the template where the [`{% slot %}`](../template_tags#slot) tag is defined, you can use
|
||||
[`ctx.slot_node.template_component`](../api#django_components.SlotNode.template_component):
|
||||
|
||||
```python
|
||||
from django_components import ComponentExtension, OnSlotRenderedContext
|
||||
|
||||
class MyExtension(ComponentExtension):
|
||||
def on_slot_rendered(self, ctx: OnSlotRenderedContext) -> Optional[str]:
|
||||
# Access slot metadata
|
||||
slot_node = ctx.slot_node
|
||||
slot_owner = slot_node.template_component
|
||||
print(f"Slot owner: {slot_owner}")
|
||||
```
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import functools
|
||||
import inspect
|
||||
import keyword
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, cast
|
||||
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Optional, Tuple, Type, cast
|
||||
|
||||
from django.template import Context, Library
|
||||
from django.template.base import Node, NodeList, Parser, Token
|
||||
|
|
@ -15,6 +15,9 @@ from django_components.util.template_tag import (
|
|||
validate_params,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django_components.component import Component
|
||||
|
||||
|
||||
# Normally, when `Node.render()` is called, it receives only a single argument `context`.
|
||||
#
|
||||
|
|
@ -252,32 +255,77 @@ class BaseNode(Node, metaclass=NodeMeta):
|
|||
# PUBLIC API (Configurable by users)
|
||||
# #####################################
|
||||
|
||||
tag: str
|
||||
tag: ClassVar[str]
|
||||
"""
|
||||
The tag name.
|
||||
|
||||
E.g. `"component"` or `"slot"` will make this class match
|
||||
template tags `{% component %}` or `{% slot %}`.
|
||||
|
||||
```python
|
||||
class SlotNode(BaseNode):
|
||||
tag = "slot"
|
||||
end_tag = "endslot"
|
||||
```
|
||||
|
||||
This will allow the template tag `{% slot %}` to be used like this:
|
||||
|
||||
```django
|
||||
{% slot %} ... {% endslot %}
|
||||
```
|
||||
"""
|
||||
|
||||
end_tag: Optional[str] = None
|
||||
end_tag: ClassVar[Optional[str]] = None
|
||||
"""
|
||||
The end tag name.
|
||||
|
||||
E.g. `"endcomponent"` or `"endslot"` will make this class match
|
||||
template tags `{% endcomponent %}` or `{% endslot %}`.
|
||||
|
||||
```python
|
||||
class SlotNode(BaseNode):
|
||||
tag = "slot"
|
||||
end_tag = "endslot"
|
||||
```
|
||||
|
||||
This will allow the template tag `{% slot %}` to be used like this:
|
||||
|
||||
```django
|
||||
{% slot %} ... {% endslot %}
|
||||
```
|
||||
|
||||
If not set, then this template tag has no end tag.
|
||||
|
||||
So instead of `{% component %} ... {% endcomponent %}`, you'd use only
|
||||
`{% component %}`.
|
||||
|
||||
```python
|
||||
class MyNode(BaseNode):
|
||||
tag = "mytag"
|
||||
end_tag = None
|
||||
```
|
||||
"""
|
||||
|
||||
allowed_flags: Optional[List[str]] = None
|
||||
allowed_flags: ClassVar[Optional[List[str]]] = None
|
||||
"""
|
||||
The allowed flags for this tag.
|
||||
The list of all *possible* flags for this tag.
|
||||
|
||||
E.g. `["required"]` will allow this tag to be used like `{% slot required %}`.
|
||||
|
||||
```python
|
||||
class SlotNode(BaseNode):
|
||||
tag = "slot"
|
||||
end_tag = "endslot"
|
||||
allowed_flags = ["required", "default"]
|
||||
```
|
||||
|
||||
This will allow the template tag `{% slot %}` to be used like this:
|
||||
|
||||
```django
|
||||
{% slot required %} ... {% endslot %}
|
||||
{% slot default %} ... {% endslot %}
|
||||
{% slot required default %} ... {% endslot %}
|
||||
```
|
||||
"""
|
||||
|
||||
def render(self, context: Context, *args: Any, **kwargs: Any) -> str:
|
||||
|
|
@ -303,6 +351,133 @@ class BaseNode(Node, metaclass=NodeMeta):
|
|||
"""
|
||||
return self.nodelist.render(context)
|
||||
|
||||
# #####################################
|
||||
# Attributes
|
||||
# #####################################
|
||||
|
||||
params: List[TagAttr]
|
||||
"""
|
||||
The parameters to the tag in the template.
|
||||
|
||||
A single param represents an arg or kwarg of the template tag.
|
||||
|
||||
E.g. the following tag:
|
||||
|
||||
```django
|
||||
{% component "my_comp" key=val key2='val2 two' %}
|
||||
```
|
||||
|
||||
Has 3 params:
|
||||
|
||||
- Posiitonal arg `"my_comp"`
|
||||
- Keyword arg `key=val`
|
||||
- Keyword arg `key2='val2 two'`
|
||||
"""
|
||||
|
||||
flags: Dict[str, bool]
|
||||
"""
|
||||
Dictionary of all [`allowed_flags`](../api#django_components.BaseNode.allowed_flags)
|
||||
that were set on the tag.
|
||||
|
||||
Flags that were set are `True`, and the rest are `False`.
|
||||
|
||||
E.g. the following tag:
|
||||
|
||||
```python
|
||||
class SlotNode(BaseNode):
|
||||
tag = "slot"
|
||||
end_tag = "endslot"
|
||||
allowed_flags = ["default", "required"]
|
||||
```
|
||||
|
||||
```django
|
||||
{% slot "content" default %}
|
||||
```
|
||||
|
||||
Has 2 flags, `default` and `required`, but only `default` was set.
|
||||
|
||||
The `flags` dictionary will be:
|
||||
|
||||
```python
|
||||
{
|
||||
"default": True,
|
||||
"required": False,
|
||||
}
|
||||
```
|
||||
|
||||
You can check if a flag is set by doing:
|
||||
|
||||
```python
|
||||
if node.flags["default"]:
|
||||
...
|
||||
```
|
||||
"""
|
||||
|
||||
nodelist: NodeList
|
||||
"""
|
||||
The nodelist of the tag.
|
||||
|
||||
This is the text between the opening and closing tags, e.g.
|
||||
|
||||
```django
|
||||
{% slot "content" default required %}
|
||||
<div>
|
||||
...
|
||||
</div>
|
||||
{% endslot %}
|
||||
```
|
||||
|
||||
The `nodelist` will contain the `<div> ... </div>` part.
|
||||
|
||||
Unlike [`contents`](../api#django_components.BaseNode.contents),
|
||||
the `nodelist` contains the actual Nodes, not just the text.
|
||||
"""
|
||||
|
||||
contents: Optional[str]
|
||||
"""
|
||||
The contents of the tag.
|
||||
|
||||
This is the text between the opening and closing tags, e.g.
|
||||
|
||||
```django
|
||||
{% slot "content" default required %}
|
||||
<div>
|
||||
...
|
||||
</div>
|
||||
{% endslot %}
|
||||
```
|
||||
|
||||
The `contents` will be `"<div> ... </div>"`.
|
||||
"""
|
||||
|
||||
node_id: str
|
||||
"""
|
||||
The unique ID of the node.
|
||||
|
||||
Extensions can use this ID to store additional information.
|
||||
"""
|
||||
|
||||
template_name: Optional[str]
|
||||
"""
|
||||
The name of the [`Template`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template)
|
||||
that contains this node.
|
||||
|
||||
The template name is set by Django's
|
||||
[template loaders](https://docs.djangoproject.com/en/5.2/topics/templates/#loaders).
|
||||
|
||||
For example, the filesystem template loader will set this to the absolute path of the template file.
|
||||
|
||||
```
|
||||
"/home/user/project/templates/my_template.html"
|
||||
```
|
||||
"""
|
||||
|
||||
template_component: Optional[Type["Component"]]
|
||||
"""
|
||||
If the template that contains this node belongs to a [`Component`](../api#django_components.Component),
|
||||
then this will be the [`Component`](../api#django_components.Component) class.
|
||||
"""
|
||||
|
||||
# #####################################
|
||||
# MISC
|
||||
# #####################################
|
||||
|
|
@ -314,12 +489,16 @@ class BaseNode(Node, metaclass=NodeMeta):
|
|||
nodelist: Optional[NodeList] = None,
|
||||
node_id: Optional[str] = None,
|
||||
contents: Optional[str] = None,
|
||||
template_name: Optional[str] = None,
|
||||
template_component: Optional[Type["Component"]] = None,
|
||||
):
|
||||
self.params = params
|
||||
self.flags = flags or {flag: False for flag in self.allowed_flags or []}
|
||||
self.nodelist = nodelist or NodeList()
|
||||
self.node_id = node_id or gen_id()
|
||||
self.contents = contents
|
||||
self.template_name = template_name
|
||||
self.template_component = template_component
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
|
|
@ -329,7 +508,21 @@ class BaseNode(Node, metaclass=NodeMeta):
|
|||
|
||||
@property
|
||||
def active_flags(self) -> List[str]:
|
||||
"""Flags that were set for this specific instance."""
|
||||
"""
|
||||
Flags that were set for this specific instance as a list of strings.
|
||||
|
||||
E.g. the following tag:
|
||||
|
||||
```django
|
||||
{% slot "content" default required / %}
|
||||
```
|
||||
|
||||
Will have the following flags:
|
||||
|
||||
```python
|
||||
["default", "required"]
|
||||
```
|
||||
"""
|
||||
flags = []
|
||||
for flag, value in self.flags.items():
|
||||
if value:
|
||||
|
|
@ -347,6 +540,9 @@ class BaseNode(Node, metaclass=NodeMeta):
|
|||
|
||||
To register the tag, you can use [`BaseNode.register()`](../api#django_components.BaseNode.register).
|
||||
"""
|
||||
# NOTE: Avoids circular import
|
||||
from django_components.template import get_component_from_origin
|
||||
|
||||
tag_id = gen_id()
|
||||
tag = parse_template_tag(cls.tag, cls.end_tag, cls.allowed_flags, parser, token)
|
||||
|
||||
|
|
@ -359,6 +555,8 @@ class BaseNode(Node, metaclass=NodeMeta):
|
|||
params=tag.params,
|
||||
flags=tag.flags,
|
||||
contents=contents,
|
||||
template_name=parser.origin.name if parser.origin else None,
|
||||
template_component=get_component_from_origin(parser.origin) if parser.origin else None,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ from django_components.util.misc import gen_id
|
|||
|
||||
class ProvideNode(BaseNode):
|
||||
"""
|
||||
The "provider" part of the [provide / inject feature](../../concepts/advanced/provide_inject).
|
||||
The [`{% provide %}`](../template_tags#provide) tag is part of the "provider" part of
|
||||
the [provide / inject feature](../../concepts/advanced/provide_inject).
|
||||
|
||||
Pass kwargs to this tag to define the provider's data.
|
||||
|
||||
Any components defined within the `{% provide %}..{% endprovide %}` tags will be able to access this data
|
||||
with [`Component.inject()`](../api#django_components.Component.inject).
|
||||
|
||||
|
|
@ -66,7 +69,7 @@ class ProvideNode(BaseNode):
|
|||
}
|
||||
```
|
||||
|
||||
Notice that the keys defined on the `{% provide %}` tag are then accessed as attributes
|
||||
Notice that the keys defined on the [`{% provide %}`](../template_tags#provide) tag are then accessed as attributes
|
||||
when accessing them with [`Component.inject()`](../api#django_components.Component.inject).
|
||||
|
||||
✅ Do this
|
||||
|
|
|
|||
|
|
@ -265,14 +265,33 @@ class Slot(Generic[TSlotData]):
|
|||
|
||||
See [Slot metadata](../../concepts/fundamentals/slots#slot-metadata).
|
||||
"""
|
||||
source: Literal["template", "python"] = "python"
|
||||
fill_node: Optional[Union["FillNode", "ComponentNode"]] = None
|
||||
"""
|
||||
Whether the slot was created from a [`{% fill %}`](../template_tags#fill) tag (`'template'`),
|
||||
or Python (`'python'`).
|
||||
If the slot was created from a [`{% fill %}`](../template_tags#fill) tag,
|
||||
this will be the [`FillNode`](../api/#django_components.FillNode) instance.
|
||||
|
||||
If the slot was a default slot created from a [`{% component %}`](../template_tags#component) tag,
|
||||
this will be the [`ComponentNode`](../api/#django_components.ComponentNode) instance.
|
||||
|
||||
Otherwise, this will be `None`.
|
||||
|
||||
Extensions can use this info to handle slots differently based on their source.
|
||||
|
||||
See [Slot metadata](../../concepts/fundamentals/slots#slot-metadata).
|
||||
|
||||
**Example:**
|
||||
|
||||
You can use this to find the [`Component`](../api/#django_components.Component) in whose
|
||||
template the [`{% fill %}`](../template_tags#fill) tag was defined:
|
||||
|
||||
```python
|
||||
class MyTable(Component):
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
footer_slot = slots.get("footer")
|
||||
if footer_slot is not None and footer_slot.fill_node is not None:
|
||||
owner_component = footer_slot.fill_node.template_component
|
||||
# ...
|
||||
```
|
||||
"""
|
||||
extra: Dict[str, Any] = field(default_factory=dict)
|
||||
"""
|
||||
|
|
@ -494,7 +513,7 @@ class SlotIsFilled(dict):
|
|||
|
||||
class SlotNode(BaseNode):
|
||||
"""
|
||||
Slot tag marks a place inside a component where content can be inserted
|
||||
[`{% slot %}`](../template_tags#slot) tag marks a place inside a component where content can be inserted
|
||||
from outside.
|
||||
|
||||
[Learn more](../../concepts/fundamentals/slots) about using slots.
|
||||
|
|
@ -508,7 +527,7 @@ class SlotNode(BaseNode):
|
|||
|
||||
- `name` (str, required): Registered name of the component to render
|
||||
- `default`: Optional flag. If there is a default slot, you can pass the component slot content
|
||||
without using the [`{% fill %}`](#fill) tag. See
|
||||
without using the [`{% fill %}`](../template_tags#fill) tag. See
|
||||
[Default slot](../../concepts/fundamentals/slots#default-slot)
|
||||
- `required`: Optional flag. Will raise an error if a slot is required but not given.
|
||||
- `**kwargs`: Any extra kwargs will be passed as the slot data.
|
||||
|
|
@ -550,8 +569,8 @@ class SlotNode(BaseNode):
|
|||
|
||||
### Slot data
|
||||
|
||||
Any extra kwargs will be considered as slot data, and will be accessible in the [`{% fill %}`](#fill)
|
||||
tag via fill's `data` kwarg:
|
||||
Any extra kwargs will be considered as slot data, and will be accessible
|
||||
in the [`{% fill %}`](../template_tags#fill) tag via fill's `data` kwarg:
|
||||
|
||||
Read more about [Slot data](../../concepts/fundamentals/slots#slot-data).
|
||||
|
||||
|
|
@ -588,8 +607,8 @@ class SlotNode(BaseNode):
|
|||
The content between the `{% slot %}..{% endslot %}` tags is the fallback content that
|
||||
will be rendered if no fill is given for the slot.
|
||||
|
||||
This fallback content can then be accessed from within the [`{% fill %}`](#fill) tag using
|
||||
the fill's `fallback` kwarg.
|
||||
This fallback content can then be accessed from within the [`{% fill %}`](../template_tags#fill) tag
|
||||
using the fill's `fallback` kwarg.
|
||||
This is useful if you need to wrap / prepend / append the original slot's content.
|
||||
|
||||
```djc_py
|
||||
|
|
@ -926,6 +945,7 @@ class SlotNode(BaseNode):
|
|||
component_id=component_id,
|
||||
slot=slot,
|
||||
slot_name=slot_name,
|
||||
slot_node=self,
|
||||
slot_is_required=is_required,
|
||||
slot_is_default=is_default,
|
||||
result=output,
|
||||
|
|
@ -968,15 +988,16 @@ class SlotNode(BaseNode):
|
|||
|
||||
class FillNode(BaseNode):
|
||||
"""
|
||||
Use this tag to insert content into component's slots.
|
||||
Use [`{% fill %}`](../template_tags#fill) tag to insert content into component's
|
||||
[slots](../../concepts/fundamentals/slots).
|
||||
|
||||
`{% fill %}` tag may be used only within a `{% component %}..{% endcomponent %}` block,
|
||||
[`{% fill %}`](../template_tags#fill) tag may be used only within a `{% component %}..{% endcomponent %}` block,
|
||||
and raises a `TemplateSyntaxError` if used outside of a component.
|
||||
|
||||
**Args:**
|
||||
|
||||
- `name` (str, required): Name of the slot to insert this content into. Use `"default"` for
|
||||
the default slot.
|
||||
the [default slot](../../concepts/fundamentals/slots#default-slot).
|
||||
- `data` (str, optional): This argument allows you to access the data passed to the slot
|
||||
under the specified variable name. See [Slot data](../../concepts/fundamentals/slots#slot-data).
|
||||
- `fallback` (str, optional): This argument allows you to access the original content of the slot
|
||||
|
|
@ -1061,7 +1082,7 @@ class FillNode(BaseNode):
|
|||
### Using default slot
|
||||
|
||||
To access slot data and the fallback slot content on the default slot,
|
||||
use `{% fill %}` with `name` set to `"default"`:
|
||||
use [`{% fill %}`](../template_tags#fill) with `name` set to `"default"`:
|
||||
|
||||
```django
|
||||
{% component "button" %}
|
||||
|
|
@ -1075,7 +1096,7 @@ class FillNode(BaseNode):
|
|||
### Slot fills from Python
|
||||
|
||||
You can pass a slot fill from Python to a component by setting the `body` kwarg
|
||||
on the `{% fill %}` tag.
|
||||
on the [`{% fill %}`](../template_tags#fill) tag.
|
||||
|
||||
First pass a [`Slot`](../api#django_components.Slot) instance to the template
|
||||
with the [`get_template_data()`](../api#django_components.Component.get_template_data)
|
||||
|
|
@ -1091,7 +1112,7 @@ class FillNode(BaseNode):
|
|||
}
|
||||
```
|
||||
|
||||
Then pass the slot to the `{% fill %}` tag:
|
||||
Then pass the slot to the [`{% fill %}`](../template_tags#fill) tag:
|
||||
|
||||
```django
|
||||
{% component "table" %}
|
||||
|
|
@ -1101,7 +1122,7 @@ class FillNode(BaseNode):
|
|||
|
||||
!!! warning
|
||||
|
||||
If you define both the `body` kwarg and the `{% fill %}` tag's body,
|
||||
If you define both the `body` kwarg and the [`{% fill %}`](../template_tags#fill) tag's body,
|
||||
an error will be raised.
|
||||
|
||||
```django
|
||||
|
|
@ -1395,7 +1416,7 @@ def resolve_fills(
|
|||
contents=contents,
|
||||
data_var=None,
|
||||
fallback_var=None,
|
||||
source="template",
|
||||
fill_node=component_node,
|
||||
)
|
||||
|
||||
# The content has fills
|
||||
|
|
@ -1405,14 +1426,15 @@ def resolve_fills(
|
|||
for fill in maybe_fills:
|
||||
# Case: Slot fill was explicitly defined as `{% fill body=... / %}`
|
||||
if fill.body is not None:
|
||||
# Set `Slot.fill_node` so the slot fill behaves the same as if it was defined inside
|
||||
# a `{% fill %}` tag.
|
||||
# This for example allows CSS scoping to work even on slots that are defined
|
||||
# as `{% fill ... body=... / %}`
|
||||
if isinstance(fill.body, Slot):
|
||||
# Make a copy of the Slot instance and set it to `source="template"`,
|
||||
# so it behaves the same as if the content was written inside the `{% fill %}` tag.
|
||||
# This for example allows CSS scoping to work even on slots that are defined
|
||||
# as `{% fill ... body=... / %}`
|
||||
slot_fill = dataclass_replace(fill.body, source="template")
|
||||
# Make a copy of the Slot instance and set its `fill_node`.
|
||||
slot_fill = dataclass_replace(fill.body, fill_node=fill.fill)
|
||||
else:
|
||||
slot_fill = Slot(fill.body)
|
||||
slot_fill = Slot(fill.body, fill_node=fill.fill)
|
||||
# Case: Slot fill was defined as the body of `{% fill / %}...{% endfill %}`
|
||||
else:
|
||||
slot_fill = _nodelist_to_slot(
|
||||
|
|
@ -1423,7 +1445,7 @@ def resolve_fills(
|
|||
data_var=fill.data_var,
|
||||
fallback_var=fill.fallback_var,
|
||||
extra_context=fill.extra_context,
|
||||
source="template",
|
||||
fill_node=fill.fill,
|
||||
)
|
||||
slots[fill.name] = slot_fill
|
||||
|
||||
|
|
@ -1497,14 +1519,14 @@ def normalize_slot_fills(
|
|||
used_slot_name = content.slot_name or slot_name
|
||||
used_nodelist = content.nodelist
|
||||
used_contents = content.contents if content.contents is not None else content_func
|
||||
used_source = content.source
|
||||
used_fill_node = content.fill_node
|
||||
used_extra = content.extra.copy()
|
||||
else:
|
||||
used_component_name = component_name
|
||||
used_slot_name = slot_name
|
||||
used_nodelist = None
|
||||
used_contents = content_func
|
||||
used_source = "python"
|
||||
used_fill_node = None
|
||||
used_extra = {}
|
||||
|
||||
slot = Slot(
|
||||
|
|
@ -1513,7 +1535,7 @@ def normalize_slot_fills(
|
|||
component_name=used_component_name,
|
||||
slot_name=used_slot_name,
|
||||
nodelist=used_nodelist,
|
||||
source=used_source,
|
||||
fill_node=used_fill_node,
|
||||
extra=used_extra,
|
||||
)
|
||||
|
||||
|
|
@ -1544,7 +1566,7 @@ def _nodelist_to_slot(
|
|||
data_var: Optional[str] = None,
|
||||
fallback_var: Optional[str] = None,
|
||||
extra_context: Optional[Dict[str, Any]] = None,
|
||||
source: Optional[Literal["template", "python"]] = None,
|
||||
fill_node: Optional[Union[FillNode, "ComponentNode"]] = None,
|
||||
extra: Optional[Dict[str, Any]] = None,
|
||||
) -> Slot:
|
||||
if data_var:
|
||||
|
|
@ -1638,7 +1660,7 @@ def _nodelist_to_slot(
|
|||
# But `Slot(contents=None)` would result in `Slot.contents` being the render function.
|
||||
# So we need to special-case this.
|
||||
contents=default(contents, ""),
|
||||
source=default(source, "python"),
|
||||
fill_node=default(fill_node, None),
|
||||
extra=default(extra, {}),
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue