mirror of
https://github.com/django-components/django-components.git
synced 2025-08-18 13:10:13 +00:00
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:
parent
4d5fecf3ee
commit
f5fd3c02cd
5 changed files with 80 additions and 6 deletions
|
@ -1311,7 +1311,7 @@ tags:
|
||||||
{% endcomponent %}
|
{% 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!**
|
to construct the `{% fill %}` tags, **as long as these other tags do not leave any text behind!**
|
||||||
|
|
||||||
```django
|
```django
|
||||||
|
|
|
@ -57,6 +57,7 @@ from django_components.slots import (
|
||||||
SlotName,
|
SlotName,
|
||||||
SlotRef,
|
SlotRef,
|
||||||
SlotResult,
|
SlotResult,
|
||||||
|
_is_extracting_fill,
|
||||||
_nodelist_to_slot_render_func,
|
_nodelist_to_slot_render_func,
|
||||||
resolve_fills,
|
resolve_fills,
|
||||||
)
|
)
|
||||||
|
@ -894,6 +895,11 @@ class ComponentNode(BaseNode):
|
||||||
def render(self, context: Context) -> str:
|
def render(self, context: Context) -> str:
|
||||||
trace_msg("RENDR", "COMP", self.name, self.node_id)
|
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)
|
component_cls: Type[Component] = self.registry.get(self.name)
|
||||||
|
|
||||||
# Resolve FilterExpressions and Variables that were passed as args to the
|
# Resolve FilterExpressions and Variables that were passed as args to the
|
||||||
|
|
|
@ -399,7 +399,7 @@ class FillNode(BaseNode):
|
||||||
self.trace_id = trace_id
|
self.trace_id = trace_id
|
||||||
|
|
||||||
def render(self, context: Context) -> str:
|
def render(self, context: Context) -> str:
|
||||||
if self._is_extracting_fill(context):
|
if _is_extracting_fill(context):
|
||||||
self._extract_fill(context)
|
self._extract_fill(context)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@ -459,9 +459,6 @@ class FillNode(BaseNode):
|
||||||
|
|
||||||
return value
|
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:
|
def _extract_fill(self, context: Context) -> None:
|
||||||
# `FILL_GEN_CONTEXT_KEY` is only ever set when we are rendering content between the
|
# `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.
|
# `{% 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 rendered
|
||||||
|
|
||||||
return Slot(content_func=cast(SlotFunc, render_func))
|
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
|
||||||
|
|
|
@ -77,7 +77,7 @@ tags:
|
||||||
{% endcomponent %}
|
{% 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!**
|
to construct the `{% fill %}` tags, **as long as these other tags do not leave any text behind!**
|
||||||
|
|
||||||
```django
|
```django
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from django.template import Context, Template, TemplateSyntaxError
|
from django.template import Context, Template, TemplateSyntaxError
|
||||||
|
|
||||||
from django_components import Component, register, types
|
from django_components import Component, register, types
|
||||||
|
@ -737,3 +739,68 @@ class InjectTest(BaseTestCase):
|
||||||
comp = InjectComponent("")
|
comp = InjectComponent("")
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
comp.inject("abc", "def")
|
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>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue