Source code for htmx_nav.partials

"""
PartialSpec: what template or block to render for a given HTMX request.
"""

from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import Protocol, TypeAlias, runtime_checkable

from django.http import HttpRequest

from .targeting import Target, _eval_target


[docs] @runtime_checkable class PartialResolver(Protocol): """Derives a partial block or template path from the request and base template."""
[docs] def resolve(self, request: HttpRequest, template_name: str) -> str | None: """Resolve a partial block or template path given the request and base template. Args: request: The incoming HTTP request. template_name: The base template path specified by the view. Returns: A block name (e.g. ``"#content"``), a template path (e.g. ``"partials/_board.html"``), or ``None`` to render the full template. """ ...
[docs] @dataclass(frozen=True) class PathReplace: """Derive a standalone partial file path by swapping a path segment. Legacy/compatibility support for codebases that keep every partial in its own file under a directory convention, e.g. ``"pages/"`` -> ``"partials/_"``. Args: old: The path segment to match (e.g. ``"pages/"``). new: The replacement segment (e.g. ``"partials/_"``). """ old: str new: str
[docs] def resolve(self, request: HttpRequest, template_name: str) -> str | None: """Derive a partial template path by replacing ``old`` with ``new``. If ``template_name`` does not contain ``old``, returns ``template_name`` unmodified so non-matching templates fall back gracefully. """ if template_name and self.old in template_name: return template_name.replace(self.old, self.new, 1) return template_name
PartialSpec: TypeAlias = ( str | Callable[[HttpRequest], str | None] | Mapping[str | PartialResolver, Target] | PartialResolver | None ) """Specifies what template or partial block to render for an HTMX request. Values resolve to: - Block name (``"#name"``): appended to the base template, giving ``template.html#name`` (Django 6 ``{% partialdef %}``). - Standalone path (``"path/to/template.html"``): rendered in place of the base template. - Callable ``(request) -> str | None``: returns a block name, path, or ``None`` per request. - Mapping: keys are block names, paths, or resolvers; values are ``Target`` conditions. First match wins, so end with ``True`` for a fallback. - ``PartialResolver``: derives a block name or path from the request and base template name. Advanced extension point; ``PathReplace`` is the built-in. - ``None``: forces a full-page render. Examples: .. code-block:: python "#content" lambda request: "#tab_content" if htmx_target_is(request, "tabs") else "#content" { "#tab_content": targeting("tab-content"), "partials/_board.html": targeting("board"), "#content": True, } # Legacy: "pages/board.html" -> "partials/_board.html" PathReplace("pages/", "partials/_") """ def _resolve_partial_name( partial: PartialSpec, request: HttpRequest, template_name: str = "" ) -> str | None: """Resolve the active partial or template name for a request.""" if partial is None: return None if isinstance(partial, str): return partial if isinstance(partial, PartialResolver): return partial.resolve(request, template_name) if isinstance(partial, Mapping): for key, target in partial.items(): if _eval_target(target, request): if isinstance(key, PartialResolver): return key.resolve(request, template_name) return key return None if callable(partial): return partial(request) raise TypeError(f"Invalid PartialSpec value: {partial!r}") def _resolve_template_name( template_name: str, partial_name: str | None, is_htmx: bool, ) -> str: """Resolve the final template path or block string to render.""" if not is_htmx or not partial_name: return template_name if partial_name.startswith("#"): return f"{template_name}{partial_name}" return partial_name