Phase 7: Replace render_template() with s-expression rendering in all POST/PUT/DELETE routes

Eliminates all render_template() calls from POST/PUT/DELETE handlers across
all 7 services. Moves sexp_components.py into sexp/ packages per service.

- Blog: like toggle, snippets, cache clear, features/sumup/entry panels,
  create/delete market, WYSIWYG editor panel (render_editor_panel)
- Federation: like/unlike/boost/unboost, follow/unfollow, actor card,
  interaction buttons
- Events: ticket widget, checkin, confirm/decline/provisional, tickets
  config, posts CRUD, description edit/save, calendar/slot/ticket_type
  CRUD, payments, buy tickets, day main panel, entry page
- Market: like toggle, cart add response
- Account: newsletter toggle
- Cart: checkout error pages (3 handlers)
- Orders: checkout error page (1 handler)

Remaining render_template() calls are exclusively in GET handlers and
internal services (email templates, fragment endpoints).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 01:15:29 +00:00
parent e65232761b
commit 838ec982eb
64 changed files with 2920 additions and 545 deletions

View File

@@ -52,7 +52,7 @@ def register():
}
from shared.sexp.page import get_template_context
from sexp_components import render_post_admin_page, render_post_admin_oob
from sexp.sexp_components import render_post_admin_page, render_post_admin_oob
tctx = await get_template_context()
tctx.update(ctx)
@@ -98,10 +98,9 @@ def register():
features = result.get("features", {})
html = await render_template(
"_types/post/admin/_features_panel.html",
features=features,
post=post,
from sexp.sexp_components import render_features_panel
html = render_features_panel(
features, post,
sumup_configured=result.get("sumup_configured", False),
sumup_merchant_code=result.get("sumup_merchant_code") or "",
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
@@ -138,10 +137,9 @@ def register():
result = await call_action("blog", "update-page-config", payload=payload)
features = result.get("features", {})
html = await render_template(
"_types/post/admin/_features_panel.html",
features=features,
post=post,
from sexp.sexp_components import render_features_panel
html = render_features_panel(
features, post,
sumup_configured=result.get("sumup_configured", False),
sumup_merchant_code=result.get("sumup_merchant_code") or "",
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
@@ -152,7 +150,7 @@ def register():
@require_admin
async def data(slug: str):
from shared.sexp.page import get_template_context
from sexp_components import render_post_data_page, render_post_data_oob
from sexp.sexp_components import render_post_data_page, render_post_data_oob
data_html = await render_template("_types/post_data/_main_panel.html")
tctx = await get_template_context()
@@ -271,7 +269,7 @@ def register():
for calendar in all_calendars:
await g.s.refresh(calendar, ["entries", "post"])
from shared.sexp.page import get_template_context
from sexp_components import render_post_entries_page, render_post_entries_oob
from sexp.sexp_components import render_post_entries_page, render_post_entries_oob
entries_html = await render_template(
"_types/post_entries/_main_panel.html",
@@ -331,20 +329,13 @@ def register():
).scalars().all()
# Return the associated entries admin list + OOB update for nav entries
admin_list = await render_template(
"_types/post/admin/_associated_entries.html",
all_calendars=all_calendars,
associated_entry_ids=associated_entry_ids,
)
from sexp.sexp_components import render_associated_entries, render_nav_entries_oob
nav_entries_oob = await render_template(
"_types/post/admin/_nav_entries_oob.html",
associated_entries=associated_entries,
calendars=calendars,
post=g.post_data["post"],
)
post = g.post_data["post"]
admin_list = render_associated_entries(all_calendars, associated_entry_ids, post["slug"])
nav_entries_html = render_nav_entries_oob(associated_entries, calendars, post)
return await make_response(admin_list + nav_entries_oob)
return await make_response(admin_list + nav_entries_html)
@bp.get("/settings/")
@require_post_author
@@ -357,7 +348,7 @@ def register():
save_success = request.args.get("saved") == "1"
from shared.sexp.page import get_template_context
from sexp_components import render_post_settings_page, render_post_settings_oob
from sexp.sexp_components import render_post_settings_page, render_post_settings_oob
settings_html = await render_template(
"_types/post_settings/_main_panel.html",
@@ -452,6 +443,7 @@ def register():
is_page = bool(g.post_data["post"].get("is_page"))
ghost_post = await get_post_for_edit(ghost_id, is_page=is_page)
save_success = request.args.get("saved") == "1"
save_error = request.args.get("error", "")
# Newsletters live in db_account — fetch via HTTP
raw_newsletters = await fetch_data("account", "newsletters", required=False) or []
@@ -460,12 +452,13 @@ def register():
newsletters = [SimpleNamespace(**nl) for nl in raw_newsletters]
from shared.sexp.page import get_template_context
from sexp_components import render_post_edit_page, render_post_edit_oob
from sexp.sexp_components import render_post_edit_page, render_post_edit_oob
edit_html = await render_template(
"_types/post_edit/_main_panel.html",
ghost_post=ghost_post,
save_success=save_success,
save_error=save_error,
newsletters=newsletters,
)
tctx = await get_template_context()
@@ -500,28 +493,15 @@ def register():
feature_image_caption = form.get("feature_image_caption", "").strip()
# Validate the lexical JSON
from urllib.parse import quote
try:
lexical_doc = json.loads(lexical_raw)
except (json.JSONDecodeError, TypeError):
from ...blog.ghost.ghost_posts import get_post_for_edit
ghost_post = await get_post_for_edit(ghost_id, is_page=is_page)
html = await render_template(
"_types/post_edit/index.html",
ghost_post=ghost_post,
save_error="Invalid JSON in editor content.",
)
return await make_response(html, 400)
return redirect(host_url(url_for("blog.post.admin.edit", slug=slug)) + "?error=" + quote("Invalid JSON in editor content."))
ok, reason = validate_lexical(lexical_doc)
if not ok:
from ...blog.ghost.ghost_posts import get_post_for_edit
ghost_post = await get_post_for_edit(ghost_id, is_page=is_page)
html = await render_template(
"_types/post_edit/index.html",
ghost_post=ghost_post,
save_error=reason,
)
return await make_response(html, 400)
return redirect(host_url(url_for("blog.post.admin.edit", slug=slug)) + "?error=" + quote(reason))
# Update in Ghost (content save — no status change yet)
ghost_post = await update_post(
@@ -617,11 +597,8 @@ def register():
page_markets = await _fetch_page_markets(post_id)
html = await render_template(
"_types/post/admin/_markets_panel.html",
markets=page_markets,
post=post,
)
from sexp.sexp_components import render_markets_panel
html = render_markets_panel(page_markets, post)
return await make_response(html)
@bp.post("/markets/new/")
@@ -647,11 +624,8 @@ def register():
# Return updated markets list
page_markets = await _fetch_page_markets(post_id)
html = await render_template(
"_types/post/admin/_markets_panel.html",
markets=page_markets,
post=post,
)
from sexp.sexp_components import render_markets_panel
html = render_markets_panel(page_markets, post)
return await make_response(html)
@bp.delete("/markets/<market_slug>/")
@@ -671,11 +645,8 @@ def register():
# Return updated markets list
page_markets = await _fetch_page_markets(post_id)
html = await render_template(
"_types/post/admin/_markets_panel.html",
markets=page_markets,
post=post,
)
from sexp.sexp_components import render_markets_panel
html = render_markets_panel(page_markets, post)
return await make_response(html)
return bp