mirror of
https://github.com/django-components/django-components.git
synced 2025-09-20 20:59:46 +00:00
feat: allow to highlight slots and components for debugging (#942)
This commit is contained in:
parent
da54d97343
commit
de32d449d9
14 changed files with 306 additions and 49 deletions
|
@ -6,6 +6,7 @@
|
||||||
- [Advanced](concepts/advanced/)
|
- [Advanced](concepts/advanced/)
|
||||||
- Guides
|
- Guides
|
||||||
- [Setup](guides/setup/)
|
- [Setup](guides/setup/)
|
||||||
|
- [Other](guides/other/)
|
||||||
- [Dev guides](guides/devguides/)
|
- [Dev guides](guides/devguides/)
|
||||||
- [API Documentation](reference/)
|
- [API Documentation](reference/)
|
||||||
- [Release notes](release_notes.md)
|
- [Release notes](release_notes.md)
|
||||||
|
|
145
docs/guides/other/troubleshooting.md
Normal file
145
docs/guides/other/troubleshooting.md
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
---
|
||||||
|
title: Troubleshooting
|
||||||
|
weight: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
As larger projects get more complex, it can be hard to debug issues. Django Components provides a number of tools and approaches that can help you with that.
|
||||||
|
|
||||||
|
## Component and slot highlighting
|
||||||
|
|
||||||
|
Django Components provides a visual debugging feature that helps you understand the structure and boundaries of your components and slots. When enabled, it adds a colored border and a label around each component and slot on your rendered page.
|
||||||
|
|
||||||
|
To enable component and slot highlighting, set
|
||||||
|
[`debug_highlight_components`](../../../reference/settings/#django_components.app_settings.ComponentsSettings.debug_highlight_components)
|
||||||
|
and/or [`debug_highlight_slots`](../../../reference/settings/#django_components.app_settings.ComponentsSettings.debug_highlight_slots)
|
||||||
|
to `True` in your `settings.py` file:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django_components import ComponentsSettings
|
||||||
|
|
||||||
|
COMPONENTS = ComponentsSettings(
|
||||||
|
debug_highlight_components=True,
|
||||||
|
debug_highlight_slots=True,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Components will be highlighted with a **blue** border and label:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
While the slots will be highlighted with a **red** border and label:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
Use this feature ONLY in during development. Do NOT use it in production.
|
||||||
|
|
||||||
|
## Component path in errors
|
||||||
|
|
||||||
|
When an error occurs, the error message will show the path to the component that
|
||||||
|
caused the error. E.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
KeyError: "An error occured while rendering components MyPage > MyLayout > MyComponent > Childomponent(slot:content)
|
||||||
|
```
|
||||||
|
|
||||||
|
The error message contains also the slot paths, so if you have a template like this:
|
||||||
|
|
||||||
|
```django
|
||||||
|
{% component "my_page" %}
|
||||||
|
{% slot "content" %}
|
||||||
|
{% component "table" %}
|
||||||
|
{% slot "header" %}
|
||||||
|
{% component "table_header" %}
|
||||||
|
... {# ERROR HERE #}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endslot %}
|
||||||
|
{% endcomponent %}
|
||||||
|
{% endslot %}
|
||||||
|
{% endcomponent %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the error message will show the path to the component that caused the error:
|
||||||
|
|
||||||
|
```
|
||||||
|
KeyError: "An error occured while rendering components my_page > layout > layout(slot:content) > my_page(slot:content) > table > table(slot:header) > table_header > table_header(slot:content)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debug and trace logging
|
||||||
|
|
||||||
|
Django components supports [logging with Django](https://docs.djangoproject.com/en/5.0/howto/logging/#logging-how-to).
|
||||||
|
|
||||||
|
To configure logging for Django components, set the `django_components` logger in
|
||||||
|
[`LOGGING`](https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-LOGGING)
|
||||||
|
in `settings.py` (below).
|
||||||
|
|
||||||
|
Also see the [`settings.py` file in sampleproject](https://github.com/django-components/django-components/blob/master/sampleproject/sampleproject/settings.py) for a real-life example.
|
||||||
|
|
||||||
|
```py
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'stream': sys.stdout,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"django_components": {
|
||||||
|
"level": logging.DEBUG,
|
||||||
|
"handlers": ["console"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
|
||||||
|
To set TRACE level, set `"level"` to `5`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
LOGGING = {
|
||||||
|
"loggers": {
|
||||||
|
"django_components": {
|
||||||
|
"level": 5,
|
||||||
|
"handlers": ["console"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logger levels
|
||||||
|
|
||||||
|
As of v0.126, django-components primarily uses these logger levels:
|
||||||
|
|
||||||
|
- `DEBUG`: Report on loading associated HTML / JS / CSS files, autodiscovery, etc.
|
||||||
|
- `TRACE`: Detailed interaction of components and slots. Logs when template tags,
|
||||||
|
components, and slots are started / ended rendering, and when a slot is filled.
|
||||||
|
|
||||||
|
## Slot origin
|
||||||
|
|
||||||
|
When you pass a slot fill to a Component, the component and slot names is remebered
|
||||||
|
on the slot object.
|
||||||
|
|
||||||
|
Thus, you can check where a slot was filled from by printing it out:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyComponent(Component):
|
||||||
|
def on_render_before(self):
|
||||||
|
print(self.input.slots)
|
||||||
|
```
|
||||||
|
|
||||||
|
might print:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
{
|
||||||
|
'content': <Slot component_name='layout' slot_name='content'>,
|
||||||
|
'header': <Slot component_name='my_page' slot_name='header'>,
|
||||||
|
'left_panel': <Slot component_name='layout' slot_name='left_panel'>,
|
||||||
|
}
|
||||||
|
```
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
weight: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
Django components supports [logging with Django](https://docs.djangoproject.com/en/5.0/howto/logging/#logging-how-to).
|
|
||||||
This can help with troubleshooting.
|
|
||||||
|
|
||||||
To configure logging for Django components, set the `django_components` logger in
|
|
||||||
[`LOGGING`](https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-LOGGING)
|
|
||||||
in `settings.py` (below).
|
|
||||||
|
|
||||||
Also see the [`settings.py` file in sampleproject](https://github.com/django-components/django-components/blob/master/sampleproject/sampleproject/settings.py) for a real-life example.
|
|
||||||
|
|
||||||
```py
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
LOGGING = {
|
|
||||||
'version': 1,
|
|
||||||
'disable_existing_loggers': False,
|
|
||||||
"handlers": {
|
|
||||||
"console": {
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'stream': sys.stdout,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"django_components": {
|
|
||||||
"level": logging.DEBUG,
|
|
||||||
"handlers": ["console"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
BIN
docs/images/debug-highlight-components.png
Normal file
BIN
docs/images/debug-highlight-components.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 493 KiB |
BIN
docs/images/debug-highlight-slots.png
Normal file
BIN
docs/images/debug-highlight-slots.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 365 KiB |
|
@ -10,8 +10,7 @@ that will be added by installing `django_components`:
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
usage: manage.py upgradecomponent [-h] [--path PATH] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
|
usage: manage.py upgradecomponent [-h] [--path PATH] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
|
||||||
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color]
|
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks]
|
||||||
[--skip-checks]
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -53,10 +52,9 @@ Updates component and component_block tags to the new syntax
|
||||||
## `startcomponent`
|
## `startcomponent`
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
usage: manage.py startcomponent [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force]
|
usage: manage.py startcomponent [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose]
|
||||||
[--verbose] [--dry-run] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
|
[--dry-run] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH]
|
||||||
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color]
|
[--traceback] [--no-color] [--force-color] [--skip-checks]
|
||||||
[--skip-checks]
|
|
||||||
name
|
name
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -38,6 +38,8 @@ defaults = ComponentsSettings(
|
||||||
dirs=[Path(settings.BASE_DIR) / "components"],
|
dirs=[Path(settings.BASE_DIR) / "components"],
|
||||||
# App-level "components" dirs, e.g. `[app]/components/`
|
# App-level "components" dirs, e.g. `[app]/components/`
|
||||||
app_dirs=["components"],
|
app_dirs=["components"],
|
||||||
|
debug_highlight_components=False,
|
||||||
|
debug_highlight_slots=False,
|
||||||
dynamic_component_name="dynamic",
|
dynamic_component_name="dynamic",
|
||||||
libraries=[], # E.g. ["mysite.components.forms", ...]
|
libraries=[], # E.g. ["mysite.components.forms", ...]
|
||||||
multiline_tags=True,
|
multiline_tags=True,
|
||||||
|
@ -93,6 +95,26 @@ defaults = ComponentsSettings(
|
||||||
show_if_no_docstring: true
|
show_if_no_docstring: true
|
||||||
show_labels: false
|
show_labels: false
|
||||||
|
|
||||||
|
::: django_components.app_settings.ComponentsSettings.debug_highlight_components
|
||||||
|
options:
|
||||||
|
show_root_heading: true
|
||||||
|
show_signature: true
|
||||||
|
separate_signature: true
|
||||||
|
show_symbol_type_heading: false
|
||||||
|
show_symbol_type_toc: false
|
||||||
|
show_if_no_docstring: true
|
||||||
|
show_labels: false
|
||||||
|
|
||||||
|
::: django_components.app_settings.ComponentsSettings.debug_highlight_slots
|
||||||
|
options:
|
||||||
|
show_root_heading: true
|
||||||
|
show_signature: true
|
||||||
|
separate_signature: true
|
||||||
|
show_symbol_type_heading: false
|
||||||
|
show_symbol_type_toc: false
|
||||||
|
show_if_no_docstring: true
|
||||||
|
show_labels: false
|
||||||
|
|
||||||
::: django_components.app_settings.ComponentsSettings.dirs
|
::: django_components.app_settings.ComponentsSettings.dirs
|
||||||
options:
|
options:
|
||||||
show_root_heading: true
|
show_root_heading: true
|
||||||
|
|
|
@ -20,7 +20,7 @@ Import as
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1118" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1066" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ If you insert this tag multiple times, ALL CSS links will be duplicately inserte
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1140" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1088" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ If you insert this tag multiple times, ALL JS scripts will be duplicately insert
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1257" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1436" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ can access only the data that was explicitly passed to it:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L466" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L577" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -336,7 +336,7 @@ renders
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L9" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L12" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -416,7 +416,7 @@ user = self.inject("user_data")["user"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L149" target="_blank">See source code</a>
|
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L153" target="_blank">See source code</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -239,6 +239,34 @@ class ComponentsSettings(NamedTuple):
|
||||||
> [here](https://github.com/django-components/django-components/issues/498).
|
> [here](https://github.com/django-components/django-components/issues/498).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
debug_highlight_components: Optional[bool] = None
|
||||||
|
"""
|
||||||
|
Enable / disable component highlighting.
|
||||||
|
See [Troubleshooting](../../guides/other/troubleshooting#component-highlighting) for more details.
|
||||||
|
|
||||||
|
Defaults to `False`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
COMPONENTS = ComponentsSettings(
|
||||||
|
debug_highlight_components=True,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
debug_highlight_slots: Optional[bool] = None
|
||||||
|
"""
|
||||||
|
Enable / disable slot highlighting.
|
||||||
|
See [Troubleshooting](../../guides/other/troubleshooting#slot-highlighting) for more details.
|
||||||
|
|
||||||
|
Defaults to `False`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
COMPONENTS = ComponentsSettings(
|
||||||
|
debug_highlight_slots=True,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
dynamic_component_name: Optional[str] = None
|
dynamic_component_name: Optional[str] = None
|
||||||
"""
|
"""
|
||||||
By default, the [dynamic component](../components#django_components.components.dynamic.DynamicComponent)
|
By default, the [dynamic component](../components#django_components.components.dynamic.DynamicComponent)
|
||||||
|
@ -594,6 +622,8 @@ defaults = ComponentsSettings(
|
||||||
dirs=Dynamic(lambda: [Path(settings.BASE_DIR) / "components"]), # type: ignore[arg-type]
|
dirs=Dynamic(lambda: [Path(settings.BASE_DIR) / "components"]), # type: ignore[arg-type]
|
||||||
# App-level "components" dirs, e.g. `[app]/components/`
|
# App-level "components" dirs, e.g. `[app]/components/`
|
||||||
app_dirs=["components"],
|
app_dirs=["components"],
|
||||||
|
debug_highlight_components=False,
|
||||||
|
debug_highlight_slots=False,
|
||||||
dynamic_component_name="dynamic",
|
dynamic_component_name="dynamic",
|
||||||
libraries=[], # E.g. ["mysite.components.forms", ...]
|
libraries=[], # E.g. ["mysite.components.forms", ...]
|
||||||
multiline_tags=True,
|
multiline_tags=True,
|
||||||
|
@ -643,6 +673,14 @@ class InternalSettings:
|
||||||
def APP_DIRS(self) -> Sequence[str]:
|
def APP_DIRS(self) -> Sequence[str]:
|
||||||
return default(self._settings.app_dirs, cast(List[str], defaults.app_dirs))
|
return default(self._settings.app_dirs, cast(List[str], defaults.app_dirs))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def DEBUG_HIGHLIGHT_COMPONENTS(self) -> bool:
|
||||||
|
return default(self._settings.debug_highlight_components, cast(bool, defaults.debug_highlight_components))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def DEBUG_HIGHLIGHT_SLOTS(self) -> bool:
|
||||||
|
return default(self._settings.debug_highlight_slots, cast(bool, defaults.debug_highlight_slots))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def DYNAMIC_COMPONENT_NAME(self) -> str:
|
def DYNAMIC_COMPONENT_NAME(self) -> str:
|
||||||
return default(self._settings.dynamic_component_name, cast(str, defaults.dynamic_component_name))
|
return default(self._settings.dynamic_component_name, cast(str, defaults.dynamic_component_name))
|
||||||
|
|
|
@ -34,7 +34,7 @@ from django.test.signals import template_rendered
|
||||||
from django.utils.html import conditional_escape
|
from django.utils.html import conditional_escape
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from django_components.app_settings import ContextBehavior
|
from django_components.app_settings import ContextBehavior, app_settings
|
||||||
from django_components.component_media import ComponentMediaInput, ComponentMediaMeta
|
from django_components.component_media import ComponentMediaInput, ComponentMediaMeta
|
||||||
from django_components.component_registry import ComponentRegistry
|
from django_components.component_registry import ComponentRegistry
|
||||||
from django_components.component_registry import registry as registry_
|
from django_components.component_registry import registry as registry_
|
||||||
|
@ -69,6 +69,7 @@ from django_components.slots import (
|
||||||
resolve_fills,
|
resolve_fills,
|
||||||
)
|
)
|
||||||
from django_components.template import cached_template
|
from django_components.template import cached_template
|
||||||
|
from django_components.util.component_highlight import apply_component_highlight
|
||||||
from django_components.util.context import snapshot_context
|
from django_components.util.context import snapshot_context
|
||||||
from django_components.util.django_monkeypatch import is_template_cls_patched
|
from django_components.util.django_monkeypatch import is_template_cls_patched
|
||||||
from django_components.util.exception import component_error_message
|
from django_components.util.exception import component_error_message
|
||||||
|
@ -1153,6 +1154,9 @@ class Component(
|
||||||
del component_context_cache[render_id] # type: ignore[arg-type]
|
del component_context_cache[render_id] # type: ignore[arg-type]
|
||||||
unregister_provide_reference(render_id) # type: ignore[arg-type]
|
unregister_provide_reference(render_id) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
if app_settings.DEBUG_HIGHLIGHT_COMPONENTS:
|
||||||
|
html = apply_component_highlight("component", html, f"{self.name} ({render_id})")
|
||||||
|
|
||||||
return html
|
return html
|
||||||
|
|
||||||
post_render_callbacks[render_id] = on_component_rendered
|
post_render_callbacks[render_id] = on_component_rendered
|
||||||
|
|
|
@ -180,7 +180,7 @@ def component_post_render(
|
||||||
# </div>
|
# </div>
|
||||||
# ```
|
# ```
|
||||||
#
|
#
|
||||||
# Then we end up with 3 bits - 1. test before, 2. component, and 3. text after
|
# Then we end up with 3 bits - 1. text before, 2. component, and 3. text after
|
||||||
#
|
#
|
||||||
# We know when we've arrived at component's end, because `child_id` will be set to `None`.
|
# We know when we've arrived at component's end, because `child_id` will be set to `None`.
|
||||||
# So we can collect the HTML parts by the component ID, and when we hit the end, we join
|
# So we can collect the HTML parts by the component ID, and when we hit the end, we join
|
||||||
|
|
|
@ -24,10 +24,11 @@ from django.template.base import NodeList, TextNode
|
||||||
from django.template.exceptions import TemplateSyntaxError
|
from django.template.exceptions import TemplateSyntaxError
|
||||||
from django.utils.safestring import SafeString, mark_safe
|
from django.utils.safestring import SafeString, mark_safe
|
||||||
|
|
||||||
from django_components.app_settings import ContextBehavior
|
from django_components.app_settings import ContextBehavior, app_settings
|
||||||
from django_components.context import _COMPONENT_CONTEXT_KEY, _INJECT_CONTEXT_KEY_PREFIX
|
from django_components.context import _COMPONENT_CONTEXT_KEY, _INJECT_CONTEXT_KEY_PREFIX
|
||||||
from django_components.node import BaseNode
|
from django_components.node import BaseNode
|
||||||
from django_components.perfutil.component import component_context_cache
|
from django_components.perfutil.component import component_context_cache
|
||||||
|
from django_components.util.component_highlight import apply_component_highlight
|
||||||
from django_components.util.exception import add_slot_to_error_message
|
from django_components.util.exception import add_slot_to_error_message
|
||||||
from django_components.util.logger import trace_component_msg
|
from django_components.util.logger import trace_component_msg
|
||||||
from django_components.util.misc import get_index, get_last_index, is_identifier
|
from django_components.util.misc import get_index, get_last_index, is_identifier
|
||||||
|
@ -537,6 +538,9 @@ class SlotNode(BaseNode):
|
||||||
# the render function ALWAYS receives them.
|
# the render function ALWAYS receives them.
|
||||||
output = slot_fill.slot(used_ctx, kwargs, slot_ref)
|
output = slot_fill.slot(used_ctx, kwargs, slot_ref)
|
||||||
|
|
||||||
|
if app_settings.DEBUG_HIGHLIGHT_SLOTS:
|
||||||
|
output = apply_component_highlight("slot", output, f"{component_name} - {slot_name}")
|
||||||
|
|
||||||
trace_component_msg(
|
trace_component_msg(
|
||||||
"RENDER_SLOT_END",
|
"RENDER_SLOT_END",
|
||||||
component_name=component_name,
|
component_name=component_name,
|
||||||
|
|
43
src/django_components/util/component_highlight.py
Normal file
43
src/django_components/util/component_highlight.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from typing import Literal, NamedTuple
|
||||||
|
|
||||||
|
from django_components.util.misc import gen_id
|
||||||
|
|
||||||
|
|
||||||
|
class HighlightColor(NamedTuple):
|
||||||
|
text_color: str
|
||||||
|
border_color: str
|
||||||
|
|
||||||
|
|
||||||
|
COLORS = {
|
||||||
|
"component": HighlightColor(text_color="#2f14bb", border_color="blue"),
|
||||||
|
"slot": HighlightColor(text_color="#bb1414", border_color="#e40c0c"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_component_highlight(type: Literal["component", "slot"], output: str, name: str) -> str:
|
||||||
|
"""
|
||||||
|
Wrap HTML (string) in a div with a border and a highlight color.
|
||||||
|
|
||||||
|
This is part of the component / slot highlighting feature. User can toggle on
|
||||||
|
to see the component / slot boundaries.
|
||||||
|
"""
|
||||||
|
color = COLORS[type]
|
||||||
|
|
||||||
|
# Because the component / slot name is set via styling as a `::before` pseudo-element,
|
||||||
|
# we need to generate a unique ID for each component / slot to avoid conflicts.
|
||||||
|
highlight_id = gen_id()
|
||||||
|
|
||||||
|
output = f"""
|
||||||
|
<style>
|
||||||
|
.{type}-highlight-{highlight_id}::before {{
|
||||||
|
content: "{name}: ";
|
||||||
|
font-weight: bold;
|
||||||
|
color: {color.text_color};
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
<div class="{type}-highlight-{highlight_id}" style="border: 1px solid {color.border_color}">
|
||||||
|
{output}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return output
|
36
tests/test_component_highlight.py
Normal file
36
tests/test_component_highlight.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from django_components.util.component_highlight import apply_component_highlight, COLORS
|
||||||
|
|
||||||
|
from .django_test_setup import setup_test_config
|
||||||
|
from .testutils import BaseTestCase
|
||||||
|
|
||||||
|
setup_test_config({"autodiscover": False})
|
||||||
|
|
||||||
|
|
||||||
|
class ComponentHighlightTests(BaseTestCase):
|
||||||
|
def test_component_highlight(self):
|
||||||
|
# Test component highlighting
|
||||||
|
test_html = "<div>Test content</div>"
|
||||||
|
component_name = "TestComponent"
|
||||||
|
result = apply_component_highlight("component", test_html, component_name)
|
||||||
|
|
||||||
|
# Check that the output contains the component name
|
||||||
|
self.assertIn(component_name, result)
|
||||||
|
# Check that the output contains the original HTML
|
||||||
|
self.assertIn(test_html, result)
|
||||||
|
# Check that the component colors are used
|
||||||
|
self.assertIn(COLORS["component"].text_color, result)
|
||||||
|
self.assertIn(COLORS["component"].border_color, result)
|
||||||
|
|
||||||
|
def test_slot_highlight(self):
|
||||||
|
# Test slot highlighting
|
||||||
|
test_html = "<span>Slot content</span>"
|
||||||
|
slot_name = "content-slot"
|
||||||
|
result = apply_component_highlight("slot", test_html, slot_name)
|
||||||
|
|
||||||
|
# Check that the output contains the slot name
|
||||||
|
self.assertIn(slot_name, result)
|
||||||
|
# Check that the output contains the original HTML
|
||||||
|
self.assertIn(test_html, result)
|
||||||
|
# Check that the slot colors are used
|
||||||
|
self.assertIn(COLORS["slot"].text_color, result)
|
||||||
|
self.assertIn(COLORS["slot"].border_color, result)
|
Loading…
Add table
Add a link
Reference in a new issue