feat: add self context var and make is_filled into attribute (#632)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Juro Oravec 2024-09-04 21:41:20 +02:00 committed by GitHub
parent 2d0f270df4
commit e712800f5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 265 additions and 31 deletions

View file

@ -65,7 +65,7 @@ from django_components.slots import (
)
from django_components.utils import gen_id, validate_typed_dict, validate_typed_tuple
# TODO_DEPRECATE_V1 - REMOVE IN V1, users should use top-level import instead
# TODO_REMOVE_IN_V1 - Users should use top-level import instead
# isort: off
from django_components.component_registry import AlreadyRegistered as AlreadyRegistered # NOQA
from django_components.component_registry import ComponentRegistry as ComponentRegistry # NOQA
@ -94,6 +94,12 @@ class RenderInput(Generic[ArgsType, KwargsType, SlotsType]):
escape_slots_content: bool
@dataclass()
class RenderStackItem(Generic[ArgsType, KwargsType, SlotsType]):
input: RenderInput[ArgsType, KwargsType, SlotsType]
is_filled: Optional[Dict[str, bool]]
class ViewFn(Protocol):
def __call__(self, request: HttpRequest, *args: Any, **kwargs: Any) -> Any: ... # noqa: E704
@ -219,7 +225,7 @@ class Component(Generic[ArgsType, KwargsType, DataType, SlotsType], metaclass=Co
self.fill_content = fill_content or {}
self.component_id = component_id or gen_id()
self.registry = registry or registry_
self._render_stack: Deque[RenderInput[ArgsType, KwargsType, SlotsType]] = deque()
self._render_stack: Deque[RenderStackItem[ArgsType, KwargsType, SlotsType]] = deque()
# None == uninitialized, False == No types, Tuple == types
self._types: Optional[Union[Tuple[Any, Any, Any, Any], Literal[False]]] = None
@ -241,7 +247,29 @@ class Component(Generic[ArgsType, KwargsType, DataType, SlotsType], metaclass=Co
# NOTE: Input is managed as a stack, so if `render` is called within another `render`,
# the propertes below will return only the inner-most state.
return self._render_stack[-1]
return self._render_stack[-1].input
@property
def is_filled(self) -> Dict[str, bool]:
"""
Dictionary describing which slots have or have not been filled.
This attribute is available for use only within the template as `{{ component_vars.is_filled.slot_name }}`,
and within `on_render_before` and `on_render_after` hooks.
"""
if not len(self._render_stack):
raise RuntimeError(
f"{self.name}: Tried to access Component's `is_filled` attribute "
"while outside of rendering execution"
)
ctx = self._render_stack[-1]
if ctx.is_filled is None:
raise RuntimeError(
f"{self.name}: Tried to access Component's `is_filled` attribute " "before slots were resolved"
)
return ctx.is_filled
def get_context_data(self, *args: Any, **kwargs: Any) -> DataType:
return cast(DataType, {})
@ -508,13 +536,16 @@ class Component(Generic[ArgsType, KwargsType, DataType, SlotsType], metaclass=Co
# to access the provided context, slots, etc. Also required so users can
# call `self.inject()` from within `get_context_data()`.
self._render_stack.append(
RenderInput(
context=context,
slots=slots,
args=args,
kwargs=kwargs,
escape_slots_content=escape_slots_content,
)
RenderStackItem(
input=RenderInput(
context=context,
slots=slots,
args=args,
kwargs=kwargs,
escape_slots_content=escape_slots_content,
),
is_filled=None,
),
)
self._validate_inputs()
@ -557,6 +588,7 @@ class Component(Generic[ArgsType, KwargsType, DataType, SlotsType], metaclass=Co
# to see if given slot was filled, e.g.:
# `{% if variable > 8 and component_vars.is_filled.header %}`
slot_bools = {slot_fill.escaped_name: slot_fill.is_filled for slot_fill in resolved_fills.values()}
self._render_stack[-1].is_filled = slot_bools
with context.update(
{