Phase 6: Replace render_template() with s-expression rendering in all GET routes

Migrate ~52 GET route handlers across all 7 services from Jinja
render_template() to s-expression component rendering. Each service
gets a sexp_components.py with page/oob/cards render functions.

- Add per-service sexp_components.py (account, blog, cart, events,
  federation, market, orders) with full page, OOB, and pagination
  card rendering
- Add shared/sexp/helpers.py with call_url, root_header_html,
  full_page, oob_page utilities
- Update all GET routes to use get_template_context() + render fns
- Fix get_template_context() to inject Jinja globals (URL helpers)
- Add qs_filter to base_context for sexp filter URL building
- Mount sexp_components.py in docker-compose.dev.yml for all services
- Import sexp_components in app.py for Hypercorn --reload watching
- Fix route_prefix import (shared.utils not shared.infrastructure.urls)
- Fix federation choose-username missing actor in context
- Fix market page_markets missing post in context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 23:19:33 +00:00
parent 8013317b41
commit d53b9648a9
53 changed files with 8690 additions and 463 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import path_setup # noqa: F401 # adds shared/ to sys.path
import sexp_components # noqa: F401 # ensure Hypercorn --reload watches this file
from pathlib import Path
from quart import g, abort, request

View File

@@ -65,19 +65,14 @@ def register() -> Blueprint:
entries, has_more, pending_tickets, page_info = await _load_entries(page)
ctx = dict(
entries=entries,
has_more=has_more,
pending_tickets=pending_tickets,
page_info=page_info,
page=page,
view=view,
)
from shared.sexp.page import get_template_context
from sexp_components import render_all_events_page, render_all_events_oob
ctx = await get_template_context()
if is_htmx_request():
html = await render_template("_types/all_events/_main_panel.html", **ctx)
html = await render_all_events_oob(ctx, entries, has_more, pending_tickets, page_info, page, view)
else:
html = await render_template("_types/all_events/index.html", **ctx)
html = await render_all_events_page(ctx, entries, has_more, pending_tickets, page_info, page, view)
return await make_response(html, 200)
@@ -88,15 +83,8 @@ def register() -> Blueprint:
entries, has_more, pending_tickets, page_info = await _load_entries(page)
html = await render_template(
"_types/all_events/_cards.html",
entries=entries,
has_more=has_more,
pending_tickets=pending_tickets,
page_info=page_info,
page=page,
view=view,
)
from sexp_components import render_all_events_cards
html = await render_all_events_cards(entries, has_more, pending_tickets, page_info, page, view)
return await make_response(html, 200)
@bp.post("/all-tickets/adjust")

View File

@@ -19,13 +19,14 @@ def register():
async def admin(calendar_slug: str, **kwargs):
from shared.browser.app.utils.htmx import is_htmx_request
# Determine which template to use based on request type
from shared.sexp.page import get_template_context
from sexp_components import render_calendar_admin_page, render_calendar_admin_oob
tctx = await get_template_context()
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template("_types/calendar/admin/index.html")
html = await render_calendar_admin_page(tctx)
else:
# HTMX request: main panel + OOB elements
html = await render_template("_types/calendar/admin/_oob_elements.html")
html = await render_calendar_admin_oob(tctx)
return await make_response(html)

View File

@@ -142,47 +142,25 @@ def register():
user_entries = visible.user_entries
confirmed_entries = visible.confirmed_entries
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template(
"_types/calendar/index.html",
from shared.sexp.page import get_template_context
from sexp_components import render_calendar_page, render_calendar_oob
tctx = await get_template_context()
tctx.update(dict(
qsession=qsession,
year=year,
month=month,
month_name=month_name,
weekday_names=weekday_names,
weeks=weeks,
prev_month=prev_month,
prev_month_year=prev_month_year,
next_month=next_month,
next_month_year=next_month_year,
prev_year=prev_year,
next_year=next_year,
user_entries=user_entries,
confirmed_entries=confirmed_entries,
month_entries=month_entries,
)
year=year, month=month, month_name=month_name,
weekday_names=weekday_names, weeks=weeks,
prev_month=prev_month, prev_month_year=prev_month_year,
next_month=next_month, next_month_year=next_month_year,
prev_year=prev_year, next_year=next_year,
user_entries=user_entries, confirmed_entries=confirmed_entries,
month_entries=month_entries,
))
if not is_htmx_request():
html = await render_calendar_page(tctx)
else:
html = await render_template(
"_types/calendar/_oob_elements.html",
qsession=qsession,
year=year,
month=month,
month_name=month_name,
weekday_names=weekday_names,
weeks=weeks,
prev_month=prev_month,
prev_month_year=prev_month_year,
next_month=next_month,
next_month_year=next_month_year,
prev_year=prev_year,
next_year=next_year,
user_entries=user_entries,
confirmed_entries=confirmed_entries,
month_entries=month_entries,
)
html = await render_calendar_oob(tctx)
return await make_response(html)

View File

@@ -35,14 +35,14 @@ def register():
@bp.get("/")
@cache_page(tag="calendars")
async def home(**kwargs):
from shared.sexp.page import get_template_context
from sexp_components import render_calendars_page, render_calendars_oob
ctx = await get_template_context()
if not is_htmx_request():
html = await render_template(
"_types/calendars/index.html",
)
html = await render_calendars_page(ctx)
else:
html = await render_template(
"_types/calendars/_oob_elements.html",
)
html = await render_calendars_oob(ctx)
return await make_response(html)

View File

@@ -17,12 +17,14 @@ def register():
async def admin(year: int, month: int, day: int, **kwargs):
from shared.browser.app.utils.htmx import is_htmx_request
# Determine which template to use based on request type
from shared.sexp.page import get_template_context
from sexp_components import render_day_admin_page, render_day_admin_oob
tctx = await get_template_context()
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template("_types/day/admin/index.html")
html = await render_day_admin_page(tctx)
else:
html = await render_template("_types/day/admin/_oob_elements.html")
html = await render_day_admin_oob(tctx)
return await make_response(html)
return bp

View File

@@ -120,16 +120,14 @@ def register():
- all confirmed + provisional + ordered entries for that day (all users)
- pending only for current user/session
"""
from shared.sexp.page import get_template_context
from sexp_components import render_day_page, render_day_oob
tctx = await get_template_context()
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template(
"_types/day/index.html",
)
html = await render_day_page(tctx)
else:
html = await render_template(
"_types/day/_oob_elements.html",
)
html = await render_day_oob(tctx)
return await make_response(html)
@bp.get("/w/<widget_domain>/")

View File

@@ -23,10 +23,14 @@ def register():
@bp.get("/")
async def home(**kwargs):
from shared.sexp.page import get_template_context
from sexp_components import render_markets_page, render_markets_oob
ctx = await get_template_context()
if not is_htmx_request():
html = await render_template("_types/markets/index.html")
html = await render_markets_page(ctx)
else:
html = await render_template("_types/markets/_oob_elements.html")
html = await render_markets_oob(ctx)
return await make_response(html)
@bp.post("/new/")

View File

@@ -45,19 +45,14 @@ def register() -> Blueprint:
entries, has_more, pending_tickets = await _load_entries(post["id"], page)
ctx = dict(
entries=entries,
has_more=has_more,
pending_tickets=pending_tickets,
page_info={},
page=page,
view=view,
)
from shared.sexp.page import get_template_context
from sexp_components import render_page_summary_page, render_page_summary_oob
ctx = await get_template_context()
if is_htmx_request():
html = await render_template("_types/page_summary/_main_panel.html", **ctx)
html = await render_page_summary_oob(ctx, entries, has_more, pending_tickets, {}, page, view)
else:
html = await render_template("_types/page_summary/index.html", **ctx)
html = await render_page_summary_page(ctx, entries, has_more, pending_tickets, {}, page, view)
return await make_response(html, 200)
@@ -69,15 +64,8 @@ def register() -> Blueprint:
entries, has_more, pending_tickets = await _load_entries(post["id"], page)
html = await render_template(
"_types/page_summary/_cards.html",
entries=entries,
has_more=has_more,
pending_tickets=pending_tickets,
page_info={},
page=page,
view=view,
)
from sexp_components import render_page_summary_cards
html = await render_page_summary_cards(entries, has_more, pending_tickets, {}, page, view, post)
return await make_response(html, 200)
@bp.post("/tickets/adjust")

View File

@@ -42,11 +42,17 @@ def register():
@bp.get("/")
@require_admin
async def home(**kwargs):
ctx = await _load_payment_ctx()
pay_ctx = await _load_payment_ctx()
from shared.sexp.page import get_template_context
from sexp_components import render_payments_page, render_payments_oob
ctx = await get_template_context()
ctx.update(pay_ctx)
if not is_htmx_request():
html = await render_template("_types/payments/index.html", **ctx)
html = await render_payments_page(ctx)
else:
html = await render_template("_types/payments/_oob_elements.html", **ctx)
html = await render_payments_oob(ctx)
return await make_response(html)
@bp.put("/")

View File

@@ -70,18 +70,14 @@ def register() -> Blueprint:
"reserved": reserved or 0,
}
from shared.sexp.page import get_template_context
from sexp_components import render_ticket_admin_page, render_ticket_admin_oob
ctx = await get_template_context()
if not is_htmx_request():
html = await render_template(
"_types/ticket_admin/index.html",
tickets=tickets,
stats=stats,
)
html = await render_ticket_admin_page(ctx, tickets, stats)
else:
html = await render_template(
"_types/ticket_admin/_main_panel.html",
tickets=tickets,
stats=stats,
)
html = await render_ticket_admin_oob(ctx, tickets, stats)
return await make_response(html, 200)

View File

@@ -50,16 +50,14 @@ def register() -> Blueprint:
session_id=ident["session_id"],
)
from shared.sexp.page import get_template_context
from sexp_components import render_tickets_page, render_tickets_oob
ctx = await get_template_context()
if not is_htmx_request():
html = await render_template(
"_types/tickets/index.html",
tickets=tickets,
)
html = await render_tickets_page(ctx, tickets)
else:
html = await render_template(
"_types/tickets/_main_panel.html",
tickets=tickets,
)
html = await render_tickets_oob(ctx, tickets)
return await make_response(html, 200)
@@ -83,16 +81,14 @@ def register() -> Blueprint:
else:
return await make_response("Ticket not found", 404)
from shared.sexp.page import get_template_context
from sexp_components import render_ticket_detail_page, render_ticket_detail_oob
ctx = await get_template_context()
if not is_htmx_request():
html = await render_template(
"_types/tickets/detail.html",
ticket=ticket,
)
html = await render_ticket_detail_page(ctx, ticket)
else:
html = await render_template(
"_types/tickets/_detail_panel.html",
ticket=ticket,
)
html = await render_ticket_detail_oob(ctx, ticket)
return await make_response(html, 200)

1872
events/sexp_components.py Normal file

File diff suppressed because it is too large Load Diff