Source code for htmx_nav.shortcuts

"""
Rendering shortcuts.

`render_with_swaps` is the htmx-aware counterpart to
django.shortcuts.render, for any HTMX view that wants to piggyback
out-of-band swaps — no navigation concept required. `render_nav` adds
PartialSpec-driven partial/block resolution on top, for views that
participate in this package's tab/nav-state model.
"""

from collections.abc import Mapping
from typing import Any

from django.http import HttpRequest
from django.template.response import TemplateResponse
from django.utils.cache import patch_vary_headers
from django.utils.html import escape

from .partials import PartialSpec, _resolve_partial_name, _resolve_template_name
from .settings import _title_context_key
from .swaps import Swaps, _normalize_swaps
from .targeting import _is_htmx_request


[docs] def render_with_swaps( request: HttpRequest, template_name: str, context: Mapping[str, Any] | None = None, content_type: str | None = None, status: int | None = None, using: str | None = None, *, swaps: Swaps = None, title: str | None = None, ) -> TemplateResponse: """Renders a template and appends swap fragments on HTMX requests. This is the foundational HTMX-aware renderer. Unlike `render_nav`, it does no partial resolution or navigation state management. Use it for HTMX responses that need out-of-band swaps without navigation involvement. Args: request: The HTTP request object. template_name: Path to the main template. context: Optional template context. content_type: Optional response content type. status: Optional HTTP status code. using: Optional template engine. swaps: A single Swap, list, or None. Additional HTML fragments to append as out-of-band swaps. title: Optional page title. Overrides title context variable and injects a `<title>` element for HTMX requests. Returns: A TemplateResponse with swaps appended as post-render callbacks if the request is HTMX. Notes: - Adds "HX-Request" to Vary headers for proper caching. - Context from swaps is merged with main context (swap context wins). - Title injection is HTML-escaped. Example: .. code-block:: python def form_submit(request): form = MyForm(request.POST) if form.is_valid(): obj = form.save() return render_with_swaps( request, "form/success.html", {"form": form}, swaps=[ Swap("components/badge.html", context={"count": get_count()}), Swap("components/notification.html", target_id="flash-messages"), ], title="Success!", ) """ is_htmx = _is_htmx_request(request) swap_list = _normalize_swaps(swaps) base_context: dict[str, Any] = {} for swap in swap_list: if swap.context: base_context.update(swap.context) page_context = {**base_context, **(context or {})} title_key = _title_context_key() page_context.setdefault(title_key, None) if title is not None: page_context[title_key] = title effective_title = page_context.get(title_key) response = TemplateResponse( request, template_name, page_context, content_type=content_type, status=status, using=using, ) patch_vary_headers(response, ("HX-Request",)) if is_htmx and (swap_list or effective_title): def append_swaps(response: TemplateResponse) -> TemplateResponse: for swap in swap_list: if not swap.applies_to(request): continue html = swap.render(request, parent_context=page_context, using=using) response.content += html.encode(response.charset) if effective_title: response.content += f"<title>{escape(effective_title)}</title>".encode( response.charset ) return response response.add_post_render_callback(append_swaps) return response
[docs] def render_nav( request: HttpRequest, template_name: str, context: Mapping[str, Any] | None = None, content_type: str | None = None, status: int | None = None, using: str | None = None, *, partial: PartialSpec = "#content", swaps: Swaps = None, title: str | None = None, ) -> TemplateResponse: """Renders a Django template with HTMX partial resolution and OOB swaps. Args: request: The HTTP request object. template_name: Path to the full template containing partial blocks. context: Optional template context. content_type: Optional response content type. status: Optional HTTP status code. using: Optional template engine. partial: Specifies which partial to render for HTMX requests. Can be a block name (`"#content"`), standalone path, callable, or dict mapping names to targets. Defaults to "#content". swaps: Additional out-of-band swaps to include. title: Optional page title. Overrides title context variable. Returns: A TemplateResponse with partial resolution and OOB swaps. Example: .. code-block:: python # Basic partial selection return render_nav( request, "project/detail.html", {"project": project}, partial="#tab_content", ) # Multiple partial targets with swaps return render_nav( request, "project/detail.html", {"project": project}, partial={ "#tab_content": targeting("tab-content"), "#main_content": targeting("main-content"), "#content": True, # fallback }, swaps=[ Swap("partials/sidebar.html", target_id="sidebar"), Swap("partials/notification.html", target_id="flash"), ], title=project.name, ) """ is_htmx = _is_htmx_request(request) active_partial = _resolve_partial_name(partial, request) resolved_template = _resolve_template_name(template_name, active_partial, is_htmx) context = dict(context or {}) context.setdefault("active_partial", active_partial) return render_with_swaps( request, resolved_template, context, content_type=content_type, status=status, using=using, swaps=swaps, title=title, )