mirror of
https://github.com/django-components/django-components.git
synced 2025-08-30 10:47:20 +00:00
fix: KeyError on component_context_cache when slot rendered outside (#1210)
This commit is contained in:
parent
6ff2d78a2f
commit
046569e16d
3 changed files with 71 additions and 7 deletions
|
@ -779,6 +779,8 @@ Summary:
|
|||
|
||||
- Fix bug: Context processors data was being generated anew for each component. Now the data is correctly created once and reused across components with the same request ([#1165](https://github.com/django-components/django-components/issues/1165)).
|
||||
|
||||
- Fix KeyError on `component_context_cache` when slots are rendered outside of the component's render context. ([#1189](https://github.com/django-components/django-components/issues/1189))
|
||||
|
||||
## v0.139.1
|
||||
|
||||
#### Fix
|
||||
|
|
|
@ -2680,9 +2680,8 @@ class Component(metaclass=ComponentMeta):
|
|||
request = getattr(context, "request", None)
|
||||
# Check if this is a nested component and whether parent has request
|
||||
if request is None:
|
||||
parent_id = context.get(_COMPONENT_CONTEXT_KEY, None)
|
||||
if parent_id:
|
||||
parent_comp_ctx = component_context_cache[parent_id]
|
||||
_, parent_comp_ctx = _get_parent_component_context(context)
|
||||
if parent_comp_ctx:
|
||||
request = parent_comp_ctx.request
|
||||
|
||||
# Allow to provide no args/kwargs/slots/context
|
||||
|
@ -2736,13 +2735,11 @@ class Component(metaclass=ComponentMeta):
|
|||
# We pass down the components the info about the component's parent.
|
||||
# This is used for correctly resolving slot fills, correct rendering order,
|
||||
# or CSS scoping.
|
||||
if context.get(_COMPONENT_CONTEXT_KEY, None):
|
||||
parent_id = cast(str, context[_COMPONENT_CONTEXT_KEY])
|
||||
parent_comp_ctx = component_context_cache[parent_id]
|
||||
parent_id, parent_comp_ctx = _get_parent_component_context(context)
|
||||
if parent_comp_ctx is not None:
|
||||
component_path = [*parent_comp_ctx.component_path, self.name]
|
||||
post_render_callbacks = parent_comp_ctx.post_render_callbacks
|
||||
else:
|
||||
parent_id = None
|
||||
component_path = [self.name]
|
||||
post_render_callbacks = {}
|
||||
|
||||
|
@ -3300,6 +3297,20 @@ class ComponentNode(BaseNode):
|
|||
return output
|
||||
|
||||
|
||||
def _get_parent_component_context(context: Context) -> Union[Tuple[None, None], Tuple[str, ComponentContext]]:
|
||||
parent_id = context.get(_COMPONENT_CONTEXT_KEY, None)
|
||||
if parent_id is None:
|
||||
return None, None
|
||||
|
||||
# NOTE: This may happen when slots are rendered outside of the component's render context.
|
||||
# See https://github.com/django-components/django-components/issues/1189
|
||||
if parent_id not in component_context_cache:
|
||||
return None, None
|
||||
|
||||
parent_comp_ctx = component_context_cache[parent_id]
|
||||
return parent_id, parent_comp_ctx
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _maybe_bind_template(context: Context, template: Template) -> Generator[None, Any, None]:
|
||||
if context.template is None:
|
||||
|
|
|
@ -474,3 +474,54 @@ class TestSlot:
|
|||
match=re.escape("Fill 'first' received content both through 'body' kwarg and '{% fill %}' body."),
|
||||
):
|
||||
template.render(Context({"my_slot": my_slot}))
|
||||
|
||||
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
||||
def test_slot_call_outside_render_context(self, components_settings):
|
||||
from django_components import register, Component
|
||||
|
||||
seen_slots = []
|
||||
|
||||
@register("MyTopLevelComponent")
|
||||
class MyTopLevelComponent(Component):
|
||||
template = """
|
||||
{% for thing in words %}
|
||||
{% component "MyComponentBeingLooped" / %}
|
||||
{% endfor %}
|
||||
"""
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
return {
|
||||
"words": ["apple", "car", "russia"],
|
||||
}
|
||||
|
||||
@register("MyComponentBeingLooped")
|
||||
class MyComponentBeingLooped(Component):
|
||||
template = """
|
||||
{% component "MyComponentWithASlot" %}
|
||||
{% fill "my_slot" %}
|
||||
{% component "MyInnerComponent" / %}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
|
||||
@register("MyInnerComponent")
|
||||
class MyInnerComponent(Component):
|
||||
template = "Hello!"
|
||||
|
||||
@register("MyComponentWithASlot")
|
||||
class MyComponentWithASlot(Component):
|
||||
template = "CAPTURER"
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
seen_slots.append(self.input.slots["my_slot"])
|
||||
|
||||
MyTopLevelComponent.render()
|
||||
|
||||
assert len(seen_slots) == 3
|
||||
|
||||
results = [slot().strip() for slot in seen_slots]
|
||||
assert results == [
|
||||
"<!-- _RENDERED MyInnerComponent_fb676b,ca1bc49,, -->Hello!",
|
||||
"<!-- _RENDERED MyInnerComponent_fb676b,ca1bc4a,, -->Hello!",
|
||||
"<!-- _RENDERED MyInnerComponent_fb676b,ca1bc4b,, -->Hello!",
|
||||
]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue