mirror of
https://github.com/django-components/django-components.git
synced 2025-09-23 06:02:27 +00:00
refactor: simplify slot intermediate slot types
This commit is contained in:
parent
094e05054d
commit
8d3a2ba8db
2 changed files with 32 additions and 33 deletions
|
@ -2,7 +2,7 @@ import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, ClassVar, Dict, Iterable, List, Mapping, MutableMapping, Optional, Tuple, Type, Union
|
from typing import Any, ClassVar, Dict, List, Mapping, MutableMapping, Optional, Sequence, Tuple, Type, Union
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.forms.widgets import Media, MediaDefiningClass
|
from django.forms.widgets import Media, MediaDefiningClass
|
||||||
|
@ -23,12 +23,12 @@ 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 (
|
||||||
DefaultFillContent,
|
|
||||||
ImplicitFillNode,
|
ImplicitFillNode,
|
||||||
NamedFillContent,
|
|
||||||
NamedFillNode,
|
NamedFillNode,
|
||||||
|
FillContent,
|
||||||
SlotName,
|
SlotName,
|
||||||
render_component_template_with_slots,
|
render_component_template_with_slots,
|
||||||
|
DEFAULT_SLOT_KEY,
|
||||||
)
|
)
|
||||||
from django_components.utils import search
|
from django_components.utils import search
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ class Component(View, metaclass=SimplifiedInterfaceMediaDefiningClass):
|
||||||
self,
|
self,
|
||||||
registered_name: Optional[str] = None,
|
registered_name: Optional[str] = None,
|
||||||
outer_context: Optional[Context] = None,
|
outer_context: Optional[Context] = None,
|
||||||
fill_content: Union[DefaultFillContent, Iterable[NamedFillContent]] = (), # type: ignore
|
fill_content: dict[str, FillContent] = {}, # type: ignore
|
||||||
):
|
):
|
||||||
self.registered_name: Optional[str] = registered_name
|
self.registered_name: Optional[str] = registered_name
|
||||||
self.outer_context: Context = outer_context or Context()
|
self.outer_context: Context = outer_context or Context()
|
||||||
|
@ -280,14 +280,13 @@ class Component(View, metaclass=SimplifiedInterfaceMediaDefiningClass):
|
||||||
escape_content: bool = True,
|
escape_content: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Fill component slots outside of template rendering."""
|
"""Fill component slots outside of template rendering."""
|
||||||
self.fill_content = [
|
self.fill_content = {
|
||||||
(
|
slot_name: FillContent(
|
||||||
slot_name,
|
|
||||||
TextNode(escape(content) if escape_content else content),
|
TextNode(escape(content) if escape_content else content),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
for (slot_name, content) in slots_data.items()
|
for (slot_name, content) in slots_data.items()
|
||||||
]
|
}
|
||||||
|
|
||||||
|
|
||||||
class ComponentNode(Node):
|
class ComponentNode(Node):
|
||||||
|
@ -299,20 +298,14 @@ 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: Union[ImplicitFillNode, Iterable[NamedFillNode]] = (),
|
fill_nodes: Sequence[Union[ImplicitFillNode, NamedFillNode]] = (),
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name_fexp = name_fexp
|
self.name_fexp = name_fexp
|
||||||
self.context_args = context_args or []
|
self.context_args = context_args or []
|
||||||
self.context_kwargs = context_kwargs or {}
|
self.context_kwargs = context_kwargs or {}
|
||||||
self.isolated_context = isolated_context
|
self.isolated_context = isolated_context
|
||||||
self.fill_nodes = fill_nodes
|
self.fill_nodes = fill_nodes
|
||||||
self.nodelist = self._create_nodelist(fill_nodes)
|
self.nodelist = NodeList(fill_nodes)
|
||||||
|
|
||||||
def _create_nodelist(self, fill_nodes: Union[Iterable[Node], ImplicitFillNode]) -> NodeList:
|
|
||||||
if isinstance(fill_nodes, ImplicitFillNode):
|
|
||||||
return NodeList([fill_nodes])
|
|
||||||
else:
|
|
||||||
return NodeList(fill_nodes)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "<ComponentNode: {}. Contents: {!r}>".format(
|
return "<ComponentNode: {}. Contents: {!r}>".format(
|
||||||
|
@ -330,16 +323,18 @@ 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 isinstance(self.fill_nodes, ImplicitFillNode):
|
if len(self.fill_nodes) == 1 and isinstance(self.fill_nodes[0], ImplicitFillNode):
|
||||||
fill_content = self.fill_nodes.nodelist
|
fill_content: Dict[str, FillContent] = {
|
||||||
|
DEFAULT_SLOT_KEY: FillContent(self.fill_nodes[0].nodelist, None)
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
fill_content = []
|
fill_content = {}
|
||||||
for fill_node in self.fill_nodes:
|
for fill_node in self.fill_nodes:
|
||||||
# Note that outer component context is used to resolve variables in
|
# Note that outer component context is used to resolve variables in
|
||||||
# fill tag.
|
# fill tag.
|
||||||
resolved_name = fill_node.name_fexp.resolve(context)
|
resolved_name = fill_node.name_fexp.resolve(context)
|
||||||
resolved_fill_alias = fill_node.resolve_alias(context, resolved_component_name)
|
resolved_fill_alias = fill_node.resolve_alias(context, resolved_component_name)
|
||||||
fill_content.append((resolved_name, fill_node.nodelist, resolved_fill_alias))
|
fill_content[resolved_name] = FillContent(fill_node.nodelist, resolved_fill_alias)
|
||||||
|
|
||||||
component: Component = component_cls(
|
component: Component = component_cls(
|
||||||
registered_name=resolved_component_name,
|
registered_name=resolved_component_name,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import difflib
|
import difflib
|
||||||
import sys
|
import sys
|
||||||
from typing import Dict, Iterable, List, Optional, Set, Tuple, Type, Union
|
from typing import Dict, List, NamedTuple, Optional, Sequence, Set, Tuple, Type, Union
|
||||||
|
|
||||||
if sys.version_info[:2] < (3, 9):
|
if sys.version_info[:2] < (3, 9):
|
||||||
from typing import ChainMap
|
from typing import ChainMap
|
||||||
|
@ -19,16 +19,20 @@ from django.template.exceptions import TemplateSyntaxError
|
||||||
from django.utils.safestring import SafeString, mark_safe
|
from django.utils.safestring import SafeString, mark_safe
|
||||||
|
|
||||||
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"
|
||||||
|
|
||||||
# Type aliases
|
# Type aliases
|
||||||
|
|
||||||
SlotName = str
|
SlotName = str
|
||||||
AliasName = str
|
AliasName = str
|
||||||
|
|
||||||
DefaultFillContent: TypeAlias = NodeList
|
|
||||||
NamedFillContent = Tuple[SlotName, NodeList, Optional[AliasName]]
|
|
||||||
|
|
||||||
FillContent = Tuple[NodeList, Optional[AliasName]]
|
class FillContent(NamedTuple):
|
||||||
|
"""Data passed from component to slot to render that slot"""
|
||||||
|
nodes: NodeList
|
||||||
|
alias: Optional[AliasName]
|
||||||
|
|
||||||
|
|
||||||
FilledSlotsContext = ChainMap[Tuple[SlotName, Template], FillContent]
|
FilledSlotsContext = ChainMap[Tuple[SlotName, Template], FillContent]
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,7 +107,7 @@ class SlotNode(Node, TemplateAwareNodeMixin):
|
||||||
|
|
||||||
extra_context = {}
|
extra_context = {}
|
||||||
try:
|
try:
|
||||||
slot_fill_content: FillContent = filled_slots_map[(self.name, self.template)]
|
slot_fill_content = filled_slots_map[(self.name, self.template)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if self.is_required:
|
if self.is_required:
|
||||||
raise TemplateSyntaxError(
|
raise TemplateSyntaxError(
|
||||||
|
@ -244,7 +248,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],
|
||||||
) -> Union[Iterable[NamedFillNode], ImplicitFillNode]:
|
) -> Sequence[Union[NamedFillNode, ImplicitFillNode]]:
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
@ -263,7 +267,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: Union[Iterable[NamedFillNode], ImplicitFillNode] = []
|
fill_nodes: Sequence[Union[NamedFillNode, ImplicitFillNode]] = []
|
||||||
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,
|
||||||
|
@ -340,7 +344,7 @@ def _block_has_content(nodelist: NodeList) -> bool:
|
||||||
def render_component_template_with_slots(
|
def render_component_template_with_slots(
|
||||||
template: Template,
|
template: Template,
|
||||||
context: Context,
|
context: Context,
|
||||||
fill_content: Union[DefaultFillContent, Iterable[NamedFillContent]],
|
fill_content: Dict[str, FillContent],
|
||||||
registered_name: Optional[str],
|
registered_name: Optional[str],
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -363,16 +367,16 @@ def render_component_template_with_slots(
|
||||||
|
|
||||||
def _prepare_component_template_filled_slot_context(
|
def _prepare_component_template_filled_slot_context(
|
||||||
template: Template,
|
template: Template,
|
||||||
fill_content: Union[DefaultFillContent, Iterable[NamedFillContent]],
|
fill_content: Dict[str, FillContent],
|
||||||
slots_context: Optional[FilledSlotsContext],
|
slots_context: Optional[FilledSlotsContext],
|
||||||
registered_name: Optional[str],
|
registered_name: Optional[str],
|
||||||
) -> FilledSlotsContext:
|
) -> FilledSlotsContext:
|
||||||
if isinstance(fill_content, NodeList):
|
if DEFAULT_SLOT_KEY in fill_content:
|
||||||
default_fill_content = (fill_content, None)
|
named_fills_content = fill_content.copy()
|
||||||
named_fills_content = {}
|
default_fill_content = named_fills_content.pop(DEFAULT_SLOT_KEY)
|
||||||
else:
|
else:
|
||||||
|
named_fills_content = fill_content
|
||||||
default_fill_content = None
|
default_fill_content = None
|
||||||
named_fills_content = {name: (nodelist, alias) for name, nodelist, alias in list(fill_content)}
|
|
||||||
|
|
||||||
# If value is `None`, then slot is unfilled.
|
# If value is `None`, then slot is unfilled.
|
||||||
slot_name2fill_content: Dict[SlotName, Optional[FillContent]] = {}
|
slot_name2fill_content: Dict[SlotName, Optional[FillContent]] = {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue