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:
Juro Oravec 2025-06-03 12:58:48 +02:00 committed by GitHub
parent abc6be343e
commit 46e524e37d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 728 additions and 103 deletions

View file

@ -57,6 +57,7 @@ The Render API includes:
- [`self.inject()`](../render_api/#provide-inject) - Inject data into the component
- Template tag metadata:
- [`self.node`](../render_api/#template-tag-metadata) - The [`ComponentNode`](../../../reference/api/#django_components.ComponentNode) instance
- [`self.registry`](../render_api/#template-tag-metadata) - The [`ComponentRegistry`](../../../reference/api/#django_components.ComponentRegistry) instance
- [`self.registered_name`](../render_api/#template-tag-metadata) - The name under which the component was registered
- [`self.outer_context`](../render_api/#template-tag-metadata) - The context outside of the [`{% component %}`](../../../reference/template_tags#component) tag
@ -337,17 +338,18 @@ class Table(Component):
If the component is rendered with [`{% component %}`](../../../reference/template_tags#component) template tag,
the following metadata is available:
- [`self.node`](../../../reference/api/#django_components.Component.node) - The [`ComponentNode`](../../../reference/api/#django_components.ComponentNode) instance
- [`self.registry`](../../../reference/api/#django_components.Component.registry) - The [`ComponentRegistry`](../../../reference/api/#django_components.ComponentRegistry) instance
that was used to render the component
- [`self.registered_name`](../../../reference/api/#django_components.Component.registered_name) - The name under which the component was registered
- [`self.outer_context`](../../../reference/api/#django_components.Component.outer_context) - The context outside of the [`{% component %}`](../../../reference/template_tags#component) tag
```django
{% with abc=123 %}
{{ abc }} {# <--- This is in outer context #}
{% component "my_component" / %}
{% endwith %}
```
```django
{% with abc=123 %}
{{ abc }} {# <--- This is in outer context #}
{% component "my_component" / %}
{% endwith %}
```
You can use these to check whether the component was rendered inside a template with [`{% component %}`](../../../reference/template_tags#component) tag
or in Python with [`Component.render()`](../../../reference/api/#django_components.Component.render).
@ -360,3 +362,40 @@ class MyComponent(Component):
else:
# Do something for the {% component %} template tag
```
You can access the [`ComponentNode`](../../../reference/api/#django_components.ComponentNode) under [`Component.node`](../../../reference/api/#django_components.Component.node):
```py
class MyComponent(Component):
def get_template_data(self, context, template):
if self.node is not None:
assert self.node.name == "my_component"
```
Accessing the [`ComponentNode`](../../../reference/api/#django_components.ComponentNode) is mostly useful for extensions, which can modify their behaviour based on the source of the 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`](../../../reference/api/#django_components.ComponentNode.template_component)
to access the owner [`Component`](../../../reference/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()`](../../../reference/api/#django_components.Component.render)
(but you can pass in the `node` kwarg yourself).

View file

@ -728,7 +728,7 @@ with extra metadata:
- [`component_name`](../../../reference/api#django_components.Slot.component_name)
- [`slot_name`](../../../reference/api#django_components.Slot.slot_name)
- [`nodelist`](../../../reference/api#django_components.Slot.nodelist)
- [`source`](../../../reference/api#django_components.Slot.source)
- [`fill_node`](../../../reference/api#django_components.Slot.fill_node)
- [`extra`](../../../reference/api#django_components.Slot.extra)
These are populated the first time a slot is passed to a component.
@ -738,10 +738,33 @@ still point to the first component that received the slot.
You can use these for debugging, such as printing out the slot's component name and slot name.
Extensions can use [`Slot.source`](../../../reference/api#django_components.Slot.source)
**Fill node**
Components or extensions can use [`Slot.fill_node`](../../../reference/api#django_components.Slot.fill_node)
to handle slots differently based on whether the slot
was defined in the template with [`{% fill %}`](../../../reference/template_tags#fill) tag
or in the component's Python code. See an example in [Pass slot metadata](../../advanced/extensions#pass-slot-metadata).
was defined in the template with [`{% fill %}`](../../../reference/template_tags#fill) and
[`{% component %}`](../../../reference/template_tags#component) tags,
or in the component's Python code.
If the slot was created from a [`{% fill %}`](../../../reference/template_tags#fill) tag,
this will be the [`FillNode`](../../../reference/api#django_components.FillNode) instance.
If the slot was a default slot created from a [`{% component %}`](../../../reference/template_tags#component) tag,
this will be the [`ComponentNode`](../../../reference/api#django_components.ComponentNode) instance.
You can use this to find the [`Component`](../../../reference/api#django_components.Component) in whose
template the [`{% fill %}`](../../../reference/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**
You can also pass any additional data along with the slot by setting it in [`Slot.extra`](../../../reference/api#django_components.Slot.extra):
@ -761,7 +784,6 @@ slot = Slot(
# Optional
component_name="table",
slot_name="name",
source="python",
extra={},
)
@ -771,6 +793,8 @@ slot.slot_name = "name"
slot.extra["foo"] = "bar"
```
Read more in [Pass slot metadata](../../advanced/extensions#pass-slot-metadata).
### Slot contents
Whether you create a slot from a function, a string, or from the [`{% fill %}`](../../../reference/template_tags#fill) tags,