mirror of
https://github.com/django-components/django-components.git
synced 2025-08-12 02:08:00 +00:00
feat: resolve slot context based on settings
This commit is contained in:
parent
ec12a3bcb8
commit
d297249d9f
2 changed files with 128 additions and 2 deletions
|
@ -9,6 +9,98 @@ class ContextBehavior(str, Enum):
|
||||||
ISOLATED = "isolated"
|
ISOLATED = "isolated"
|
||||||
|
|
||||||
|
|
||||||
|
class SlotContextBehavior(str, Enum):
|
||||||
|
ALLOW_OVERRIDE = "allow_override"
|
||||||
|
"""
|
||||||
|
Components CAN override the slot context variables passed from the outer scopes.
|
||||||
|
Contexts of deeper components take precedence over shallower ones.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Given this template
|
||||||
|
|
||||||
|
```txt
|
||||||
|
{% component 'my_comp' %}
|
||||||
|
{{ my_var }}
|
||||||
|
{% endcomponent %}
|
||||||
|
```
|
||||||
|
|
||||||
|
and this context passed to the render function (AKA root context)
|
||||||
|
```py
|
||||||
|
{ "my_var": 123 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Then if component "my_comp" defines context
|
||||||
|
```py
|
||||||
|
{ "my_var": 456 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Then since "my_comp" overrides the varialbe "my_var", so `{{ my_var }}` will equal `456`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
PREFER_ROOT = "prefer_root"
|
||||||
|
"""
|
||||||
|
This is the same as "allow_override", except any variables defined in the root context
|
||||||
|
take precedence over anything else.
|
||||||
|
|
||||||
|
So if a variable is found in the root context, then root context is used.
|
||||||
|
Otherwise, the context of the component where the slot fill is located is used.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Given this template
|
||||||
|
|
||||||
|
```txt
|
||||||
|
{% component 'my_comp' %}
|
||||||
|
{{ my_var_one }}
|
||||||
|
{{ my_var_two }}
|
||||||
|
{% endcomponent %}
|
||||||
|
```
|
||||||
|
|
||||||
|
and this context passed to the render function (AKA root context)
|
||||||
|
```py
|
||||||
|
{ "my_var_one": 123 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Then if component "my_comp" defines context
|
||||||
|
```py
|
||||||
|
{ "my_var": 456, "my_var_two": "abc" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the rendered `{{ my_var_one }}` will equal to `123`, and `{{ my_var_two }}`
|
||||||
|
will equal to "abc".
|
||||||
|
"""
|
||||||
|
|
||||||
|
ISOLATED = "isolated"
|
||||||
|
"""
|
||||||
|
This setting makes the slots behave similar to Vue or React, where
|
||||||
|
the slot uses EXCLUSIVELY the root context, and nested components CANNOT
|
||||||
|
override context variables inside the slots.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Given this template
|
||||||
|
|
||||||
|
```txt
|
||||||
|
{% component 'my_comp' %}
|
||||||
|
{{ my_var }}
|
||||||
|
{% endcomponent %}
|
||||||
|
```
|
||||||
|
|
||||||
|
and this context passed to the render function (AKA root context)
|
||||||
|
```py
|
||||||
|
{ "my_var": 123 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Then if component "my_comp" defines context
|
||||||
|
```py
|
||||||
|
{ "my_var": 456 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the rendered `{{ my_var }}` will equal `123`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class AppSettings:
|
class AppSettings:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.settings = getattr(settings, "COMPONENTS", {})
|
self.settings = getattr(settings, "COMPONENTS", {})
|
||||||
|
@ -37,5 +129,17 @@ class AppSettings:
|
||||||
valid_values = [behavior.value for behavior in ContextBehavior]
|
valid_values = [behavior.value for behavior in ContextBehavior]
|
||||||
raise ValueError(f"Invalid context behavior: {raw_value}. Valid options are {valid_values}")
|
raise ValueError(f"Invalid context behavior: {raw_value}. Valid options are {valid_values}")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def SLOT_CONTEXT_BEHAVIOR(self) -> SlotContextBehavior:
|
||||||
|
raw_value = self.settings.setdefault("slot_context_behavior", SlotContextBehavior.PREFER_ROOT.value)
|
||||||
|
return self._validate_slot_context_behavior(raw_value)
|
||||||
|
|
||||||
|
def _validate_slot_context_behavior(self, raw_value: SlotContextBehavior) -> SlotContextBehavior:
|
||||||
|
try:
|
||||||
|
return SlotContextBehavior(raw_value)
|
||||||
|
except ValueError:
|
||||||
|
valid_values = [behavior.value for behavior in SlotContextBehavior]
|
||||||
|
raise ValueError(f"Invalid slot context behavior: {raw_value}. Valid options are {valid_values}")
|
||||||
|
|
||||||
|
|
||||||
app_settings = AppSettings()
|
app_settings = AppSettings()
|
||||||
|
|
|
@ -18,6 +18,8 @@ from django.template.defaulttags import CommentNode
|
||||||
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 app_settings, SlotContextBehavior
|
||||||
|
|
||||||
FILLED_SLOTS_CONTENT_CONTEXT_KEY = "_DJANGO_COMPONENTS_FILLED_SLOTS"
|
FILLED_SLOTS_CONTENT_CONTEXT_KEY = "_DJANGO_COMPONENTS_FILLED_SLOTS"
|
||||||
DEFAULT_SLOT_KEY = "_DJANGO_COMPONENTS_DEFAULT_SLOT"
|
DEFAULT_SLOT_KEY = "_DJANGO_COMPONENTS_DEFAULT_SLOT"
|
||||||
OUTER_CONTEXT_CONTEXT_KEY = "_DJANGO_COMPONENTS_OUTER_CONTEXT"
|
OUTER_CONTEXT_CONTEXT_KEY = "_DJANGO_COMPONENTS_OUTER_CONTEXT"
|
||||||
|
@ -122,8 +124,28 @@ class SlotNode(Node, TemplateAwareNodeMixin):
|
||||||
raise TemplateSyntaxError()
|
raise TemplateSyntaxError()
|
||||||
extra_context[alias] = UserSlotVar(self, context)
|
extra_context[alias] = UserSlotVar(self, context)
|
||||||
|
|
||||||
with context.update(extra_context):
|
used_ctx = self.resolve_slot_context(context)
|
||||||
return nodelist.render(context)
|
with used_ctx.update(extra_context):
|
||||||
|
return nodelist.render(used_ctx)
|
||||||
|
|
||||||
|
def resolve_slot_context(self, context: Context) -> Context:
|
||||||
|
"""
|
||||||
|
Prepare the context used in a slot fill based on the settings.
|
||||||
|
|
||||||
|
See SlotContextBehavior for the description of each option.
|
||||||
|
"""
|
||||||
|
root_ctx = context.get(OUTER_CONTEXT_CONTEXT_KEY, Context())
|
||||||
|
|
||||||
|
if app_settings.SLOT_CONTEXT_BEHAVIOR == SlotContextBehavior.ALLOW_OVERRIDE:
|
||||||
|
return context
|
||||||
|
elif app_settings.SLOT_CONTEXT_BEHAVIOR == SlotContextBehavior.ISOLATED:
|
||||||
|
return root_ctx
|
||||||
|
elif app_settings.SLOT_CONTEXT_BEHAVIOR == SlotContextBehavior.PREFER_ROOT:
|
||||||
|
new_context = context.__copy__()
|
||||||
|
new_context.push(root_ctx)
|
||||||
|
return new_context
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown value for SLOT_CONTEXT_BEHAVIOR: '{app_settings.SLOT_CONTEXT_BEHAVIOR}'")
|
||||||
|
|
||||||
|
|
||||||
class BaseFillNode(Node):
|
class BaseFillNode(Node):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue