refactor: fix bug with complex slots and "django" mode + add docs on debugging with AI agents (#956)

This commit is contained in:
Juro Oravec 2025-02-11 10:57:37 +01:00 committed by GitHub
parent be4d744d64
commit eb3f72ee0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 601 additions and 148 deletions

320
README.md
View file

@ -11,17 +11,24 @@
> >
> Report any broken links links in [#922](https://github.com/django-components/django-components/issues/922). > Report any broken links links in [#922](https://github.com/django-components/django-components/issues/922).
Django-components is a package that introduces component-based architecture to Django's server-side rendering. It aims to combine Django's templating system with the modularity seen in modern frontend frameworks. `django-components` combines Django's templating system with the modularity seen
in modern frontend frameworks like Vue or React.
With `django-components` you can support Django projects small and large without leaving the Django ecosystem.
## Quickstart
A component in django-components can be as simple as a Django template and Python code to declare the component: A component in django-components can be as simple as a Django template and Python code to declare the component:
```htmldjango title="calendar.html" ```django
{# components/calendar/calendar.html #}
<div class="calendar"> <div class="calendar">
Today's date is <span>{{ date }}</span> Today's date is <span>{{ date }}</span>
</div> </div>
``` ```
```py title="calendar.py" ```py
# components/calendar/calendar.html
from django_components import Component from django_components import Component
class Calendar(Component): class Calendar(Component):
@ -30,113 +37,280 @@ class Calendar(Component):
Or a combination of Django template, Python, CSS, and Javascript: Or a combination of Django template, Python, CSS, and Javascript:
```htmldjango title="calendar.html" ```django
{# components/calendar/calendar.html #}
<div class="calendar"> <div class="calendar">
Today's date is <span>{{ date }}</span> Today's date is <span>{{ date }}</span>
</div> </div>
``` ```
```css title="calendar.css" ```css
/* components/calendar/calendar.css */
.calendar { .calendar {
width: 200px; width: 200px;
background: pink; background: pink;
} }
``` ```
```js title="calendar.js" ```js
document.querySelector(".calendar").onclick = function () { /* components/calendar/calendar.js */
document.querySelector(".calendar").onclick = () => {
alert("Clicked calendar!"); alert("Clicked calendar!");
}; };
``` ```
```py title="calendar.py" ```py
# components/calendar/calendar.py
from django_components import Component from django_components import Component
class Calendar(Component): class Calendar(Component):
template_file = "calendar.html" template_file = "calendar.html"
js_file = "calendar.js" js_file = "calendar.js"
css_file = "calendar.css" css_file = "calendar.css"
```
Alternatively, you can "inline" HTML, JS, and CSS right into the component class:
```py
from django_components import Component
class Calendar(Component):
template = """
<div class="calendar">
Today's date is <span>{{ date }}</span>
</div>
"""
css = """
.calendar {
width: 200px;
background: pink;
}
"""
js = """
document.querySelector(".calendar").onclick = function () {
alert("Clicked calendar!");
};
"""
```
## Features
1. 🧩 **Reusability:** Allows creation of self-contained, reusable UI elements.
2. 📦 **Encapsulation:** Each component can include its own HTML, CSS, and JavaScript.
3. 🚀 **Server-side rendering:** Components render on the server, improving initial load times and SEO.
4. 🐍 **Django integration:** Works within the Django ecosystem, using familiar concepts like template tags.
5. ⚡ **Asynchronous loading:** Components can render independently opening up for integration with JS frameworks like HTMX or AlpineJS.
Potential benefits:
- 🔄 Reduced code duplication
- 🛠️ Improved maintainability through modular design
- 🧠 Easier management of complex UIs
- 🤝 Enhanced collaboration between frontend and backend developers
Django-components can be particularly useful for larger Django projects that require a more structured approach to UI development, without necessitating a shift to a separate frontend framework.
## Quickstart
django-components lets you create reusable blocks of code needed to generate the front end code you need for a modern app.
Define a component in `components/calendar/calendar.py` like this:
```python
@register("calendar")
class Calendar(Component):
template_file = "template.html"
def get_context_data(self, date): def get_context_data(self, date):
return {"date": date} return {"date": date}
``` ```
With this `template.html` file:
```htmldjango
<div class="calendar-component">Today's date is <span>{{ date }}</span></div>
```
Use the component like this: Use the component like this:
```htmldjango ```django
{% component "calendar" date="2024-11-06" %}{% endcomponent %} {% component "calendar" date="2024-11-06" %}{% endcomponent %}
``` ```
And this is what gets rendered: And this is what gets rendered:
```html ```html
<div class="calendar-component">Today's date is <span>2024-11-06</span></div> <div class="calendar-component">
Today's date is <span>2024-11-06</span>
</div>
``` ```
### <table><td>[Read the full documentation](https://django-components.github.io/django-components/latest/)</td></table> ## Features
... or jump right into the code, [check out the example project](https://github.com/django-components/django-components/tree/master/sampleproject)) ### Modern and modular UI
- Create self-contained, reusable UI elements.
- Each component can include its own HTML, CSS, and JS, or additional third-party JS and CSS.
- HTML, CSS, and JS can be defined on the component class, or loaded from files.
```python
from django_components import Component
@register("calendar")
class Calendar(Component):
template = """
<div class="calendar">
Today's date is
<span>{{ date }}</span>
</div>
"""
css = """
.calendar {
width: 200px;
background: pink;
}
"""
js = """
document.querySelector(".calendar")
.addEventListener("click", () => {
alert("Clicked calendar!");
});
"""
# Additional JS and CSS
class Media:
js = ["https://cdn.jsdelivr.net/npm/htmx.org@2.1.1/dist/htmx.min.js"]
css = ["bootstrap/dist/css/bootstrap.min.css"]
# Variables available in the template
def get_context_data(self, date):
return {
"date": date
}
```
### Composition with slots
- Render components inside templates with `{% component %}` tag.
- Compose them with `{% slot %}` and `{% fill %}` tags.
- Vue-like slot system, including scoped slots.
```django
{% component "Layout"
bookmarks=bookmarks
breadcrumbs=breadcrumbs
%}
{% fill "header" %}
<div class="flex justify-between gap-x-12">
<div class="prose">
<h3>{{ project.name }}</h3>
</div>
<div class="font-semibold text-gray-500">
{{ project.start_date }} - {{ project.end_date }}
</div>
</div>
{% endfill %}
{# Access data passed to `{% slot %}` with `data` #}
{% fill "tabs" data="tabs_data" %}
{% component "TabItem" header="Project Info" %}
{% component "ProjectInfo"
project=project
project_tags=project_tags
attrs:class="py-5"
attrs:width=tabs_data.width
/ %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
```
### Extended template tags
`django-components` extends Django's template tags syntax with:
- Literal lists and dictionaries in template tags
- Self-closing tags `{% mytag / %}`
- Multi-line template tags
- Spread operator `...` to dynamically pass args or kwargs into the template tag
- Nested template tags like `"{{ first_name }} {{ last_name }}"`
- Flat definition of dictionary keys `attr:key=val`
```django
{% component "table"
...default_attrs
title="Friend list for {{ user.name }}"
headers=["Name", "Age", "Email"]
data=[
{
"name": "John"|upper,
"age": 30|add:1,
"email": "john@example.com",
"hobbies": ["reading"],
},
{
"name": "Jane"|upper,
"age": 25|add:1,
"email": "jane@example.com",
"hobbies": ["reading", "coding"],
},
],
attrs:class="py-4 ma-2 border-2 border-gray-300 rounded-md"
/ %}
```
### HTML fragment support
`django-components` makes intergration with HTMX, AlpineJS or jQuery easy by allowing components to be rendered as HTML fragments:
- Components's JS and CSS is loaded automatically when the fragment is inserted into the DOM.
- Expose components as views with `get`, `post`, `put`, `patch`, `delete` methods
```py
# components/calendar/calendar.py
@register("calendar")
class Calendar(Component):
template_file = "calendar.html"
def get(self, request, *args, **kwargs):
page = request.GET.get("page", 1)
return self.render_to_response(
kwargs={
"page": page,
}
)
def get_context_data(self, page):
return {
"page": page,
}
# urls.py
path("calendar/", Calendar.as_view()),
```
### Type hints
Opt-in to type hints by defining types for component's args, kwargs, slots, and more:
```py
from typing import NotRequired, Tuple, TypedDict, SlotContent, SlotFunc
ButtonArgs = Tuple[int, str]
class ButtonKwargs(TypedDict):
variable: str
another: int
maybe_var: NotRequired[int] # May be omitted
class ButtonData(TypedDict):
variable: str
class ButtonSlots(TypedDict):
my_slot: NotRequired[SlotFunc]
another_slot: SlotContent
ButtonType = Component[ButtonArgs, ButtonKwargs, ButtonSlots, ButtonData, JsData, CssData]
class Button(ButtonType):
def get_context_data(self, *args, **kwargs):
self.input.args[0] # int
self.input.kwargs["variable"] # str
self.input.slots["my_slot"] # SlotFunc[MySlotData]
return {} # Error: Key "variable" is missing
```
When you then call `Button.render()` or `Button.render_to_response()`, you will get type hints:
```py
Button.render(
# Error: First arg must be `int`, got `float`
args=(1.25, "abc"),
# Error: Key "another" is missing
kwargs={
"variable": "text",
},
)
```
### Debugging features
- **Visual component inspection**: Highlight components and slots directly in your browser.
- **Detailed tracing logs to supply AI-agents with context**: The logs include component and slot names and IDs, and their position in the tree.
<div style="text-align: center;">
<img src="https://github.com/django-components/django-components/blob/master/docs/images/debug-highlight-slots.png?raw=true" alt="Component debugging visualization showing slot highlighting" width="500" style="margin: auto;">
</div>
### Sharing components
- Install and use third-party components from PyPI
- Or publish your own "component registry"
- Highly customizable - Choose how the components are called in the template, and more:
```django
{% component "calendar" date="2024-11-06" %}
{% endcomponent %}
{% calendar date="2024-11-06" %}
{% endcalendar %}
```
### Other features
- Vue-like provide / inject system
- Format HTML attributes with `{% html_attrs %}`
## Documentation
[Read the full documentation here](https://django-components.github.io/django-components/latest/).
... or jump right into the code, [check out the example project](https://github.com/django-components/django-components/tree/master/sampleproject).
## Release notes ## Release notes

View file

@ -143,3 +143,82 @@ might print:
'left_panel': <Slot component_name='layout' slot_name='left_panel'>, 'left_panel': <Slot component_name='layout' slot_name='left_panel'>,
} }
``` ```
## Agentic debugging
All the features above make django-components to work really well with coding AI agents
like Github Copilot or CursorAI.
To debug component rendering with LLMs, you want to provide the LLM with:
1. The components source code
2. The rendered output
3. As much additional context as possible
Your codebase already contains the components source code, but not the latter two.
### Providing rendered output
To provide the LLM with the rendered output, you can simply export the rendered output to a file.
```python
rendered = ProjectPage.render(...)
with open("result.html", "w") as f:
f.write(rendered)
```
If you're using `render_to_response`, access the output from the `HttpResponse` object:
```python
response = ProjectPage.render_to_response(...)
with open("result.html", "wb") as f:
f.write(response.content)
```
### Providing contextual logs
Next, we provide the agent with info on HOW we got the result that we have. We do so
by providing the agent with the trace-level logs.
In your `settings.py`, configure the trace-level logs to be written to the `django_components.log` file:
```python
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"file": {
"class": "logging.FileHandler",
"filename": "django_components.log",
"mode": "w", # Overwrite the file each time
},
},
"loggers": {
"django_components": {
"level": 5,
"handlers": ["file"],
},
},
}
```
### Prompting the agent
Now, you can prompt the agent and include the trace log and the rendered output to guide the agent with debugging.
> I have a django-components (DJC) project. DJC is like if Vue or React component-based web development but made for Django ecosystem.
>
> In the view `project_view`, I am rendering the `ProjectPage` component. However, the output is not as expected.
> The output is missing the tabs.
>
> You have access to the full log trace in `django_components.log`.
>
> You can also see the rendered output in `result.html`.
>
> With this information, help me debug the issue.
>
> First, tell me what kind of info you would be looking for in the logs, and why (how it relates to understanding the cause of the bug).
>
> Then tell me if that info was there, and what the implications are.
>
> Finally, tell me what you would do to fix the issue.

View file

@ -7,18 +7,22 @@ weight: 1
[![PyPI - Version](https://img.shields.io/pypi/v/django-components)](https://pypi.org/project/django-components/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-components)](https://pypi.org/project/django-components/) [![PyPI - License](https://img.shields.io/pypi/l/django-components)](https://github.com/django-components/django-components/blob/master/LICENSE/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/django-components)](https://pypistats.org/packages/django-components) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/django-components/django-components/tests.yml)](https://github.com/django-components/django-components/actions/workflows/tests.yml) [![PyPI - Version](https://img.shields.io/pypi/v/django-components)](https://pypi.org/project/django-components/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-components)](https://pypi.org/project/django-components/) [![PyPI - License](https://img.shields.io/pypi/l/django-components)](https://github.com/django-components/django-components/blob/master/LICENSE/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/django-components)](https://pypistats.org/packages/django-components) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/django-components/django-components/tests.yml)](https://github.com/django-components/django-components/actions/workflows/tests.yml)
django-components introduces component-based architecture to Django's server-side rendering. `django-components` combines Django's templating system with the modularity seen
It combines Django's templating system with the modularity seen in modern frontend frameworks like Vue or React. in modern frontend frameworks like Vue or React.
With `django-components` you can support Django projects small and large without leaving the Django ecosystem.
## Quickstart
A component in django-components can be as simple as a Django template and Python code to declare the component: A component in django-components can be as simple as a Django template and Python code to declare the component:
```htmldjango title="calendar.html" ```htmldjango title="components/calendar/calendar.html"
<div class="calendar"> <div class="calendar">
Today's date is <span>{{ date }}</span> Today's date is <span>{{ date }}</span>
</div> </div>
``` ```
```py title="calendar.py" ```py title="components/calendar/calendar.py"
from django_components import Component from django_components import Component
class Calendar(Component): class Calendar(Component):
@ -27,98 +31,37 @@ class Calendar(Component):
Or a combination of Django template, Python, CSS, and Javascript: Or a combination of Django template, Python, CSS, and Javascript:
```htmldjango title="calendar.html" ```htmldjango title="components/calendar/calendar.html"
<div class="calendar"> <div class="calendar">
Today's date is <span>{{ date }}</span> Today's date is <span>{{ date }}</span>
</div> </div>
``` ```
```css title="calendar.css" ```css title="components/calendar/calendar.css"
.calendar { .calendar {
width: 200px; width: 200px;
background: pink; background: pink;
} }
``` ```
```js title="calendar.js" ```js title="components/calendar/calendar.js"
document.querySelector(".calendar").onclick = function () { document.querySelector(".calendar").onclick = function () {
alert("Clicked calendar!"); alert("Clicked calendar!");
}; };
``` ```
```py title="calendar.py" ```py title="components/calendar/calendar.py"
from django_components import Component from django_components import Component
class Calendar(Component): class Calendar(Component):
template_file = "calendar.html" template_file = "calendar.html"
js_file = "calendar.js" js_file = "calendar.js"
css_file = "calendar.css" css_file = "calendar.css"
```
Alternatively, you can "inline" HTML, JS, and CSS right into the component class:
```py
from django_components import Component
class Calendar(Component):
template = """
<div class="calendar">
Today's date is <span>{{ date }}</span>
</div>
"""
css = """
.calendar {
width: 200px;
background: pink;
}
"""
js = """
document.querySelector(".calendar").onclick = function () {
alert("Clicked calendar!");
};
"""
```
## Features
1. 🧩 **Reusability:** Allows creation of self-contained, reusable UI elements.
2. 📦 **Encapsulation:** Each component can include its own HTML, CSS, and JavaScript.
3. 🚀 **Server-side rendering:** Components render on the server, improving initial load times and SEO.
4. 🐍 **Django integration:** Works within the Django ecosystem, using familiar concepts like template tags.
5. ⚡ **Asynchronous loading:** Components can render independently opening up for integration with JS frameworks like HTMX or AlpineJS.
Potential benefits:
- 🔄 Reduced code duplication
- 🛠️ Improved maintainability through modular design
- 🧠 Easier management of complex UIs
- 🤝 Enhanced collaboration between frontend and backend developers
Django-components can be particularly useful for larger Django projects that require a more structured approach to UI development, without necessitating a shift to a separate frontend framework.
## Quickstart
django-components lets you create reusable blocks of code needed to generate the front end code you need for a modern app.
Define a component in `components/calendar/calendar.py` like this:
```python
@register("calendar")
class Calendar(Component):
template_file = "template.html"
def get_context_data(self, date): def get_context_data(self, date):
return {"date": date} return {"date": date}
``` ```
With this `template.html` file:
```htmldjango
<div class="calendar-component">Today's date is <span>{{ date }}</span></div>
```
Use the component like this: Use the component like this:
```htmldjango ```htmldjango
@ -128,13 +71,235 @@ Use the component like this:
And this is what gets rendered: And this is what gets rendered:
```html ```html
<div class="calendar-component">Today's date is <span>2024-11-06</span></div> <div class="calendar-component">
Today's date is <span>2024-11-06</span>
</div>
``` ```
Read on to learn about all the exciting details and configuration possibilities! Read on to learn about all the exciting details and configuration possibilities!
(If you instead prefer to jump right into the code, [check out the example project](https://github.com/django-components/django-components/tree/master/sampleproject)) (If you instead prefer to jump right into the code, [check out the example project](https://github.com/django-components/django-components/tree/master/sampleproject))
## Features
### Modern and modular UI
- Create self-contained, reusable UI elements.
- Each component can include its own HTML, CSS, and JS, or additional third-party JS and CSS.
- HTML, CSS, and JS can be defined on the component class, or loaded from files.
```python
from django_components import Component
@register("calendar")
class Calendar(Component):
template = """
<div class="calendar">
Today's date is
<span>{{ date }}</span>
</div>
"""
css = """
.calendar {
width: 200px;
background: pink;
}
"""
js = """
document.querySelector(".calendar")
.addEventListener("click", () => {
alert("Clicked calendar!");
});
"""
# Additional JS and CSS
class Media:
js = ["https://cdn.jsdelivr.net/npm/htmx.org@2.1.1/dist/htmx.min.js"]
css = ["bootstrap/dist/css/bootstrap.min.css"]
# Variables available in the template
def get_context_data(self, date):
return {
"date": date
}
```
### Composition with slots
- Render components inside templates with `{% component %}` tag.
- Compose them with `{% slot %}` and `{% fill %}` tags.
- Vue-like slot system, including scoped slots.
```htmldjango
{% component "Layout"
bookmarks=bookmarks
breadcrumbs=breadcrumbs
%}
{% fill "header" %}
<div class="flex justify-between gap-x-12">
<div class="prose">
<h3>{{ project.name }}</h3>
</div>
<div class="font-semibold text-gray-500">
{{ project.start_date }} - {{ project.end_date }}
</div>
</div>
{% endfill %}
{# Access data passed to `{% slot %}` with `data` #}
{% fill "tabs" data="tabs_data" %}
{% component "TabItem" header="Project Info" %}
{% component "ProjectInfo"
project=project
project_tags=project_tags
attrs:class="py-5"
attrs:width=tabs_data.width
/ %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
```
### Extended template tags
`django-components` extends Django's template tags syntax with:
- Literal lists and dictionaries in template tags
- Self-closing tags `{% mytag / %}`
- Multi-line template tags
- Spread operator `...` to dynamically pass args or kwargs into the template tag
- Nested template tags like `"{{ first_name }} {{ last_name }}"`
- Flat definition of dictionary keys `attr:key=val`
```htmldjango
{% component "table"
...default_attrs
title="Friend list for {{ user.name }}"
headers=["Name", "Age", "Email"]
data=[
{
"name": "John"|upper,
"age": 30|add:1,
"email": "john@example.com",
"hobbies": ["reading"],
},
{
"name": "Jane"|upper,
"age": 25|add:1,
"email": "jane@example.com",
"hobbies": ["reading", "coding"],
},
],
attrs:class="py-4 ma-2 border-2 border-gray-300 rounded-md"
/ %}
```
### HTML fragment support
`django-components` makes intergration with HTMX, AlpineJS or jQuery easy by allowing components to be rendered as HTML fragments:
- Components's JS and CSS is loaded automatically when the fragment is inserted into the DOM
- Expose components as views with `get`, `post`, `put`, `patch`, `delete` methods
```py
# components/calendar/calendar.py
@register("calendar")
class Calendar(Component):
template_file = "calendar.html"
def get(self, request, *args, **kwargs):
page = request.GET.get("page", 1)
return self.render_to_response(
kwargs={
"page": page,
}
)
def get_context_data(self, page):
return {
"page": page,
}
# urls.py
path("calendar/", Calendar.as_view()),
```
### Type hints
Opt-in to type hints by defining types for component's args, kwargs, slots, and more:
```py
from typing import NotRequired, Tuple, TypedDict, SlotContent, SlotFunc
ButtonArgs = Tuple[int, str]
class ButtonKwargs(TypedDict):
variable: str
another: int
maybe_var: NotRequired[int] # May be omitted
class ButtonData(TypedDict):
variable: str
class ButtonSlots(TypedDict):
my_slot: NotRequired[SlotFunc]
another_slot: SlotContent
ButtonType = Component[ButtonArgs, ButtonKwargs, ButtonSlots, ButtonData, JsData, CssData]
class Button(ButtonType):
def get_context_data(self, *args, **kwargs):
self.input.args[0] # int
self.input.kwargs["variable"] # str
self.input.slots["my_slot"] # SlotFunc[MySlotData]
return {} # Error: Key "variable" is missing
```
When you then call `Button.render()` or `Button.render_to_response()`, you will get type hints:
```py
Button.render(
# Error: First arg must be `int`, got `float`
args=(1.25, "abc"),
# Error: Key "another" is missing
kwargs={
"variable": "text",
},
)
```
### Debugging features
- **Visual component inspection**: Highlight components and slots directly in your browser.
- **Detailed tracing logs to supply AI-agents with context**: The logs include component and slot names and IDs, and their position in the tree.
<div style="text-align: center;">
<img src="https://github.com/django-components/django-components/blob/master/docs/images/debug-highlight-slots.png?raw=true" alt="Component debugging visualization showing slot highlighting" width="500" style="margin: auto;">
</div>
### Sharing components
- Install and use third-party components from PyPI
- Or publish your own "component registry"
- Highly customizable - Choose how the components are called in the template (and more):
```htmldjango
{% component "calendar" date="2024-11-06" %}
{% endcomponent %}
{% calendar date="2024-11-06" %}
{% endcalendar %}
```
### Other features
- Vue-like provide / inject system
- Format HTML attributes with `{% html_attrs %}`
## Release notes ## Release notes
Read the [Release Notes](../release_notes.md) Read the [Release Notes](../release_notes.md)

View file

@ -1045,7 +1045,7 @@ class Component(
component_id=render_id, component_id=render_id,
slot_name=None, slot_name=None,
component_path=component_path, component_path=component_path,
extra=f"Received {len(args)} args, {len(kwargs)} kwargs, {len(slots)} slots", extra=f"Received {len(args)} args, {len(kwargs)} kwargs, {len(slots)} slots, Available slots: {slots}",
) )
# Register the component to provide # Register the component to provide

View file

@ -330,6 +330,7 @@ class SlotNode(BaseNode):
slot_name=slot_name, slot_name=slot_name,
component_path=component_path, component_path=component_path,
slot_fills=slot_fills, slot_fills=slot_fills,
extra=f"Available fills: {slot_fills}",
) )
# Check for errors # Check for errors
@ -395,6 +396,7 @@ class SlotNode(BaseNode):
if ( if (
component_ctx.registry.settings.context_behavior == ContextBehavior.DJANGO component_ctx.registry.settings.context_behavior == ContextBehavior.DJANGO
and component_ctx.outer_context is None and component_ctx.outer_context is None
and (slot_name not in component_ctx.fills)
): ):
# When we have nested components with fills, the context layers are added in # When we have nested components with fills, the context layers are added in
# the following order: # the following order:
@ -418,20 +420,53 @@ class SlotNode(BaseNode):
# #
# In the Context, the components are identified by their ID, NOT by their name, as in the example above. # In the Context, the components are identified by their ID, NOT by their name, as in the example above.
# So the path is more like this: # So the path is more like this:
# ax3c89 -> hui3q2 -> kok92a -> a1b2c3 -> kok92a -> hui3q2 -> d4e5f6 -> hui3q2 # a1b2c3 -> ax3c89 -> hui3q2 -> kok92a -> a1b2c3 -> kok92a -> hui3q2 -> d4e5f6 -> hui3q2
# #
# We're at the right-most `hui3q2`, and we want to find `ax3c89`. # We're at the right-most `hui3q2` (index 8), and we want to find `ax3c89` (index 1).
# To achieve that, we first find the left-most `hui3q2`, and then find the `ax3c89` # To achieve that, we first find the left-most `hui3q2` (index 2), and then find the `ax3c89`
# in the list of dicts before it. # in the list of dicts before it (index 1).
curr_index = get_index( curr_index = get_index(
context.dicts, lambda d: _COMPONENT_CONTEXT_KEY in d and d[_COMPONENT_CONTEXT_KEY] == component_id context.dicts, lambda d: _COMPONENT_CONTEXT_KEY in d and d[_COMPONENT_CONTEXT_KEY] == component_id
) )
parent_index = get_last_index(context.dicts[:curr_index], lambda d: _COMPONENT_CONTEXT_KEY in d) parent_index = get_last_index(context.dicts[:curr_index], lambda d: _COMPONENT_CONTEXT_KEY in d)
# NOTE: There's an edge case when our component `hui3q2` appears at the start of the stack:
# hui3q2 -> ax3c89 -> ... -> hui3q2
#
# Looking left finds nothing. In this case, look for the first component layer to the right.
if parent_index is None and curr_index + 1 < len(context.dicts):
parent_index = get_index(
context.dicts[curr_index + 1 :], lambda d: _COMPONENT_CONTEXT_KEY in d # noqa: E203
)
if parent_index is not None:
parent_index = parent_index + curr_index + 1
trace_component_msg(
"SLOT_PARENT_INDEX",
component_name=component_ctx.component_name,
component_id=component_ctx.component_id,
slot_name=name,
component_path=component_ctx.component_path,
extra=(
f"Parent index: {parent_index}, Current index: {curr_index}, "
f"Context stack: {[d.get(_COMPONENT_CONTEXT_KEY) for d in context.dicts]}"
),
)
if parent_index is not None: if parent_index is not None:
ctx_id_with_fills = context.dicts[parent_index][_COMPONENT_CONTEXT_KEY] ctx_id_with_fills = context.dicts[parent_index][_COMPONENT_CONTEXT_KEY]
ctx_with_fills = component_context_cache[ctx_id_with_fills] ctx_with_fills = component_context_cache[ctx_id_with_fills]
slot_fills = ctx_with_fills.fills slot_fills = ctx_with_fills.fills
# Add trace message when slot_fills are overwritten
trace_component_msg(
"SLOT_FILLS_OVERWRITTEN",
component_name=component_name,
component_id=component_id,
slot_name=slot_name,
component_path=component_path,
extra=f"Slot fills overwritten in django mode. New fills: {slot_fills}",
)
if fill_name in slot_fills: if fill_name in slot_fills:
slot_fill_fn = slot_fills[fill_name] slot_fill_fn = slot_fills[fill_name]
slot_fill = SlotFill( slot_fill = SlotFill(