fix: do NOT render component tags when parsing {% component %} body for fills (#778)

* refactor: do NOT render component tags when parsing {% component %} body for fills

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* refactor: fix typo

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Juro Oravec 2024-11-26 14:52:55 +01:00 committed by GitHub
parent 4d5fecf3ee
commit f5fd3c02cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 80 additions and 6 deletions

View file

@ -1311,7 +1311,7 @@ tags:
{% endcomponent %}
```
You can also use `{% for %}`, `{% with %}`, or other tags (even `{% include %}`)
You can also use `{% for %}`, `{% with %}`, or other non-component tags (even `{% include %}`)
to construct the `{% fill %}` tags, **as long as these other tags do not leave any text behind!**
```django

View file

@ -57,6 +57,7 @@ from django_components.slots import (
SlotName,
SlotRef,
SlotResult,
_is_extracting_fill,
_nodelist_to_slot_render_func,
resolve_fills,
)
@ -894,6 +895,11 @@ class ComponentNode(BaseNode):
def render(self, context: Context) -> str:
trace_msg("RENDR", "COMP", self.name, self.node_id)
# Do not render nested `{% component %}` tags in other `{% component %}` tags
# at the stage when we are determining if the latter has named fills or not.
if _is_extracting_fill(context):
return ""
component_cls: Type[Component] = self.registry.get(self.name)
# Resolve FilterExpressions and Variables that were passed as args to the

View file

@ -399,7 +399,7 @@ class FillNode(BaseNode):
self.trace_id = trace_id
def render(self, context: Context) -> str:
if self._is_extracting_fill(context):
if _is_extracting_fill(context):
self._extract_fill(context)
return ""
@ -459,9 +459,6 @@ class FillNode(BaseNode):
return value
def _is_extracting_fill(self, context: Context) -> bool:
return context.get(FILL_GEN_CONTEXT_KEY, None) is not None
def _extract_fill(self, context: Context) -> None:
# `FILL_GEN_CONTEXT_KEY` is only ever set when we are rendering content between the
# `{% component %}...{% endcomponent %}` tags. This is done in order to collect all fill tags.
@ -775,3 +772,7 @@ def _nodelist_to_slot_render_func(
return rendered
return Slot(content_func=cast(SlotFunc, render_func))
def _is_extracting_fill(context: Context) -> bool:
return context.get(FILL_GEN_CONTEXT_KEY, None) is not None

View file

@ -77,7 +77,7 @@ tags:
{% endcomponent %}
```
You can also use `{% for %}`, `{% with %}`, or other tags (even `{% include %}`)
You can also use `{% for %}`, `{% with %}`, or other non-component tags (even `{% include %}`)
to construct the `{% fill %}` tags, **as long as these other tags do not leave any text behind!**
```django

View file

@ -1,3 +1,5 @@
from typing import Any
from django.template import Context, Template, TemplateSyntaxError
from django_components import Component, register, types
@ -737,3 +739,68 @@ class InjectTest(BaseTestCase):
comp = InjectComponent("")
with self.assertRaises(RuntimeError):
comp.inject("abc", "def")
@parametrize_context_behavior(["django", "isolated"])
def test_inject_in_fill(self):
@register("injectee")
class Injectee(Component):
template: types.django_html = """
{% load component_tags %}
<div> injected: {{ data|safe }} </div>
<main>
{% slot "content" default / %}
</main>
"""
def get_context_data(self):
data = self.inject("my_provide")
return {"data": data}
@register("provider")
class Provider(Component):
def get_context_data(self, data: Any) -> Any:
return {"data": data}
template: types.django_html = """
{% load component_tags %}
{% provide "my_provide" key="hi" data=data %}
{% slot "content" default / %}
{% endprovide %}
"""
@register("parent")
class Parent(Component):
def get_context_data(self, data: Any) -> Any:
return {"data": data}
template: types.django_html = """
{% load component_tags %}
{% component "provider" data=data %}
{% component "injectee" %}
{% slot "content" default / %}
{% endcomponent %}
{% endcomponent %}
"""
@register("root")
class Root(Component):
template: types.django_html = """
{% load component_tags %}
{% component "parent" data=123 %}
{% fill "content" %}
456
{% endfill %}
{% endcomponent %}
"""
rendered = Root.render()
self.assertHTMLEqual(
rendered,
"""
<div>
injected: DepInject(key='hi', data=123)
</div>
<main>456</main>
""",
)