refactor: merge Base, Implicit and Named FillNodes into FillNode

This commit is contained in:
Juro Oravec 2024-04-15 10:56:28 +02:00
parent c0c9e145a9
commit 9d9462162a
3 changed files with 40 additions and 46 deletions

View file

@ -23,11 +23,8 @@ from django_components.component_registry import AlreadyRegistered, ComponentReg
from django_components.logger import logger from django_components.logger import logger
from django_components.middleware import is_dependency_middleware_active from django_components.middleware import is_dependency_middleware_active
from django_components.slots import ( from django_components.slots import (
DEFAULT_SLOT_KEY, FillNode,
OUTER_CONTEXT_CONTEXT_KEY,
FillContent, FillContent,
ImplicitFillNode,
NamedFillNode,
SlotName, SlotName,
render_component_template_with_slots, render_component_template_with_slots,
) )
@ -299,7 +296,7 @@ class ComponentNode(Node):
context_args: List[FilterExpression], context_args: List[FilterExpression],
context_kwargs: Mapping[str, FilterExpression], context_kwargs: Mapping[str, FilterExpression],
isolated_context: bool = False, isolated_context: bool = False,
fill_nodes: Sequence[Union[ImplicitFillNode, NamedFillNode]] = (), fill_nodes: Sequence[FillNode] = (),
) -> None: ) -> None:
self.name_fexp = name_fexp self.name_fexp = name_fexp
self.context_args = context_args or [] self.context_args = context_args or []
@ -330,7 +327,7 @@ class ComponentNode(Node):
resolved_context_args = [safe_resolve(arg, context) for arg in self.context_args] resolved_context_args = [safe_resolve(arg, context) for arg in self.context_args]
resolved_context_kwargs = {key: safe_resolve(kwarg, context) for key, kwarg in self.context_kwargs.items()} resolved_context_kwargs = {key: safe_resolve(kwarg, context) for key, kwarg in self.context_kwargs.items()}
if len(self.fill_nodes) == 1 and isinstance(self.fill_nodes[0], ImplicitFillNode): if len(self.fill_nodes) == 1 and self.fill_nodes[0].is_implicit:
fill_content: Dict[str, FillContent] = {DEFAULT_SLOT_KEY: FillContent(self.fill_nodes[0].nodelist, None)} fill_content: Dict[str, FillContent] = {DEFAULT_SLOT_KEY: FillContent(self.fill_nodes[0].nodelist, None)}
else: else:
fill_content = {} fill_content = {}

View file

