mirror of
https://github.com/django-components/django-components.git
synced 2025-07-07 17:34:59 +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
67
CHANGELOG.md
67
CHANGELOG.md
|
@ -158,12 +158,17 @@ Summary:
|
|||
```py
|
||||
def render_to_response(
|
||||
context: Optional[Union[Dict[str, Any], Context]] = None,
|
||||
args: Optional[Tuple[Any, ...]] = None,
|
||||
kwargs: Optional[Mapping] = None,
|
||||
slots: Optional[Mapping] = None,
|
||||
args: Optional[Any] = None,
|
||||
kwargs: Optional[Any] = None,
|
||||
slots: Optional[Any] = None,
|
||||
deps_strategy: DependenciesStrategy = "document",
|
||||
render_dependencies: bool = True,
|
||||
type: Optional[DependenciesStrategy] = None, # Deprecated, use `deps_strategy`
|
||||
render_dependencies: bool = True, # Deprecated, use `deps_strategy="ignore"`
|
||||
outer_context: Optional[Context] = None,
|
||||
request: Optional[HttpRequest] = None,
|
||||
registry: Optional[ComponentRegistry] = None,
|
||||
registered_name: Optional[str] = None,
|
||||
node: Optional[ComponentNode] = None,
|
||||
**response_kwargs: Any,
|
||||
) -> HttpResponse:
|
||||
```
|
||||
|
@ -1008,6 +1013,13 @@ Summary:
|
|||
|
||||
Then, the `contents` attribute of the `BaseNode` instance will contain the string `"Hello, world!"`.
|
||||
|
||||
- The `BaseNode` class also has two new metadata attributes:
|
||||
|
||||
- `template_name` - the name of the template that rendered the node.
|
||||
- `template_component` - the component class that the template belongs to.
|
||||
|
||||
This is useful for debugging purposes.
|
||||
|
||||
- `Slot` class now has 3 new metadata fields:
|
||||
|
||||
1. `Slot.contents` attribute contains the original contents:
|
||||
|
@ -1018,10 +1030,11 @@ Summary:
|
|||
|
||||
2. `Slot.extra` attribute where you can put arbitrary metadata about the slot.
|
||||
|
||||
3. `Slot.source` attribute tells where the slot comes from:
|
||||
3. `Slot.fill_node` attribute tells where the slot comes from:
|
||||
|
||||
- `'template'` if the slot was created from `{% fill %}` tag.
|
||||
- `'python'` if the slot was created from string, function, or `Slot` instance.
|
||||
- `FillNode` instance if the slot was created from `{% fill %}` tag.
|
||||
- `ComponentNode` instance if the slot was created as a default slot from a `{% component %}` tag.
|
||||
- `None` if the slot was created from a string, function, or `Slot` instance.
|
||||
|
||||
See [Slot metadata](https://django-components.github.io/django-components/0.140/concepts/fundamentals/slots/#slot-metadata).
|
||||
|
||||
|
@ -1048,6 +1061,46 @@ Summary:
|
|||
{% endcomponent %}
|
||||
```
|
||||
|
||||
- You can now access the `{% component %}` tag (`ComponentNode` instance) from which a Component
|
||||
was created. Use `Component.node` to access it.
|
||||
|
||||
This is mostly useful for extensions, which can use this to detect if the given Component
|
||||
comes from a `{% component %}` tag or from a different source (such as `Component.render()`).
|
||||
|
||||
`Component.node` is `None` if the component is created by `Component.render()` (but you
|
||||
can pass in the `node` kwarg yourself).
|
||||
|
||||
```py
|
||||
class MyComponent(Component):
|
||||
def get_template_data(self, context, template):
|
||||
if self.node is not None:
|
||||
assert self.node.name == "my_component"
|
||||
```
|
||||
|
||||
- Node classes `ComponentNode`, `FillNode`, `ProvideNode`, and `SlotNode` are part of the public API.
|
||||
|
||||
These classes are what is instantiated when you use `{% component %}`, `{% fill %}`, `{% provide %}`, and `{% slot %}` tags.
|
||||
|
||||
You can for example use these for type hints:
|
||||
|
||||
```py
|
||||
from django_components import Component, ComponentNode
|
||||
|
||||
class MyTable(Component):
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
if kwargs.get("show_owner"):
|
||||
node: Optional[ComponentNode] = self.node
|
||||
owner: Optional[Component] = self.node.template_component
|
||||
else:
|
||||
node = None
|
||||
owner = None
|
||||
|
||||
return {
|
||||
"owner": owner,
|
||||
"node": node,
|
||||
}
|
||||
```
|
||||
|
||||
- Component caching can now take slots into account, by setting `Component.Cache.include_slots` to `True`.
|
||||
|
||||
```py
|
||||
|
|
|
@ -153,12 +153,14 @@ GreetNode.register(library)
|
|||
|
||||
When using [`BaseNode`](../../../reference/api#django_components.BaseNode), you have access to several useful properties:
|
||||
|
||||
- `node_id`: A unique identifier for this node instance
|
||||
- `flags`: Dictionary of flag values (e.g. `{"required": True}`)
|
||||
- `params`: List of raw parameters passed to the tag
|
||||
- `nodelist`: The template nodes between the start and end tags
|
||||
- `contents`: The raw contents between the start and end tags
|
||||
- `active_flags`: List of flags that are currently set to True
|
||||
- [`node_id`](../../../reference/api#django_components.BaseNode.node_id): A unique identifier for this node instance
|
||||
- [`flags`](../../../reference/api#django_components.BaseNode.flags): Dictionary of flag values (e.g. `{"required": True}`)
|
||||
- [`params`](../../../reference/api#django_components.BaseNode.params): List of raw parameters passed to the tag
|
||||
- [`nodelist`](../../../reference/api#django_components.BaseNode.nodelist): The template nodes between the start and end tags
|
||||
- [`contents`](../../../reference/api#django_components.BaseNode.contents): The raw contents between the start and end tags
|
||||
- [`active_flags`](../../../reference/api#django_components.BaseNode.active_flags): List of flags that are currently set to True
|
||||
- [`template_name`](../../../reference/api#django_components.BaseNode.template_name): The name of the `Template` instance inside which the node was defined
|
||||
- [`template_component`](../../../reference/api#django_components.BaseNode.template_component): The component class that the `Template` belongs to
|
||||
|
||||
This is what the `node` parameter in the [`@template_tag`](../../../reference/api#django_components.template_tag) decorator gives you access to - it's the instance of the node class that was automatically created for your template tag.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -47,6 +47,10 @@
|
|||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.ComponentNode
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.ComponentRegistry
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
@ -83,6 +87,14 @@
|
|||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.FillNode
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.ProvideNode
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.RegistrySettings
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
@ -111,6 +123,10 @@
|
|||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.SlotNode
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
||||
::: django_components.SlotRef
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
|
|
|
@ -204,6 +204,7 @@ name | type | description
|
|||
`slot_is_default` | `bool` | Whether the slot is default
|
||||
`slot_is_required` | `bool` | Whether the slot is required
|
||||
`slot_name` | `str` | The name of the `{% slot %}` tag
|
||||
`slot_node` | `SlotNode` | The node instance of the `{% slot %}` tag
|
||||
|
||||
## Objects
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ If you insert this tag multiple times, ALL JS scripts will be duplicately insert
|
|||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L3301" target="_blank">See source code</a>
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L3350" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
|
@ -75,7 +75,7 @@ Renders one of the components that was previously registered with
|
|||
[`@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.
|
||||
|
@ -92,7 +92,8 @@ The component name must be a string literal.
|
|||
### 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 %}
|
||||
|
@ -102,7 +103,7 @@ If the component defined any [slots](../concepts/fundamentals/slots.md), you can
|
|||
{% 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:
|
||||
|
@ -141,7 +142,7 @@ COMPONENTS = {
|
|||
}
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
@ -169,19 +170,20 @@ COMPONENTS = {
|
|||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L948" target="_blank">See source code</a>
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L988" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
@ -266,7 +268,7 @@ Fill:
|
|||
### 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" %}
|
||||
|
@ -280,7 +282,7 @@ use `{% fill %}` with `name` set to `"default"`:
|
|||
### 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)
|
||||
|
@ -296,7 +298,7 @@ class Table(Component):
|
|||
}
|
||||
```
|
||||
|
||||
Then pass the slot to the `{% fill %}` tag:
|
||||
Then pass the slot to the [`{% fill %}`](../template_tags#fill) tag:
|
||||
|
||||
```django
|
||||
{% component "table" %}
|
||||
|
@ -306,7 +308,7 @@ Then pass the slot to the `{% fill %}` tag:
|
|||
|
||||
!!! 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
|
||||
|
@ -391,8 +393,11 @@ See more usage examples in
|
|||
|
||||
|
||||
|
||||
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).
|
||||
|
||||
|
@ -445,7 +450,7 @@ class Child(Component):
|
|||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
@ -467,11 +472,11 @@ user = self.inject("user_data")["user"]
|
|||
|
||||
|
||||
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L474" target="_blank">See source code</a>
|
||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L513" target="_blank">See source code</a>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
@ -485,7 +490,7 @@ or [React's `children`](https://react.dev/learn/passing-props-to-a-component#pas
|
|||
|
||||
- `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.
|
||||
|
@ -527,8 +532,8 @@ class Parent(Component):
|
|||
|
||||
### 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).
|
||||
|
||||
|
@ -565,8 +570,8 @@ class Parent(Component):
|
|||
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
|
||||
|
|
|
@ -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, {}),
|
||||
)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ Tests focusing on the Component class.
|
|||
For tests focusing on the `component` tag, see `test_templatetags_component.py`
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
|
@ -17,6 +18,7 @@ from pytest_django.asserts import assertHTMLEqual, assertInHTML
|
|||
|
||||
from django_components import (
|
||||
Component,
|
||||
ComponentRegistry,
|
||||
ComponentView,
|
||||
Slot,
|
||||
SlotInput,
|
||||
|
@ -598,6 +600,110 @@ class TestComponentRenderAPI:
|
|||
assert comp.slots == {} # type: ignore[attr-defined]
|
||||
assert comp.context == Context() # type: ignore[attr-defined]
|
||||
|
||||
def test_metadata__template(self):
|
||||
comp: Any = None
|
||||
|
||||
@register("test")
|
||||
class TestComponent(Component):
|
||||
template = "hello"
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
nonlocal comp
|
||||
comp = self
|
||||
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<div class="test-component">
|
||||
{% component "test" / %}
|
||||
</div>
|
||||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context())
|
||||
|
||||
assertHTMLEqual(rendered, '<div class="test-component">hello</div>')
|
||||
|
||||
assert isinstance(comp, TestComponent)
|
||||
|
||||
assert isinstance(comp.outer_context, Context)
|
||||
assert comp.outer_context == Context()
|
||||
|
||||
assert isinstance(comp.registry, ComponentRegistry)
|
||||
assert comp.registered_name == "test"
|
||||
|
||||
assert comp.node is not None
|
||||
assert comp.node.template_component is None
|
||||
assert comp.node.template_name == "<unknown source>"
|
||||
|
||||
def test_metadata__component(self):
|
||||
comp: Any = None
|
||||
|
||||
@register("test")
|
||||
class TestComponent(Component):
|
||||
template = "hello"
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
nonlocal comp
|
||||
comp = self
|
||||
|
||||
class Outer(Component):
|
||||
template = "{% component 'test' only / %}"
|
||||
|
||||
rendered = Outer.render()
|
||||
|
||||
assert rendered == 'hello'
|
||||
|
||||
assert isinstance(comp, TestComponent)
|
||||
|
||||
assert isinstance(comp.outer_context, Context)
|
||||
assert comp.outer_context is not comp.context
|
||||
|
||||
assert isinstance(comp.registry, ComponentRegistry)
|
||||
assert comp.registered_name == "test"
|
||||
|
||||
assert comp.node is not None
|
||||
assert comp.node.template_component == Outer
|
||||
|
||||
if os.name == "nt":
|
||||
assert comp.node.template_name.endswith("tests\\test_component.py::Outer") # type: ignore
|
||||
else:
|
||||
assert comp.node.template_name.endswith("tests/test_component.py::Outer") # type: ignore
|
||||
|
||||
def test_metadata__python(self):
|
||||
comp: Any = None
|
||||
|
||||
@register("test")
|
||||
class TestComponent(Component):
|
||||
template = "hello"
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
nonlocal comp
|
||||
comp = self
|
||||
|
||||
rendered = TestComponent.render(
|
||||
context=Context(),
|
||||
args=(),
|
||||
kwargs={},
|
||||
slots={},
|
||||
deps_strategy="document",
|
||||
render_dependencies=True,
|
||||
request=None,
|
||||
outer_context=Context(),
|
||||
registry=ComponentRegistry(),
|
||||
registered_name="test",
|
||||
)
|
||||
|
||||
assert rendered == 'hello'
|
||||
|
||||
assert isinstance(comp, TestComponent)
|
||||
|
||||
assert isinstance(comp.outer_context, Context)
|
||||
assert comp.outer_context == Context()
|
||||
|
||||
assert isinstance(comp.registry, ComponentRegistry)
|
||||
assert comp.registered_name == "test"
|
||||
|
||||
assert comp.node is None
|
||||
|
||||
|
||||
@djc_test
|
||||
class TestComponentTemplateVars:
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.http import HttpRequest, HttpResponse
|
|||
from django.template import Context
|
||||
from django.test import Client
|
||||
|
||||
from django_components import Component, Slot, register, registry
|
||||
from django_components import Component, Slot, SlotNode, register, registry
|
||||
from django_components.app_settings import app_settings
|
||||
from django_components.component_registry import ComponentRegistry
|
||||
from django_components.extension import (
|
||||
|
@ -140,6 +140,13 @@ class RenderExtension(ComponentExtension):
|
|||
name = "render"
|
||||
|
||||
|
||||
class SlotOverrideExtension(ComponentExtension):
|
||||
name = "slot_override"
|
||||
|
||||
def on_slot_rendered(self, ctx: OnSlotRenderedContext):
|
||||
return "OVERRIDEN BY EXTENSION"
|
||||
|
||||
|
||||
def with_component_cls(on_created: Callable):
|
||||
class TempComponent(Component):
|
||||
template = "Hello {{ name }}!"
|
||||
|
@ -341,13 +348,15 @@ class TestExtensionHooks:
|
|||
|
||||
# Render the component with some args and kwargs
|
||||
test_context = Context({"foo": "bar"})
|
||||
TestComponent.render(
|
||||
rendered = TestComponent.render(
|
||||
context=test_context,
|
||||
args=("arg1", "arg2"),
|
||||
kwargs={"name": "Test"},
|
||||
slots={"content": "Some content"},
|
||||
)
|
||||
|
||||
assert rendered == "Hello Some content!"
|
||||
|
||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
||||
|
||||
# Verify on_slot_rendered was called with correct args
|
||||
|
@ -359,10 +368,25 @@ class TestExtensionHooks:
|
|||
assert slot_call.component_id == "ca1bc3e"
|
||||
assert isinstance(slot_call.slot, Slot)
|
||||
assert slot_call.slot_name == "content"
|
||||
assert isinstance(slot_call.slot_node, SlotNode)
|
||||
assert slot_call.slot_node.template_name.endswith("test_extension.py::TestComponent") # type: ignore
|
||||
assert slot_call.slot_node.template_component == TestComponent
|
||||
assert slot_call.slot_is_required is True
|
||||
assert slot_call.slot_is_default is True
|
||||
assert slot_call.result == "Some content"
|
||||
|
||||
@djc_test(components_settings={"extensions": [SlotOverrideExtension]})
|
||||
def test_on_slot_rendered__override(self):
|
||||
@register("test_comp")
|
||||
class TestComponent(Component):
|
||||
template = "Hello {% slot 'content' required default / %}!"
|
||||
|
||||
rendered = TestComponent.render(
|
||||
slots={"content": "Some content"},
|
||||
)
|
||||
|
||||
assert rendered == "Hello OVERRIDEN BY EXTENSION!"
|
||||
|
||||
|
||||
@djc_test
|
||||
class TestExtensionViews:
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import inspect
|
||||
import os
|
||||
import re
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from django.template import Context, Template
|
||||
from django.template.base import TextNode, VariableNode
|
||||
from django.template.defaulttags import IfNode, LoremNode
|
||||
from django.template.exceptions import TemplateSyntaxError
|
||||
|
||||
from django_components import types
|
||||
from django_components import Component, types
|
||||
from django_components.node import BaseNode, template_tag
|
||||
from django_components.templatetags import component_tags
|
||||
from django_components.util.tag_parser import TagAttr
|
||||
|
@ -849,7 +852,14 @@ class TestSignatureBasedValidation:
|
|||
@force_signature_validation
|
||||
def render(self, context: Context, name: str, **kwargs) -> str:
|
||||
nonlocal captured
|
||||
captured = self.params, self.nodelist, self.node_id, self.contents
|
||||
captured = (
|
||||
self.params,
|
||||
self.nodelist,
|
||||
self.node_id,
|
||||
self.contents,
|
||||
self.template_name,
|
||||
self.template_component,
|
||||
)
|
||||
return f"Hello, {name}!"
|
||||
|
||||
# Case 1 - Node with end tag and NOT self-closing
|
||||
|
@ -864,7 +874,7 @@ class TestSignatureBasedValidation:
|
|||
template1 = Template(template_str1)
|
||||
template1.render(Context({}))
|
||||
|
||||
params1, nodelist1, node_id1, contents1 = captured # type: ignore
|
||||
params1, nodelist1, node_id1, contents1, template_name1, template_component1 = captured # type: ignore
|
||||
assert len(params1) == 1
|
||||
assert isinstance(params1[0], TagAttr)
|
||||
# NOTE: The comment node is not included in the nodelist
|
||||
|
@ -879,6 +889,8 @@ class TestSignatureBasedValidation:
|
|||
assert isinstance(nodelist1[7], TextNode)
|
||||
assert contents1 == "\n INSIDE TAG {{ my_var }} {# comment #} {% lorem 1 w %} {% if True %} henlo {% endif %}\n " # noqa: E501
|
||||
assert node_id1 == "a1bc3e"
|
||||
assert template_name1 == '<unknown source>'
|
||||
assert template_component1 is None
|
||||
|
||||
captured = None # Reset captured
|
||||
|
||||
|
@ -890,12 +902,14 @@ class TestSignatureBasedValidation:
|
|||
template2 = Template(template_str2)
|
||||
template2.render(Context({}))
|
||||
|
||||
params2, nodelist2, node_id2, contents2 = captured # type: ignore
|
||||
params2, nodelist2, node_id2, contents2, template_name2, template_component2 = captured # type: ignore
|
||||
assert len(params2) == 1 # type: ignore
|
||||
assert isinstance(params2[0], TagAttr) # type: ignore
|
||||
assert len(nodelist2) == 0 # type: ignore
|
||||
assert contents2 is None # type: ignore
|
||||
assert node_id2 == "a1bc3f" # type: ignore
|
||||
assert template_name2 == '<unknown source>' # type: ignore
|
||||
assert template_component2 is None # type: ignore
|
||||
|
||||
captured = None # Reset captured
|
||||
|
||||
|
@ -906,7 +920,7 @@ class TestSignatureBasedValidation:
|
|||
@force_signature_validation
|
||||
def render(self, context: Context, name: str, **kwargs) -> str:
|
||||
nonlocal captured
|
||||
captured = self.params, self.nodelist, self.node_id, self.contents
|
||||
captured = self.params, self.nodelist, self.node_id, self.contents, self.template_name, self.template_component # noqa: E501
|
||||
return f"Hello, {name}!"
|
||||
|
||||
TestNodeWithoutEndTag.register(component_tags.register)
|
||||
|
@ -918,12 +932,38 @@ class TestSignatureBasedValidation:
|
|||
template3 = Template(template_str3)
|
||||
template3.render(Context({}))
|
||||
|
||||
params3, nodelist3, node_id3, contents3 = captured # type: ignore
|
||||
params3, nodelist3, node_id3, contents3, template_name3, template_component3 = captured # type: ignore
|
||||
assert len(params3) == 1 # type: ignore
|
||||
assert isinstance(params3[0], TagAttr) # type: ignore
|
||||
assert len(nodelist3) == 0 # type: ignore
|
||||
assert contents3 is None # type: ignore
|
||||
assert node_id3 == "a1bc40" # type: ignore
|
||||
assert template_name3 == '<unknown source>' # type: ignore
|
||||
assert template_component3 is None # type: ignore
|
||||
|
||||
# Case 4 - Node nested in Component end tag
|
||||
class TestComponent(Component):
|
||||
template = """
|
||||
{% load component_tags %}
|
||||
{% mytag2 'John' %}
|
||||
"""
|
||||
|
||||
TestComponent.render(Context({}))
|
||||
|
||||
params4, nodelist4, node_id4, contents4, template_name4, template_component4 = captured # type: ignore
|
||||
assert len(params4) == 1 # type: ignore
|
||||
assert isinstance(params4[0], TagAttr) # type: ignore
|
||||
assert len(nodelist4) == 0 # type: ignore
|
||||
assert contents4 is None # type: ignore
|
||||
assert node_id4 == "a1bc42" # type: ignore
|
||||
|
||||
if os.name == "nt":
|
||||
assert cast(str, template_name4).endswith("\\tests\\test_node.py::TestComponent") # type: ignore
|
||||
else:
|
||||
assert cast(str, template_name4).endswith("/tests/test_node.py::TestComponent") # type: ignore
|
||||
|
||||
assert template_name4 == f"{__file__}::TestComponent" # type: ignore
|
||||
assert template_component4 is TestComponent # type: ignore
|
||||
|
||||
# Cleanup
|
||||
TestNodeWithEndTag.unregister(component_tags.register)
|
||||
|
|
|
@ -12,7 +12,8 @@ from django.template.base import NodeList, TextNode
|
|||
from pytest_django.asserts import assertHTMLEqual
|
||||
|
||||
from django_components import Component, register, types
|
||||
from django_components.slots import Slot, SlotContext, SlotFallback
|
||||
from django_components.component import ComponentNode
|
||||
from django_components.slots import FillNode, Slot, SlotContext, SlotFallback
|
||||
|
||||
from django_components.testing import djc_test
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
@ -431,7 +432,7 @@ class TestSlot:
|
|||
assert isinstance(first_slot_func, Slot)
|
||||
assert first_slot_func.component_name == "SimpleComponent"
|
||||
assert first_slot_func.slot_name == "first"
|
||||
assert first_slot_func.source == "python"
|
||||
assert first_slot_func.fill_node is None
|
||||
assert first_slot_func.extra == {}
|
||||
|
||||
first_nodelist: NodeList = first_slot_func.nodelist
|
||||
|
@ -465,7 +466,7 @@ class TestSlot:
|
|||
assert isinstance(first_slot_func, Slot)
|
||||
assert first_slot_func.component_name == "SimpleComponent"
|
||||
assert first_slot_func.slot_name == "first"
|
||||
assert first_slot_func.source == "python"
|
||||
assert first_slot_func.fill_node is None
|
||||
assert first_slot_func.extra == {}
|
||||
assert first_slot_func.nodelist is None
|
||||
|
||||
|
@ -495,7 +496,7 @@ class TestSlot:
|
|||
assert isinstance(first_slot_func, Slot)
|
||||
assert first_slot_func.component_name == "SimpleComponent"
|
||||
assert first_slot_func.slot_name == "whoop"
|
||||
assert first_slot_func.source == "python"
|
||||
assert first_slot_func.fill_node is None
|
||||
assert first_slot_func.extra == {"foo": "bar"}
|
||||
assert first_slot_func.nodelist is None
|
||||
|
||||
|
@ -530,7 +531,7 @@ class TestSlot:
|
|||
assert isinstance(first_slot_func, Slot)
|
||||
assert first_slot_func.component_name == "test"
|
||||
assert first_slot_func.slot_name == "default"
|
||||
assert first_slot_func.source == "template"
|
||||
assert isinstance(first_slot_func.fill_node, ComponentNode)
|
||||
assert first_slot_func.extra == {}
|
||||
|
||||
first_nodelist: NodeList = first_slot_func.nodelist
|
||||
|
@ -571,7 +572,7 @@ class TestSlot:
|
|||
assert isinstance(first_slot_func, Slot)
|
||||
assert first_slot_func.component_name == "test"
|
||||
assert first_slot_func.slot_name == "first"
|
||||
assert first_slot_func.source == "template"
|
||||
assert isinstance(first_slot_func.fill_node, FillNode)
|
||||
assert first_slot_func.extra == {}
|
||||
|
||||
first_nodelist: NodeList = first_slot_func.nodelist
|
||||
|
@ -616,7 +617,7 @@ class TestSlot:
|
|||
assert isinstance(first_slot_func, Slot)
|
||||
assert first_slot_func.component_name == "test"
|
||||
assert first_slot_func.slot_name == "whoop"
|
||||
assert first_slot_func.source == "template"
|
||||
assert isinstance(first_slot_func.fill_node, FillNode)
|
||||
assert first_slot_func.extra == {"foo": "bar"}
|
||||
assert first_slot_func.nodelist is None
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue