Source code for next.forms.uid

"""Stable UIDs and related helpers for `@action` endpoints."""

from __future__ import annotations

import hashlib
from pathlib import Path
from typing import TYPE_CHECKING

from django.conf import settings
from django.http import HttpResponseRedirect


if TYPE_CHECKING:
    from django.http import HttpRequest


URL_NAME_FORM_ACTION = "form_action"
FORM_ACTION_REVERSE_NAME = "next:form_action"


def _make_uid(action_name: str) -> str:
    """Return a stable short id derived from the action name."""
    raw = f"next.form.action:{action_name}".encode()
    return hashlib.sha256(raw).hexdigest()[:16]


[docs] def validated_next_form_page_path(request: HttpRequest) -> Path | None: # noqa: PLR0911 """Return a trusted `page.py` path from POST `_next_form_page`, or `None`. Accepts both real `page.py` files and virtual ones — directories whose only source is a sibling `template.djx` (the file router already emits routes for those; see `FilesystemTreeDispatcher._visit`). The downstream renderer (`_load_python_module_memo`, `_load_static_body`) tolerates a missing module and falls back to the template, so virtual pages survive the re-render path on form-validation failures. """ if not hasattr(request, "POST"): return None raw = request.POST.get("_next_form_page") if not raw or not isinstance(raw, str): return None raw_stripped = raw.strip() if not raw_stripped: return None try: p = Path(raw_stripped).resolve() except OSError: return None if p.name != "page.py": return None if not p.is_file() and not (p.parent / "template.djx").is_file(): return None base = getattr(settings, "BASE_DIR", None) if base is None: return None try: p.relative_to(Path(base).resolve()) except ValueError: return None return p
def _validated_origin_path(raw: object) -> str | None: """Return `raw` as a same-site path or `None`.""" if not isinstance(raw, str): return None raw = raw.strip() if not raw.startswith("/") or raw.startswith("//"): return None return raw
[docs] def redirect_to_origin( request: HttpRequest, fallback: str = "/", ) -> HttpResponseRedirect: """Redirect back to the page that rendered the form.""" origin: str | None = None if hasattr(request, "POST"): origin = _validated_origin_path(request.POST.get("_next_form_origin")) return HttpResponseRedirect(origin or fallback)
__all__ = [ "FORM_ACTION_REVERSE_NAME", "URL_NAME_FORM_ACTION", "_make_uid", "redirect_to_origin", "validated_next_form_page_path", ]