@ -1,4 +1,5 @@
import difflib import difflib
import json
import sys import sys
from typing import Dict, List, NamedTuple, Optional, Sequence, Set, Tuple, Type, Union from typing import Dict, List, NamedTuple, Optional, Sequence, Set, Tuple, Type, Union
@ -13,7 +14,7 @@ else:
from typing import TypeAlias from typing import TypeAlias
from django.template import Context, Template from django.template import Context, Template
from django.template.base import FilterExpression, Node, NodeList, TextNode from django.template.base import FilterExpression, Node, NodeList, TextNode, Parser
from django.template.defaulttags import CommentNode 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
@ -149,12 +150,25 @@ class SlotNode(Node, TemplateAwareNodeMixin):
raise ValueError(f"Unknown value for SLOT_CONTEXT_BEHAVIOR: '{app_settings.SLOT_CONTEXT_BEHAVIOR}'") raise ValueError(f"Unknown value for SLOT_CONTEXT_BEHAVIOR: '{app_settings.SLOT_CONTEXT_BEHAVIOR}'")
class BaseFillNode(Node): class FillNode(Node):
def __init__(self, nodelist: NodeList): is_implicit: bool
self.nodelist: NodeList = nodelist """
Set when a `component` tag pair is passed template content that
excludes `fill` tags. Nodes of this type contribute their nodelists to slots marked
as 'default'.
"""
def __repr__(self) -> str: def __init__(
raise NotImplementedError self,
nodelist: NodeList,
name_fexp: FilterExpression,
alias_fexp: Optional[FilterExpression] = None,
is_implicit: bool = False,
):
self.nodelist: NodeList = nodelist
self.name_fexp = name_fexp
self.alias_fexp = alias_fexp
self.is_implicit = is_implicit
def render(self, context: Context) -> str: def render(self, context: Context) -> str:
raise TemplateSyntaxError( raise TemplateSyntaxError(
@ -162,19 +176,7 @@ class BaseFillNode(Node):
"You are probably seeing this because you have used one outside " "You are probably seeing this because you have used one outside "
"a {% component %} context." "a {% component %} context."
) )
class NamedFillNode(BaseFillNode):
def __init__(
self,
nodelist: NodeList,
name_fexp: FilterExpression,
alias_fexp: Optional[FilterExpression] = None,
):
super().__init__(nodelist)
self.name_fexp = name_fexp
self.alias_fexp = alias_fexp
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<{type(self)} Name: {self.name_fexp}. Contents: {repr(self.nodelist)}.>" return f"<{type(self)} Name: {self.name_fexp}. Contents: {repr(self.nodelist)}.>"
@ -192,17 +194,6 @@ class NamedFillNode(BaseFillNode):
return resolved_alias return resolved_alias
class ImplicitFillNode(BaseFillNode):
"""
Instantiated when a `component` tag pair is passed template content that
excludes `fill` tags. Nodes of this type contribute their nodelists to slots marked
as 'default'.
"""
def __repr__(self) -> str:
return f"<{type(self)} Contents: {repr(self.nodelist)}.>"
class _IfSlotFilledBranchNode(Node): class _IfSlotFilledBranchNode(Node):
def __init__(self, nodelist: NodeList) -> None: def __init__(self, nodelist: NodeList) -> None:
self.nodelist = nodelist self.nodelist = nodelist
@ -272,7 +263,7 @@ class IfSlotFilledNode(Node):
def parse_slot_fill_nodes_from_component_nodelist( def parse_slot_fill_nodes_from_component_nodelist(
component_nodelist: NodeList, component_nodelist: NodeList,
ComponentNodeCls: Type[Node], ComponentNodeCls: Type[Node],
) -> Sequence[Union[NamedFillNode, ImplicitFillNode]]: ) -> Sequence[FillNode]:
""" """
Given a component body (`django.template.NodeList`), find all slot fills, Given a component body (`django.template.NodeList`), find all slot fills,
whether defined explicitly with `{% fill %}` or implicitly. whether defined explicitly with `{% fill %}` or implicitly.
@ -291,7 +282,7 @@ def parse_slot_fill_nodes_from_component_nodelist(
Then this function returns the nodes (`django.template.Node`) for `fill "first_fill"` Then this function returns the nodes (`django.template.Node`) for `fill "first_fill"`
and `fill "second_fill"`. and `fill "second_fill"`.
""" """
fill_nodes: Sequence[Union[NamedFillNode, ImplicitFillNode]] = [] fill_nodes: Sequence[FillNode] = []
if _block_has_content(component_nodelist): if _block_has_content(component_nodelist):
for parse_fn in ( for parse_fn in (
_try_parse_as_default_fill, _try_parse_as_default_fill,
@ -314,11 +305,11 @@ def parse_slot_fill_nodes_from_component_nodelist(
def _try_parse_as_named_fill_tag_set( def _try_parse_as_named_fill_tag_set(
nodelist: NodeList, nodelist: NodeList,
ComponentNodeCls: Type[Node], ComponentNodeCls: Type[Node],
) -> Sequence[NamedFillNode]: ) -> Sequence[FillNode]:
result = [] result = []
seen_name_fexps: Set[FilterExpression] = set() seen_name_fexps: Set[FilterExpression] = set()
for node in nodelist: for node in nodelist:
if isinstance(node, NamedFillNode): if isinstance(node, FillNode):
if node.name_fexp in seen_name_fexps: if node.name_fexp in seen_name_fexps:
raise TemplateSyntaxError( raise TemplateSyntaxError(
f"Multiple fill tags cannot target the same slot name: " f"Multiple fill tags cannot target the same slot name: "
@ -338,11 +329,11 @@ def _try_parse_as_named_fill_tag_set(
def _try_parse_as_default_fill( def _try_parse_as_default_fill(
nodelist: NodeList, nodelist: NodeList,
ComponentNodeCls: Type[Node], ComponentNodeCls: Type[Node],
) -> Sequence[ImplicitFillNode]: ) -> Sequence[FillNode]:
nodes_stack: List[Node] = list(nodelist) nodes_stack: List[Node] = list(nodelist)
while nodes_stack: while nodes_stack:
node = nodes_stack.pop() node = nodes_stack.pop()
if isinstance(node, NamedFillNode): if isinstance(node, FillNode):
return [] return []
elif isinstance(node, ComponentNodeCls): elif isinstance(node, ComponentNodeCls):
# Stop searching here, as fill tags are permitted inside component blocks # Stop searching here, as fill tags are permitted inside component blocks
@ -351,7 +342,13 @@ def _try_parse_as_default_fill(
for nodelist_attr_name in node.child_nodelists: for nodelist_attr_name in node.child_nodelists:
nodes_stack.extend(getattr(node, nodelist_attr_name, [])) nodes_stack.extend(getattr(node, nodelist_attr_name, []))
else: else:
return [ImplicitFillNode(nodelist=nodelist)] return [
FillNode(
nodelist=nodelist,
name_fexp=FilterExpression(json.dumps(DEFAULT_SLOT_KEY), Parser('')),
is_implicit=True,
)
]
def _block_has_content(nodelist: NodeList) -> bool: def _block_has_content(nodelist: NodeList) -> bool:

View file

@ -19,7 +19,7 @@ from django_components.slots import (
IfSlotFilledConditionBranchNode, IfSlotFilledConditionBranchNode,
IfSlotFilledElseBranchNode, IfSlotFilledElseBranchNode,
IfSlotFilledNode, IfSlotFilledNode,
NamedFillNode, FillNode,
SlotNode, SlotNode,
_IfSlotFilledBranchNode, _IfSlotFilledBranchNode,
parse_slot_fill_nodes_from_component_nodelist, parse_slot_fill_nodes_from_component_nodelist,
@ -156,7 +156,7 @@ def do_slot(parser: Parser, token: Token) -> SlotNode:
@register.tag("fill") @register.tag("fill")
def do_fill(parser: Parser, token: Token) -> NamedFillNode: def do_fill(parser: Parser, token: Token) -> FillNode:
"""Block tag whose contents 'fill' (are inserted into) an identically named """Block tag whose contents 'fill' (are inserted into) an identically named
'slot'-block in the component template referred to by a parent component. 'slot'-block in the component template referred to by a parent component.
It exists to make component nesting easier. It exists to make component nesting easier.
@ -182,7 +182,7 @@ def do_fill(parser: Parser, token: Token) -> NamedFillNode:
nodelist = parser.parse(parse_until=["endfill"]) nodelist = parser.parse(parse_until=["endfill"])
parser.delete_first_token() parser.delete_first_token()
return NamedFillNode( return FillNode(
nodelist, nodelist,
name_fexp=FilterExpression(tgt_slot_name, tag), name_fexp=FilterExpression(tgt_slot_name, tag),
alias_fexp=alias_fexp, alias_fexp=alias_fexp,