mirror of
https://github.com/django-components/django-components.git
synced 2025-09-26 23:49:07 +00:00
refactor: change fill alias from "as var" to default=var (#504)
This commit is contained in:
parent
c07f0e6341
commit
edb2f347f2
5 changed files with 238 additions and 111 deletions
29
README.md
29
README.md
|
@ -44,6 +44,12 @@ Read on to learn about the details!
|
|||
|
||||
## Release notes
|
||||
|
||||
🚨📢 **Version 0.77** CHANGED the syntax for accessing default slot content.
|
||||
- Previously, the syntax was
|
||||
`{% fill "my_slot" as "alias" %}` and `{{ alias.default }}`.
|
||||
- Now, the syntax is
|
||||
`{% fill "my_slot" default="alias" %}` and `{{ alias }}`.
|
||||
|
||||
**Version 0.74** introduces `html_attrs` tag and `prefix:key=val` construct for passing dicts to components.
|
||||
|
||||
🚨📢 **Version 0.70**
|
||||
|
@ -627,13 +633,26 @@ Which you can then use are regular default slot:
|
|||
|
||||
_Added in version 0.26_
|
||||
|
||||
Certain properties of a slot can be accessed from within a 'fill' context. They are provided as attributes on a user-defined alias of the targeted slot. For instance, let's say you're filling a slot called 'body'. To access properties of this slot, alias it using the 'as' keyword to a new name -- or keep the original name. With the new slot alias, you can call `<alias>.default` to insert the default content.
|
||||
> NOTE: In version 0.77, the syntax was changed from
|
||||
> ```django
|
||||
> {% fill "my_slot" as "alias" %} {{ alias.default }}
|
||||
> ```
|
||||
> to
|
||||
> ```django
|
||||
> {% fill "my_slot" default="slot_default" %} {{ slot_default }}
|
||||
> ```
|
||||
|
||||
Notice the use of `as "body"` below:
|
||||
Sometimes you may want to keep the original slot, but only wrap or prepend/append content to it. To do so, you can access the default slot via the `default` kwarg.
|
||||
|
||||
Similarly to the `data` attribute, you specify the variable name through which the default slot will be made available.
|
||||
|
||||
For instance, let's say you're filling a slot called 'body'. To render the original slot, assign it to a variable using the `'default'` keyword. You then render this variable to insert the default content:
|
||||
|
||||
```htmldjango
|
||||
{% component "calendar" date="2020-06-06" %}
|
||||
{% fill "body" as "body" %}{{ body.default }}. Have a great day!{% endfill %}
|
||||
{% fill "body" default="body_default" %}
|
||||
{{ body_default }}. Have a great day!
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
|
@ -832,12 +851,12 @@ While this does not:
|
|||
```
|
||||
|
||||
Note: You cannot set the `data` attribute and
|
||||
[slot alias (`as var` syntax)](#accessing-original-content-of-slots)
|
||||
[`default` attribute)](#accessing-original-content-of-slots)
|
||||
to the same name. This raises an error:
|
||||
|
||||
```django
|
||||
{% component "my_comp" %}
|
||||
{% fill "content" data="slot_var" as "slot_var" %}
|
||||
{% fill "content" data="slot_var" default="slot_var" %}
|
||||
{{ slot_var.input }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
|
|
|
@ -358,8 +358,8 @@ class Component(View, metaclass=SimplifiedInterfaceMediaDefiningClass):
|
|||
slot_fills = {
|
||||
slot_name: FillContent(
|
||||
nodes=NodeList([TextNode(escape(content) if escape_content else content)]),
|
||||
alias=None,
|
||||
scope=None,
|
||||
slot_default_var=None,
|
||||
slot_data_var=None,
|
||||
)
|
||||
for (slot_name, content) in slots_data.items()
|
||||
}
|
||||
|
@ -422,12 +422,12 @@ class ComponentNode(Node):
|
|||
f"Detected duplicate fill tag name '{resolved_name}'."
|
||||
)
|
||||
|
||||
resolved_fill_alias = fill_node.resolve_alias(context, resolved_component_name)
|
||||
resolved_scope_var = fill_node.resolve_scope(context, resolved_component_name)
|
||||
resolved_slot_default_var = fill_node.resolve_slot_default(context, resolved_component_name)
|
||||
resolved_slot_data_var = fill_node.resolve_slot_data(context, resolved_component_name)
|
||||
fill_content[resolved_name] = FillContent(
|
||||
nodes=fill_node.nodelist,
|
||||
alias=resolved_fill_alias,
|
||||
scope=resolved_scope_var,
|
||||
slot_default_var=resolved_slot_default_var,
|
||||
slot_data_var=resolved_slot_data_var,
|
||||
)
|
||||
|
||||
component: Component = component_cls(
|
||||
|
|
|
@ -24,8 +24,8 @@ DEFAULT_SLOT_KEY = "_DJANGO_COMPONENTS_DEFAULT_SLOT"
|
|||
|
||||
SlotId = str
|
||||
SlotName = str
|
||||
AliasName = str
|
||||
ScopeName = str
|
||||
SlotDefaultName = str
|
||||
SlotDataName = str
|
||||
|
||||
|
||||
class FillContent(NamedTuple):
|
||||
|
@ -44,8 +44,8 @@ class FillContent(NamedTuple):
|
|||
"""
|
||||
|
||||
nodes: NodeList
|
||||
alias: Optional[AliasName]
|
||||
scope: Optional[ScopeName]
|
||||
slot_default_var: Optional[SlotDefaultName]
|
||||
slot_data_var: Optional[SlotDataName]
|
||||
|
||||
|
||||
class Slot(NamedTuple):
|
||||
|
@ -80,27 +80,26 @@ class SlotFill(NamedTuple):
|
|||
is_filled: bool
|
||||
nodelist: NodeList
|
||||
context_data: Dict
|
||||
alias: Optional[AliasName]
|
||||
scope: Optional[ScopeName]
|
||||
slot_default_var: Optional[SlotDefaultName]
|
||||
slot_data_var: Optional[SlotDataName]
|
||||
|
||||
|
||||
class UserSlotVar:
|
||||
class LazySlot:
|
||||
"""
|
||||
Extensible mechanism for offering 'fill' blocks in template access to properties
|
||||
of parent slot.
|
||||
Bound a slot and a context, so we can move these around as a single variable,
|
||||
and lazily render the slot with given context only once coerced to string.
|
||||
|
||||
How it works: At render time, SlotNode(s) that have been aliased in the fill tag
|
||||
of the component instance create an instance of UserSlotVar. This instance is made
|
||||
available to the rendering context on a key matching the slot alias (see
|
||||
SlotNode.render() for implementation).
|
||||
This is used to access slots as variables inside the templates. When a LazySlot
|
||||
is rendered in the template with `{{ my_lazy_slot }}`, it will output the contents
|
||||
of the slot.
|
||||
"""
|
||||
|
||||
def __init__(self, slot: "SlotNode", context: Context):
|
||||
self._slot = slot
|
||||
self._context = context
|
||||
|
||||
@property
|
||||
def default(self) -> str:
|
||||
# Render the slot when the template coerces LazySlot to string
|
||||
def __str__(self) -> str:
|
||||
return mark_safe(self._slot.nodelist.render(self._context))
|
||||
|
||||
|
||||
|
@ -141,20 +140,28 @@ class SlotNode(Node):
|
|||
|
||||
extra_context: Dict[str, Any] = {}
|
||||
|
||||
# If slot is using alias `{% slot "myslot" as "abc" %}`, then set the "abc" to
|
||||
# the context, so users can refer to the slot from within the slot.
|
||||
if slot_fill.alias:
|
||||
if not slot_fill.alias.isidentifier():
|
||||
raise TemplateSyntaxError(f"Invalid fill alias. Must be a valid identifier. Got '{slot_fill.alias}'")
|
||||
extra_context[slot_fill.alias] = UserSlotVar(self, context)
|
||||
# If slot fill is using `{% fill "myslot" default="abc" %}`, then set the "abc" to
|
||||
# the context, so users can refer to the default slot from within the fill content.
|
||||
default_var = slot_fill.slot_default_var
|
||||
if default_var:
|
||||
if not default_var.isidentifier():
|
||||
raise TemplateSyntaxError(
|
||||
f"Slot default alias in fill '{self.name}' must be a valid identifier. Got '{default_var}'"
|
||||
)
|
||||
extra_context[default_var] = LazySlot(self, context)
|
||||
|
||||
# Expose the kwargs that were passed to the `{% slot %}` tag. These kwargs
|
||||
# are made available through a variable name that was set on the `{% fill %}`
|
||||
# tag.
|
||||
if slot_fill.scope:
|
||||
data_var = slot_fill.slot_data_var
|
||||
if data_var:
|
||||
if not data_var.isidentifier():
|
||||
raise TemplateSyntaxError(
|
||||
f"Slot data alias in fill '{self.name}' must be a valid identifier. Got '{data_var}'"
|
||||
)
|
||||
slot_kwargs = safe_resolve_dict(self.slot_kwargs, context)
|
||||
slot_kwargs = process_aggregate_kwargs(slot_kwargs)
|
||||
extra_context[slot_fill.scope] = slot_kwargs
|
||||
extra_context[data_var] = slot_kwargs
|
||||
|
||||
# For the user-provided slot fill, we want to use the context of where the slot
|
||||
# came from (or current context if configured so)
|
||||
|
@ -192,17 +199,17 @@ class FillNode(Node):
|
|||
self,
|
||||
nodelist: NodeList,
|
||||
name_fexp: FilterExpression,
|
||||
alias_fexp: Optional[FilterExpression] = None,
|
||||
scope_fexp: Optional[FilterExpression] = None,
|
||||
slot_default_var_fexp: Optional[FilterExpression] = None,
|
||||
slot_data_var_fexp: Optional[FilterExpression] = None,
|
||||
is_implicit: bool = False,
|
||||
node_id: Optional[str] = None,
|
||||
):
|
||||
self.node_id = node_id or gen_id()
|
||||
self.nodelist = nodelist
|
||||
self.name_fexp = name_fexp
|
||||
self.alias_fexp = alias_fexp
|
||||
self.slot_default_var_fexp = slot_default_var_fexp
|
||||
self.is_implicit = is_implicit
|
||||
self.scope_fexp = scope_fexp
|
||||
self.slot_data_var_fexp = slot_data_var_fexp
|
||||
self.component_id: Optional[str] = None
|
||||
|
||||
def render(self, context: Context) -> str:
|
||||
|
@ -215,11 +222,11 @@ class FillNode(Node):
|
|||
def __repr__(self) -> str:
|
||||
return f"<{type(self)} Name: {self.name_fexp}. Contents: {repr(self.nodelist)}.>"
|
||||
|
||||
def resolve_alias(self, context: Context, component_name: Optional[str] = None) -> Optional[str]:
|
||||
return self.resolve_fexp("alias", self.alias_fexp, context, component_name)
|
||||
def resolve_slot_default(self, context: Context, component_name: Optional[str] = None) -> Optional[str]:
|
||||
return self.resolve_fexp("slot default", self.slot_default_var_fexp, context, component_name)
|
||||
|
||||
def resolve_scope(self, context: Context, component_name: Optional[str] = None) -> Optional[str]:
|
||||
return self.resolve_fexp("scope", self.scope_fexp, context, component_name)
|
||||
def resolve_slot_data(self, context: Context, component_name: Optional[str] = None) -> Optional[str]:
|
||||
return self.resolve_fexp("slot data", self.slot_data_var_fexp, context, component_name)
|
||||
|
||||
def resolve_fexp(
|
||||
self,
|
||||
|
@ -232,14 +239,14 @@ class FillNode(Node):
|
|||
return None
|
||||
|
||||
try:
|
||||
resolved_alias = resolve_expression_as_identifier(context, fexp)
|
||||
resolved_name = resolve_expression_as_identifier(context, fexp)
|
||||
except ValueError as err:
|
||||
raise TemplateSyntaxError(
|
||||
f"Fill tag {name} '{fexp.var}' in component {component_name}"
|
||||
f"does not resolve to a valid Python identifier."
|
||||
) from err
|
||||
|
||||
return resolved_alias
|
||||
return resolved_name
|
||||
|
||||
|
||||
def parse_slot_fill_nodes_from_component_nodelist(
|
||||
|
@ -363,8 +370,8 @@ def resolve_slots(
|
|||
is_filled=True,
|
||||
nodelist=fill.nodes,
|
||||
context_data=context_data,
|
||||
alias=fill.alias,
|
||||
scope=fill.scope,
|
||||
slot_default_var=fill.slot_default_var,
|
||||
slot_data_var=fill.slot_data_var,
|
||||
)
|
||||
for name, fill in fill_content.items()
|
||||
}
|
||||
|
@ -452,8 +459,8 @@ def resolve_slots(
|
|||
is_filled=False,
|
||||
nodelist=slot.nodelist,
|
||||
context_data=context_data,
|
||||
alias=None,
|
||||
scope=None,
|
||||
slot_default_var=None,
|
||||
slot_data_var=None,
|
||||
)
|
||||
# Since the slot's default CAN include other slots (because it's defined in
|
||||
# the same template), we need to enqueue the slot's children
|
||||
|
@ -500,8 +507,8 @@ def _resolve_default_slot(
|
|||
is_filled=default_fill.is_filled,
|
||||
nodelist=default_fill.nodelist,
|
||||
context_data=default_fill.context_data,
|
||||
alias=default_fill.alias,
|
||||
scope=default_fill.scope,
|
||||
slot_default_var=default_fill.slot_default_var,
|
||||
slot_data_var=default_fill.slot_data_var,
|
||||
# Updated fields
|
||||
name=slot.name,
|
||||
escaped_name=_escape_slot_name(slot.name),
|
||||
|
|
|
@ -31,6 +31,7 @@ register = django.template.Library()
|
|||
SLOT_REQUIRED_OPTION_KEYWORD = "required"
|
||||
SLOT_DEFAULT_OPTION_KEYWORD = "default"
|
||||
SLOT_DATA_ATTR = "data"
|
||||
SLOT_DEFAULT_ATTR = "default"
|
||||
|
||||
|
||||
def get_components_from_registry(registry: ComponentRegistry) -> List["Component"]:
|
||||
|
@ -147,7 +148,7 @@ def do_fill(parser: Parser, token: Token) -> FillNode:
|
|||
"""
|
||||
# e.g. {% fill <name> %}
|
||||
tag_name, *args = token.split_contents()
|
||||
slot_name_fexp, alias_fexp, scope_var_fexp = _parse_fill_args(parser, args, tag_name)
|
||||
slot_name_fexp, slot_default_var_fexp, slot_data_var_fexp = _parse_fill_args(parser, args, tag_name)
|
||||
|
||||
# Use a unique ID to be able to tie the fill nodes with components and slots
|
||||
# NOTE: MUST be called BEFORE `parser.parse()` to ensure predictable numbering
|
||||
|
@ -160,8 +161,8 @@ def do_fill(parser: Parser, token: Token) -> FillNode:
|
|||
fill_node = FillNode(
|
||||
nodelist,
|
||||
name_fexp=slot_name_fexp,
|
||||
alias_fexp=alias_fexp,
|
||||
scope_fexp=scope_var_fexp,
|
||||
slot_default_var_fexp=slot_default_var_fexp,
|
||||
slot_data_var_fexp=slot_data_var_fexp,
|
||||
node_id=fill_id,
|
||||
)
|
||||
|
||||
|
@ -382,19 +383,13 @@ def _parse_fill_args(
|
|||
) -> Tuple[FilterExpression, Optional[FilterExpression], Optional[FilterExpression]]:
|
||||
if not len(bits):
|
||||
raise TemplateSyntaxError(
|
||||
"'fill' tag does not match pattern " f"{{% fill <name> [{SLOT_DATA_ATTR}=val] [as alias] %}}. "
|
||||
"'fill' tag does not match pattern "
|
||||
f"{{% fill <name> [{SLOT_DATA_ATTR}=val] [{SLOT_DEFAULT_ATTR}=slot_var] %}} "
|
||||
)
|
||||
|
||||
slot_name = bits.pop(0)
|
||||
slot_name_fexp = parser.compile_filter(slot_name)
|
||||
|
||||
alias_fexp: Optional[FilterExpression] = None
|
||||
# e.g. {% fill <name> as <alias> %}
|
||||
if len(bits) >= 2 and bits[-2].lower() == "as":
|
||||
alias = bits.pop()
|
||||
bits.pop() # Remove the "as" keyword
|
||||
alias_fexp = parser.compile_filter(alias)
|
||||
|
||||
# Even tho we want to parse only single kwarg, we use the same logic for parsing
|
||||
# as we use for other tags, for consistency.
|
||||
_, tag_kwarg_pairs = parse_bits(
|
||||
|
@ -410,17 +405,29 @@ def _parse_fill_args(
|
|||
raise TemplateSyntaxError(f"'{tag_name}' received multiple values for keyword argument '{key}'")
|
||||
tag_kwargs[key] = val
|
||||
|
||||
scope_var_fexp: Optional[FilterExpression] = None
|
||||
# Extract known kwargs
|
||||
slot_data_var_fexp: Optional[FilterExpression] = None
|
||||
if SLOT_DATA_ATTR in tag_kwargs:
|
||||
scope_var_fexp = tag_kwargs.pop(SLOT_DATA_ATTR)
|
||||
if not is_wrapped_in_quotes(scope_var_fexp.token):
|
||||
slot_data_var_fexp = tag_kwargs.pop(SLOT_DATA_ATTR)
|
||||
if not is_wrapped_in_quotes(slot_data_var_fexp.token):
|
||||
raise TemplateSyntaxError(
|
||||
f"Value of '{SLOT_DATA_ATTR}' in '{tag_name}' tag must be a string literal, got '{scope_var_fexp}'"
|
||||
f"Value of '{SLOT_DATA_ATTR}' in '{tag_name}' tag must be a string literal, got '{slot_data_var_fexp}'"
|
||||
)
|
||||
|
||||
if scope_var_fexp and alias_fexp and scope_var_fexp.token == alias_fexp.token:
|
||||
slot_default_var_fexp: Optional[FilterExpression] = None
|
||||
if SLOT_DEFAULT_ATTR in tag_kwargs:
|
||||
slot_default_var_fexp = tag_kwargs.pop(SLOT_DEFAULT_ATTR)
|
||||
if not is_wrapped_in_quotes(slot_default_var_fexp.token):
|
||||
raise TemplateSyntaxError(
|
||||
f"'{tag_name}' received the same string for slot alias (as ...) and slot data ({SLOT_DATA_ATTR}=...)"
|
||||
f"Value of '{SLOT_DEFAULT_ATTR}' in '{tag_name}' tag must be a string literal,"
|
||||
f" got '{slot_default_var_fexp}'"
|
||||
)
|
||||
|
||||
# data and default cannot be bound to the same variable
|
||||
if slot_data_var_fexp and slot_default_var_fexp and slot_data_var_fexp.token == slot_default_var_fexp.token:
|
||||
raise TemplateSyntaxError(
|
||||
f"'{tag_name}' received the same string for slot default ({SLOT_DEFAULT_ATTR}=...)"
|
||||
f" and slot data ({SLOT_DATA_ATTR}=...)"
|
||||
)
|
||||
|
||||
if len(tag_kwargs):
|
||||
|
@ -428,7 +435,7 @@ def _parse_fill_args(
|
|||
extra_keys = ", ".join(extra_keywords)
|
||||
raise TemplateSyntaxError(f"'{tag_name}' received unexpected kwargs: {extra_keys}")
|
||||
|
||||
return slot_name_fexp, alias_fexp, scope_var_fexp
|
||||
return slot_name_fexp, slot_default_var_fexp, slot_data_var_fexp
|
||||
|
||||
|
||||
def _get_positional_param(
|
||||
|
|
|
@ -912,7 +912,7 @@ class ConditionalSlotTests(BaseTestCase):
|
|||
self.assertHTMLEqual(rendered, '<p id="a">Override A</p><p id="b">Override B</p>')
|
||||
|
||||
|
||||
class SlotSuperTests(BaseTestCase):
|
||||
class SlotDefaultTests(BaseTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
@ -924,13 +924,13 @@ class SlotSuperTests(BaseTestCase):
|
|||
super().tearDownClass()
|
||||
component.registry.clear()
|
||||
|
||||
def test_basic_super_functionality(self):
|
||||
def test_basic(self):
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" %}
|
||||
{% fill "header" as "header" %}Before: {{ header.default }}{% endfill %}
|
||||
{% fill "main" as "main" %}{{ main.default }}{% endfill %}
|
||||
{% fill "footer" as "footer" %}{{ footer.default }}, after{% endfill %}
|
||||
{% fill "header" default="header" %}Before: {{ header }}{% endfill %}
|
||||
{% fill "main" default="main" %}{{ main }}{% endfill %}
|
||||
{% fill "footer" default="footer" %}{{ footer }}, after{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
|
@ -947,13 +947,13 @@ class SlotSuperTests(BaseTestCase):
|
|||
""",
|
||||
)
|
||||
|
||||
def test_multiple_super_calls(self):
|
||||
def test_multiple_calls(self):
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" %}
|
||||
{% fill "header" as "header" %}
|
||||
First: {{ header.default }};
|
||||
Second: {{ header.default }}
|
||||
{% fill "header" default="header" %}
|
||||
First: {{ header }};
|
||||
Second: {{ header }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
|
@ -971,14 +971,16 @@ class SlotSuperTests(BaseTestCase):
|
|||
""",
|
||||
)
|
||||
|
||||
def test_super_under_if_node(self):
|
||||
def test_under_if_and_forloop(self):
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" %}
|
||||
{% fill "header" as "header" %}
|
||||
{% fill "header" default="header" %}
|
||||
{% for i in range %}
|
||||
{% if forloop.first %}First {{ header.default }}
|
||||
{% else %}Later {{ header.default }}
|
||||
{% if forloop.first %}
|
||||
First {{ header }}
|
||||
{% else %}
|
||||
Later {{ header }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfill %}
|
||||
|
@ -998,6 +1000,52 @@ class SlotSuperTests(BaseTestCase):
|
|||
""",
|
||||
)
|
||||
|
||||
def test_nested_fills(self):
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" %}
|
||||
{% fill "header" default="header1" %}
|
||||
header1_in_header1: {{ header1 }}
|
||||
{% component "test" %}
|
||||
{% fill "header" default="header2" %}
|
||||
header1_in_header2: {{ header1 }}
|
||||
header2_in_header2: {{ header2 }}
|
||||
{% endfill %}
|
||||
{% fill "footer" default="footer2" %}
|
||||
header1_in_footer2: {{ header1 }}
|
||||
footer2_in_footer2: {{ footer2 }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<header>
|
||||
header1_in_header1: Default header
|
||||
<custom-template>
|
||||
<header>
|
||||
header1_in_header2: Default header
|
||||
header2_in_header2: Default header
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>
|
||||
header1_in_footer2: Default header
|
||||
footer2_in_footer2: Default footer
|
||||
</footer>
|
||||
</custom-template>
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
class TemplateSyntaxErrorTests(BaseTestCase):
|
||||
@classmethod
|
||||
|
@ -1310,11 +1358,11 @@ class ComponentNestingTests(BaseTestCase):
|
|||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
@override_settings(COMPONENTS={"context_behavior": "django"})
|
||||
def test_component_nesting_component_with_fill_and_super__django(self):
|
||||
def test_component_nesting_component_with_slot_default__django(self):
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "dashboard" %}
|
||||
{% fill "header" as "h" %} Hello! {{ h.default }} {% endfill %}
|
||||
{% fill "header" default="h" %} Hello! {{ h }} {% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
|
@ -1338,11 +1386,11 @@ class ComponentNestingTests(BaseTestCase):
|
|||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
@override_settings(COMPONENTS={"context_behavior": "isolated"})
|
||||
def test_component_nesting_component_with_fill_and_super__isolated(self):
|
||||
def test_component_nesting_component_with_slot_default__isolated(self):
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "dashboard" %}
|
||||
{% fill "header" as "h" %} Hello! {{ h.default }} {% endfill %}
|
||||
{% fill "header" default="h" %} Hello! {{ h }} {% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
|
@ -2194,8 +2242,8 @@ class IterationFillTest(BaseTestCase):
|
|||
{% component "slot_in_a_loop" objects=objects %}
|
||||
{% fill "slot_inner" %}
|
||||
{% component "slot_in_a_loop" objects=object.inner %}
|
||||
{% fill "slot_inner" as "super_slot_inner" %}
|
||||
{{ super_slot_inner.default }}
|
||||
{% fill "slot_inner" default="super_slot_inner" %}
|
||||
{{ super_slot_inner }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% endfill %}
|
||||
|
@ -2228,8 +2276,8 @@ class IterationFillTest(BaseTestCase):
|
|||
{% component "slot_in_a_loop" objects=objects %}
|
||||
{% fill "slot_inner" %}
|
||||
{% component "slot_in_a_loop" objects=object.inner %}
|
||||
{% fill "slot_inner" as "super_slot_inner" %}
|
||||
{{ super_slot_inner.default }}
|
||||
{% fill "slot_inner" default="super_slot_inner" %}
|
||||
{{ super_slot_inner }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% endfill %}
|
||||
|
@ -2256,9 +2304,9 @@ class IterationFillTest(BaseTestCase):
|
|||
{% fill "slot_inner" %}
|
||||
{{ outer_scope_variable_1 }}
|
||||
{% component "slot_in_a_loop" objects=object.inner %}
|
||||
{% fill "slot_inner" as "super_slot_inner" %}
|
||||
{% fill "slot_inner" default="super_slot_inner" %}
|
||||
{{ outer_scope_variable_2 }}
|
||||
{{ super_slot_inner.default }}
|
||||
{{ super_slot_inner }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% endfill %}
|
||||
|
@ -2311,9 +2359,9 @@ class IterationFillTest(BaseTestCase):
|
|||
{% fill "slot_inner" %}
|
||||
{{ outer_scope_variable_1 }}
|
||||
{% component "slot_in_a_loop" objects=object.inner %}
|
||||
{% fill "slot_inner" as "super_slot_inner" %}
|
||||
{% fill "slot_inner" default="super_slot_inner" %}
|
||||
{{ outer_scope_variable_2 }}
|
||||
{{ super_slot_inner.default }}
|
||||
{{ super_slot_inner }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% endfill %}
|
||||
|
@ -2358,9 +2406,9 @@ class IterationFillTest(BaseTestCase):
|
|||
{% fill "slot_inner" %}
|
||||
{{ outer_scope_variable_1|safe }}
|
||||
{% component "slot_in_a_loop" objects=objects %}
|
||||
{% fill "slot_inner" as "super_slot_inner" %}
|
||||
{% fill "slot_inner" default="super_slot_inner" %}
|
||||
{{ outer_scope_variable_2|safe }}
|
||||
{{ super_slot_inner.default }}
|
||||
{{ super_slot_inner }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% endfill %}
|
||||
|
@ -2401,7 +2449,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% slot "my_slot" abc=abc def=var123 %}Default text{% endslot %}
|
||||
{% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
@ -2416,7 +2464,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
{% component "test" %}
|
||||
{% fill "my_slot" data="slot_data_in_fill" %}
|
||||
{{ slot_data_in_fill.abc }}
|
||||
{{ slot_data_in_fill.def }}
|
||||
{{ slot_data_in_fill.var123 }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
|
@ -2435,7 +2483,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% slot "my_slot" default abc=abc 123=var123 required %}Default text{% endslot %}
|
||||
{% slot "my_slot" default abc=abc var123=var123 required %}Default text{% endslot %}
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
@ -2450,7 +2498,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
{% component "test" %}
|
||||
{% fill "my_slot" data="slot_data_in_fill" %}
|
||||
{{ slot_data_in_fill.abc }}
|
||||
{{ slot_data_in_fill.123 }}
|
||||
{{ slot_data_in_fill.var123 }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
|
@ -2463,13 +2511,13 @@ class ScopedSlotTest(BaseTestCase):
|
|||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
def test_slot_data_fill_with_as(self):
|
||||
def test_slot_data_with_slot_default(self):
|
||||
@component.register("test")
|
||||
class TestComponent(component.Component):
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% slot "my_slot" abc=abc 123=var123 %}Default text{% endslot %}
|
||||
{% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
@ -2482,10 +2530,10 @@ class ScopedSlotTest(BaseTestCase):
|
|||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" %}
|
||||
{% fill "my_slot" data="slot_data_in_fill" as "slot_var" %}
|
||||
{{ slot_var.default }}
|
||||
{% fill "my_slot" data="slot_data_in_fill" default="slot_var" %}
|
||||
{{ slot_var }}
|
||||
{{ slot_data_in_fill.abc }}
|
||||
{{ slot_data_in_fill.123 }}
|
||||
{{ slot_data_in_fill.var123 }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
|
@ -2499,13 +2547,13 @@ class ScopedSlotTest(BaseTestCase):
|
|||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
def test_slot_data_raises_on_slot_data_and_as_same_var(self):
|
||||
def test_slot_data_raises_on_slot_data_and_slot_default_same_var(self):
|
||||
@component.register("test")
|
||||
class TestComponent(component.Component):
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% slot "my_slot" abc=abc 123=var123 %}Default text{% endslot %}
|
||||
{% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
@ -2518,14 +2566,14 @@ class ScopedSlotTest(BaseTestCase):
|
|||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" %}
|
||||
{% fill "my_slot" data="slot_var" as "slot_var" %}
|
||||
{{ slot_var.default }}
|
||||
{% fill "my_slot" data="slot_var" default="slot_var" %}
|
||||
{{ slot_var }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
with self.assertRaisesMessage(
|
||||
TemplateSyntaxError,
|
||||
"'fill' received the same string for slot alias (as ...) and slot data (data=...)",
|
||||
"'fill' received the same string for slot default (default=...) and slot data (data=...)",
|
||||
):
|
||||
Template(template).render(Context())
|
||||
|
||||
|
@ -2535,7 +2583,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% slot "my_slot" abc=abc 123=var123 %}Default text{% endslot %}
|
||||
{% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
@ -2585,7 +2633,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% slot "my_slot" abc=abc 123=var123 %}Default text{% endslot %}
|
||||
{% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
@ -2603,3 +2651,49 @@ class ScopedSlotTest(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = "<div> Default text </div>"
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
def test_nested_fills(self):
|
||||
@component.register("test")
|
||||
class TestComponent(component.Component):
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% slot "my_slot" abc=abc input=input %}Default text{% endslot %}
|
||||
</div>
|
||||
"""
|
||||
|
||||
def get_context_data(self, input):
|
||||
return {
|
||||
"abc": "def",
|
||||
"input": input,
|
||||
}
|
||||
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" input=1 %}
|
||||
{% fill "my_slot" data="data1" %}
|
||||
data1_in_slot1: {{ data1|safe }}
|
||||
{% component "test" input=2 %}
|
||||
{% fill "my_slot" data="data2" %}
|
||||
data1_in_slot2: {{ data1|safe }}
|
||||
data2_in_slot2: {{ data2|safe }}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
data1_in_slot1: {'abc': 'def', 'input': 1}
|
||||
<div>
|
||||
data1_in_slot2: {'abc': 'def', 'input': 1}
|
||||
data2_in_slot2: {'abc': 'def', 'input': 2}
|
||||
</div>
|
||||
</div>
|
||||
""",
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue