feat(modals): layout context processor for HX-Modal header

Switches the Jinja base layout to _modal_partial.html (yield-only) when
the HX-Modal:1 request header is set, otherwise base.html. Foundation
for URL-addressable modals (spec 2026-05-17-url-addressable-modals).

Guards with has_request_context() so the processor is safe when
render_template_string is called from app_context() without a request
(e.g. test_timeago_filter_registered_on_app).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-17 11:27:25 +02:00
parent d05d00449f
commit 82c3f041ce
No known key found for this signature in database
3 changed files with 37 additions and 0 deletions

View file

@ -61,6 +61,13 @@ def create_app(test_config: dict[str, object] | None = None) -> Flask:
app.add_template_filter(format_time_html, "timeago")
@app.context_processor
def inject_base_layout() -> dict[str, str]:
from flask import has_request_context
is_modal = has_request_context() and request.headers.get("HX-Modal") == "1"
return {"base_layout": "_modal_partial.html" if is_modal else "base.html"}
@app.before_request
def csrf_protect() -> Response | None:
if "csrf_token" not in session:

View file

@ -0,0 +1 @@
{% block content %}{% endblock %}

View file

@ -0,0 +1,29 @@
from flask import render_template_string
from l4d2web.app import create_app
def _make_app(tmp_path, monkeypatch, db_name: str):
db_url = f"sqlite:///{tmp_path/db_name}"
monkeypatch.setenv("DATABASE_URL", db_url)
return create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"})
def test_base_layout_is_modal_partial_when_hx_modal_header_set(tmp_path, monkeypatch):
app = _make_app(tmp_path, monkeypatch, "layout-modal.db")
with app.test_request_context("/", headers={"HX-Modal": "1"}):
assert render_template_string("{{ base_layout }}") == "_modal_partial.html"
def test_base_layout_is_base_html_for_normal_request(tmp_path, monkeypatch):
app = _make_app(tmp_path, monkeypatch, "layout-default.db")
with app.test_request_context("/"):
assert render_template_string("{{ base_layout }}") == "base.html"
def test_base_layout_does_not_react_to_plain_hx_request_header(tmp_path, monkeypatch):
# HTMX sets HX-Request on every request including the build-status poll;
# only HX-Modal should switch the layout.
app = _make_app(tmp_path, monkeypatch, "layout-hxreq.db")
with app.test_request_context("/", headers={"HX-Request": "true"}):
assert render_template_string("{{ base_layout }}") == "base.html"