Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
JuroOravec authored Apr 17, 2024
2 parents 267a196 + c422f20 commit 23528b0
Show file tree
Hide file tree
Showing 13 changed files with 939 additions and 220 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Read on to learn about the details!

## Release notes

**Version 0.67** CHANGED the default way how context variables are resolved in slots. See the [documentation](#isolate-components-slots) for more details.

🚨📢 **Version 0.5** CHANGES THE SYNTAX for components. `component_block` is now `component`, and `component` blocks need an ending `endcomponent` tag. The new `python manage.py upgradecomponent` command can be used to upgrade a directory (use --path argument to point to each dir) of components to the new syntax automatically.

This change is done to simplify the API in anticipation of a 1.0 release of django_components. After 1.0 we intend to be stricter with big changes like this in point releases.
Expand Down Expand Up @@ -704,6 +706,27 @@ COMPONENTS = {
}
```

### Isolate components' slots

What variables should be available from inside a component slot?

By default, variables inside component slots are preferentially taken from the root context.
This is similar to [how Vue renders slots](https://vuejs.org/guide/components/slots.html#render-scope),
except that, if variable is not found in the root, then the surrounding context is searched too.

You can change this with the `slot_contet_behavior` setting. Options are:
- `"prefer_root"` - Default - as described above
- `"isolated"` - Same behavior as Vue - variables are taken ONLY from the root context
- `"allow_override"` - slot context variables are taken from its surroundings (default before v0.67)

```python
COMPONENTS = {
"slot_context_behavior": "isolated",
}
```

For further details and examples, see [SlotContextBehavior](https://github.com/EmilStenstrom/django-components/blob/master/src/django_components/app_settings.py#L12).

## Logging and debugging

Django components supports [logging with Django](https://docs.djangoproject.com/en/5.0/howto/logging/#logging-how-to). This can help with troubleshooting.
Expand Down
8 changes: 8 additions & 0 deletions sampleproject/sampleproject/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@

WSGI_APPLICATION = "sampleproject.wsgi.application"

# COMPONENTS = {
# "autodiscover": True,
# "libraries": [],
# "template_cache_size": 128,
# "context_behavior": "isolated", # "global" | "isolated"
# "slot_context_behavior": "prefer_root", # "allow_override" | "prefer_root" | "isolated"
# }


# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
Expand Down
119 changes: 112 additions & 7 deletions src/django_components/app_settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from enum import Enum
from typing import List
from typing import Dict, List

from django.conf import settings

Expand All @@ -9,25 +9,118 @@ class ContextBehavior(str, Enum):
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:
def __init__(self) -> None:
self.settings = getattr(settings, "COMPONENTS", {})
@property
def settings(self) -> Dict:
return getattr(settings, "COMPONENTS", {})

@property
def AUTODISCOVER(self) -> bool:
return self.settings.setdefault("autodiscover", True)
return self.settings.get("autodiscover", True)

@property
def LIBRARIES(self) -> List:
return self.settings.setdefault("libraries", [])
return self.settings.get("libraries", [])

@property
def TEMPLATE_CACHE_SIZE(self) -> int:
return self.settings.setdefault("template_cache_size", 128)
return self.settings.get("template_cache_size", 128)

@property
def CONTEXT_BEHAVIOR(self) -> ContextBehavior:
raw_value = self.settings.setdefault("context_behavior", ContextBehavior.GLOBAL.value)
raw_value = self.settings.get("context_behavior", ContextBehavior.GLOBAL.value)
return self._validate_context_behavior(raw_value)

def _validate_context_behavior(self, raw_value: ContextBehavior) -> ContextBehavior:
Expand All @@ -37,5 +130,17 @@ def _validate_context_behavior(self, raw_value: ContextBehavior) -> ContextBehav
valid_values = [behavior.value for behavior in ContextBehavior]
raise ValueError(f"Invalid context behavior: {raw_value}. Valid options are {valid_values}")

@property
def SLOT_CONTEXT_BEHAVIOR(self) -> SlotContextBehavior:
raw_value = self.settings.get("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()
Loading

0 comments on commit 23528b0

Please sign in to comment.