mirror of
https://github.com/django-components/django-components.git
synced 2025-08-08 08:17:59 +00:00
refactor: fix bug with complex slots and "django" mode + add docs on debugging with AI agents (#956)
This commit is contained in:
parent
be4d744d64
commit
eb3f72ee0d
5 changed files with 601 additions and 148 deletions
262
README.md
262
README.md
|
@ -11,17 +11,24 @@
|
|||
>
|
||||
> 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:
|
||||
|
||||
```htmldjango title="calendar.html"
|
||||
```django
|
||||
{# components/calendar/calendar.html #}
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
```py title="calendar.py"
|
||||
```py
|
||||
# components/calendar/calendar.html
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
|
@ -30,43 +37,72 @@ class Calendar(Component):
|
|||
|
||||
Or a combination of Django template, Python, CSS, and Javascript:
|
||||
|
||||
```htmldjango title="calendar.html"
|
||||
```django
|
||||
{# components/calendar/calendar.html #}
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css title="calendar.css"
|
||||
```css
|
||||
/* components/calendar/calendar.css */
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
```
|
||||
|
||||
```js title="calendar.js"
|
||||
document.querySelector(".calendar").onclick = function () {
|
||||
```js
|
||||
/* components/calendar/calendar.js */
|
||||
document.querySelector(".calendar").onclick = () => {
|
||||
alert("Clicked calendar!");
|
||||
};
|
||||
```
|
||||
|
||||
```py title="calendar.py"
|
||||
```py
|
||||
# components/calendar/calendar.py
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
css_file = "calendar.css"
|
||||
|
||||
def get_context_data(self, date):
|
||||
return {"date": date}
|
||||
```
|
||||
|
||||
Alternatively, you can "inline" HTML, JS, and CSS right into the component class:
|
||||
Use the component like this:
|
||||
|
||||
```py
|
||||
```django
|
||||
{% component "calendar" date="2024-11-06" %}{% endcomponent %}
|
||||
```
|
||||
|
||||
And this is what gets rendered:
|
||||
|
||||
```html
|
||||
<div class="calendar-component">
|
||||
Today's date is <span>2024-11-06</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 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>
|
||||
Today's date is
|
||||
<span>{{ date }}</span>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
@ -78,65 +114,203 @@ class Calendar(Component):
|
|||
"""
|
||||
|
||||
js = """
|
||||
document.querySelector(".calendar").onclick = function () {
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
### Composition with slots
|
||||
|
||||
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.
|
||||
- Render components inside templates with `{% component %}` tag.
|
||||
- Compose them with `{% slot %}` and `{% fill %}` tags.
|
||||
- Vue-like slot system, including scoped slots.
|
||||
|
||||
Potential benefits:
|
||||
```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 %}
|
||||
|
||||
- 🔄 Reduced code duplication
|
||||
- 🛠️ Improved maintainability through modular design
|
||||
- 🧠 Easier management of complex UIs
|
||||
- 🤝 Enhanced collaboration between frontend and backend developers
|
||||
{# 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 %}
|
||||
```
|
||||
|
||||
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.
|
||||
### Extended template tags
|
||||
|
||||
## Quickstart
|
||||
`django-components` extends Django's template tags syntax with:
|
||||
|
||||
django-components lets you create reusable blocks of code needed to generate the front end code you need for a modern app.
|
||||
- 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`
|
||||
|
||||
Define a component in `components/calendar/calendar.py` like this:
|
||||
```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"
|
||||
/ %}
|
||||
```
|
||||
|
||||
```python
|
||||
### 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 = "template.html"
|
||||
template_file = "calendar.html"
|
||||
|
||||
def get_context_data(self, date):
|
||||
return {"date": date}
|
||||
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()),
|
||||
```
|
||||
|
||||
With this `template.html` file:
|
||||
### Type hints
|
||||
|
||||
```htmldjango
|
||||
<div class="calendar-component">Today's date is <span>{{ date }}</span></div>
|
||||
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
|
||||
```
|
||||
|
||||
Use the component like this:
|
||||
When you then call `Button.render()` or `Button.render_to_response()`, you will get type hints:
|
||||
|
||||
```htmldjango
|
||||
{% component "calendar" date="2024-11-06" %}{% endcomponent %}
|
||||
```py
|
||||
Button.render(
|
||||
# Error: First arg must be `int`, got `float`
|
||||
args=(1.25, "abc"),
|
||||
# Error: Key "another" is missing
|
||||
kwargs={
|
||||
"variable": "text",
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
And this is what gets rendered:
|
||||
### Debugging features
|
||||
|
||||
```html
|
||||
<div class="calendar-component">Today's date is <span>2024-11-06</span></div>
|
||||
```
|
||||
- **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.
|
||||
|
||||
### <table><td>[Read the full documentation](https://django-components.github.io/django-components/latest/)</td></table>
|
||||
<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>
|
||||
|
||||
... or jump right into the code, [check out the example project](https://github.com/django-components/django-components/tree/master/sampleproject))
|
||||
### 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
|
||||
|
||||
|
|
|
@ -143,3 +143,82 @@ might print:
|
|||
'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.
|
||||
|
|
|
@ -7,18 +7,22 @@ weight: 1
|
|||
|
||||
[](https://pypi.org/project/django-components/) [](https://pypi.org/project/django-components/) [](https://github.com/django-components/django-components/blob/master/LICENSE/) [](https://pypistats.org/packages/django-components) [](https://github.com/django-components/django-components/actions/workflows/tests.yml)
|
||||
|
||||
django-components introduces component-based architecture to Django's server-side rendering.
|
||||
It combines Django's templating system with the modularity seen in modern frontend frameworks like Vue or React.
|
||||
`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:
|
||||
|
||||
```htmldjango title="calendar.html"
|
||||
```htmldjango title="components/calendar/calendar.html"
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
```py title="calendar.py"
|
||||
```py title="components/calendar/calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
|
@ -27,98 +31,37 @@ class Calendar(Component):
|
|||
|
||||
Or a combination of Django template, Python, CSS, and Javascript:
|
||||
|
||||
```htmldjango title="calendar.html"
|
||||
```htmldjango title="components/calendar/calendar.html"
|
||||
<div class="calendar">
|
||||
Today's date is <span>{{ date }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css title="calendar.css"
|
||||
```css title="components/calendar/calendar.css"
|
||||
.calendar {
|
||||
width: 200px;
|
||||
background: pink;
|
||||
}
|
||||
```
|
||||
|
||||
```js title="calendar.js"
|
||||
```js title="components/calendar/calendar.js"
|
||||
document.querySelector(".calendar").onclick = function () {
|
||||
alert("Clicked calendar!");
|
||||
};
|
||||
```
|
||||
|
||||
```py title="calendar.py"
|
||||
```py title="components/calendar/calendar.py"
|
||||
from django_components import Component
|
||||
|
||||
class Calendar(Component):
|
||||
template_file = "calendar.html"
|
||||
js_file = "calendar.js"
|
||||
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):
|
||||
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:
|
||||
|
||||
```htmldjango
|
||||
|
@ -128,13 +71,235 @@ Use the component like this:
|
|||
And this is what gets rendered:
|
||||
|
||||
```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!
|
||||
|
||||
(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
|
||||
|
||||
Read the [Release Notes](../release_notes.md)
|
||||
|
|
|
@ -1045,7 +1045,7 @@ class Component(
|
|||
component_id=render_id,
|
||||
slot_name=None,
|
||||
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
|
||||
|
|
|
@ -330,6 +330,7 @@ class SlotNode(BaseNode):
|
|||
slot_name=slot_name,
|
||||
component_path=component_path,
|
||||
slot_fills=slot_fills,
|
||||
extra=f"Available fills: {slot_fills}",
|
||||
)
|
||||
|
||||
# Check for errors
|
||||
|
@ -395,6 +396,7 @@ class SlotNode(BaseNode):
|
|||
if (
|
||||
component_ctx.registry.settings.context_behavior == ContextBehavior.DJANGO
|
||||
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
|
||||
# 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.
|
||||
# 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`.
|
||||
# To achieve that, we first find the left-most `hui3q2`, and then find the `ax3c89`
|
||||
# in the list of dicts before it.
|
||||
# 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` (index 2), and then find the `ax3c89`
|
||||
# in the list of dicts before it (index 1).
|
||||
curr_index = get_index(
|
||||
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)
|
||||
|
||||
# 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:
|
||||
ctx_id_with_fills = context.dicts[parent_index][_COMPONENT_CONTEXT_KEY]
|
||||
ctx_with_fills = component_context_cache[ctx_id_with_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:
|
||||
slot_fill_fn = slot_fills[fill_name]
|
||||
slot_fill = SlotFill(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue