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:
@@ -1,6 +1,6 @@
|
||||
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
|
||||
import sexp.sexp_components as sexp_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
||||
from pathlib import Path
|
||||
|
||||
from quart import g, request
|
||||
@@ -96,7 +96,7 @@ def create_app() -> "Quart":
|
||||
async def home():
|
||||
from quart import make_response
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_federation_home
|
||||
from sexp.sexp_components import render_federation_home
|
||||
|
||||
ctx = await get_template_context()
|
||||
html = await render_federation_home(ctx)
|
||||
|
||||
@@ -11,7 +11,6 @@ from datetime import datetime, timezone, timedelta
|
||||
from quart import (
|
||||
Blueprint,
|
||||
request,
|
||||
render_template,
|
||||
redirect,
|
||||
url_for,
|
||||
session as qsession,
|
||||
@@ -101,7 +100,7 @@ def register(url_prefix="/auth"):
|
||||
redirect_url = pop_login_redirect_target()
|
||||
return redirect(redirect_url)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_login_page
|
||||
from sexp.sexp_components import render_login_page
|
||||
ctx = await get_template_context()
|
||||
return await render_login_page(ctx)
|
||||
|
||||
@@ -112,14 +111,10 @@ def register(url_prefix="/auth"):
|
||||
|
||||
is_valid, email = validate_email(email_input)
|
||||
if not is_valid:
|
||||
return (
|
||||
await render_template(
|
||||
"auth/login.html",
|
||||
error="Please enter a valid email address.",
|
||||
email=email_input,
|
||||
),
|
||||
400,
|
||||
)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp.sexp_components import render_login_page
|
||||
ctx = await get_template_context(error="Please enter a valid email address.", email=email_input)
|
||||
return await render_login_page(ctx), 400
|
||||
|
||||
user = await find_or_create_user(g.s, email)
|
||||
token, expires = await create_magic_link(g.s, user.id)
|
||||
@@ -137,11 +132,10 @@ def register(url_prefix="/auth"):
|
||||
"Please try again in a moment."
|
||||
)
|
||||
|
||||
return await render_template(
|
||||
"auth/check_email.html",
|
||||
email=email,
|
||||
email_error=email_error,
|
||||
)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp.sexp_components import render_check_email_page
|
||||
ctx = await get_template_context(email=email, email_error=email_error)
|
||||
return await render_check_email_page(ctx)
|
||||
|
||||
@auth_bp.get("/magic/<token>/")
|
||||
async def magic(token: str):
|
||||
@@ -154,20 +148,17 @@ def register(url_prefix="/auth"):
|
||||
user, error = await validate_magic_link(s, token)
|
||||
|
||||
if error:
|
||||
return (
|
||||
await render_template("auth/login.html", error=error),
|
||||
400,
|
||||
)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp.sexp_components import render_login_page
|
||||
ctx = await get_template_context(error=error)
|
||||
return await render_login_page(ctx), 400
|
||||
user_id = user.id
|
||||
|
||||
except Exception:
|
||||
return (
|
||||
await render_template(
|
||||
"auth/login.html",
|
||||
error="Could not sign you in right now. Please try again.",
|
||||
),
|
||||
502,
|
||||
)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp.sexp_components import render_login_page
|
||||
ctx = await get_template_context(error="Could not sign you in right now. Please try again.")
|
||||
return await render_login_page(ctx), 502
|
||||
|
||||
assert user_id is not None
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from __future__ import annotations
|
||||
import re
|
||||
|
||||
from quart import (
|
||||
Blueprint, request, render_template, redirect, url_for, g, abort,
|
||||
Blueprint, request, redirect, url_for, g, abort,
|
||||
)
|
||||
|
||||
from shared.services.registry import services
|
||||
@@ -40,7 +40,7 @@ def register(url_prefix="/identity"):
|
||||
return redirect(url_for("activitypub.actor_profile", username=actor.preferred_username))
|
||||
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_choose_username_page
|
||||
from sexp.sexp_components import render_choose_username_page
|
||||
ctx = await get_template_context()
|
||||
ctx["actor"] = actor
|
||||
return await render_choose_username_page(ctx)
|
||||
@@ -71,11 +71,11 @@ def register(url_prefix="/identity"):
|
||||
error = "This username is already taken."
|
||||
|
||||
if error:
|
||||
return await render_template(
|
||||
"federation/choose_username.html",
|
||||
error=error,
|
||||
username=username,
|
||||
), 400
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp.sexp_components import render_choose_username_page
|
||||
ctx = await get_template_context(error=error, username=username)
|
||||
ctx["actor"] = None
|
||||
return await render_choose_username_page(ctx), 400
|
||||
|
||||
# Create ActorProfile with RSA keys
|
||||
display_name = g.user.name or username
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from quart import Blueprint, request, g, redirect, url_for, abort, render_template, Response
|
||||
from quart import Blueprint, request, g, redirect, url_for, abort, Response
|
||||
|
||||
from shared.services.registry import services
|
||||
|
||||
@@ -40,7 +40,7 @@ def register(url_prefix="/social"):
|
||||
actor = _require_actor()
|
||||
items = await services.federation.get_home_timeline(g.s, actor.id)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_timeline_page
|
||||
from sexp.sexp_components import render_timeline_page
|
||||
ctx = await get_template_context()
|
||||
return await render_timeline_page(ctx, items, "home", actor)
|
||||
|
||||
@@ -57,7 +57,7 @@ def register(url_prefix="/social"):
|
||||
items = await services.federation.get_home_timeline(
|
||||
g.s, actor.id, before=before,
|
||||
)
|
||||
from sexp_components import render_timeline_items
|
||||
from sexp.sexp_components import render_timeline_items
|
||||
return await render_timeline_items(items, "home", actor)
|
||||
|
||||
@bp.get("/public")
|
||||
@@ -65,7 +65,7 @@ def register(url_prefix="/social"):
|
||||
items = await services.federation.get_public_timeline(g.s)
|
||||
actor = getattr(g, "_social_actor", None)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_timeline_page
|
||||
from sexp.sexp_components import render_timeline_page
|
||||
ctx = await get_template_context()
|
||||
return await render_timeline_page(ctx, items, "public", actor)
|
||||
|
||||
@@ -80,7 +80,7 @@ def register(url_prefix="/social"):
|
||||
pass
|
||||
items = await services.federation.get_public_timeline(g.s, before=before)
|
||||
actor = getattr(g, "_social_actor", None)
|
||||
from sexp_components import render_timeline_items
|
||||
from sexp.sexp_components import render_timeline_items
|
||||
return await render_timeline_items(items, "public", actor)
|
||||
|
||||
# -- Compose --------------------------------------------------------------
|
||||
@@ -90,7 +90,7 @@ def register(url_prefix="/social"):
|
||||
actor = _require_actor()
|
||||
reply_to = request.args.get("reply_to")
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_compose_page
|
||||
from sexp.sexp_components import render_compose_page
|
||||
ctx = await get_template_context()
|
||||
return await render_compose_page(ctx, actor, reply_to)
|
||||
|
||||
@@ -136,7 +136,7 @@ def register(url_prefix="/social"):
|
||||
)
|
||||
followed_urls = {a.actor_url for a in following}
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_search_page
|
||||
from sexp.sexp_components import render_search_page
|
||||
ctx = await get_template_context()
|
||||
return await render_search_page(ctx, query, actors, total, 1, followed_urls, actor)
|
||||
|
||||
@@ -157,7 +157,7 @@ def register(url_prefix="/social"):
|
||||
g.s, actor.preferred_username, page=1, per_page=1000,
|
||||
)
|
||||
followed_urls = {a.actor_url for a in following}
|
||||
from sexp_components import render_search_results
|
||||
from sexp.sexp_components import render_search_results
|
||||
return await render_search_results(actors, query, page, followed_urls, actor)
|
||||
|
||||
@bp.post("/follow")
|
||||
@@ -200,15 +200,8 @@ def register(url_prefix="/social"):
|
||||
list_type = "followers"
|
||||
else:
|
||||
list_type = "following"
|
||||
return await render_template(
|
||||
"federation/_actor_list_items.html",
|
||||
actors=[remote_dto],
|
||||
total=0,
|
||||
page=1,
|
||||
list_type=list_type,
|
||||
followed_urls=followed_urls,
|
||||
actor=actor,
|
||||
)
|
||||
from sexp.sexp_components import render_actor_card
|
||||
return render_actor_card(remote_dto, actor, followed_urls, list_type=list_type)
|
||||
|
||||
# -- Interactions ---------------------------------------------------------
|
||||
|
||||
@@ -296,10 +289,10 @@ def register(url_prefix="/social"):
|
||||
).limit(1)
|
||||
)).scalar())
|
||||
|
||||
return await render_template(
|
||||
"federation/_interaction_buttons.html",
|
||||
item_object_id=object_id,
|
||||
item_author_inbox=author_inbox,
|
||||
from sexp.sexp_components import render_interaction_buttons
|
||||
return render_interaction_buttons(
|
||||
object_id=object_id,
|
||||
author_inbox=author_inbox,
|
||||
like_count=like_count,
|
||||
boost_count=boost_count,
|
||||
liked_by_me=liked_by_me,
|
||||
@@ -316,7 +309,7 @@ def register(url_prefix="/social"):
|
||||
g.s, actor.preferred_username,
|
||||
)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_following_page
|
||||
from sexp.sexp_components import render_following_page
|
||||
ctx = await get_template_context()
|
||||
return await render_following_page(ctx, actors, total, actor)
|
||||
|
||||
@@ -327,7 +320,7 @@ def register(url_prefix="/social"):
|
||||
actors, total = await services.federation.get_following(
|
||||
g.s, actor.preferred_username, page=page,
|
||||
)
|
||||
from sexp_components import render_following_items
|
||||
from sexp.sexp_components import render_following_items
|
||||
return await render_following_items(actors, page, actor)
|
||||
|
||||
@bp.get("/followers")
|
||||
@@ -342,7 +335,7 @@ def register(url_prefix="/social"):
|
||||
)
|
||||
followed_urls = {a.actor_url for a in following}
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_followers_page
|
||||
from sexp.sexp_components import render_followers_page
|
||||
ctx = await get_template_context()
|
||||
return await render_followers_page(ctx, actors, total, followed_urls, actor)
|
||||
|
||||
@@ -357,7 +350,7 @@ def register(url_prefix="/social"):
|
||||
g.s, actor.preferred_username, page=1, per_page=1000,
|
||||
)
|
||||
followed_urls = {a.actor_url for a in following}
|
||||
from sexp_components import render_followers_items
|
||||
from sexp.sexp_components import render_followers_items
|
||||
return await render_followers_items(actors, page, followed_urls, actor)
|
||||
|
||||
@bp.get("/actor/<int:id>")
|
||||
@@ -390,7 +383,7 @@ def register(url_prefix="/social"):
|
||||
).scalar_one_or_none()
|
||||
is_following = existing is not None
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_actor_timeline_page
|
||||
from sexp.sexp_components import render_actor_timeline_page
|
||||
ctx = await get_template_context()
|
||||
return await render_actor_timeline_page(ctx, remote_dto, items, is_following, actor)
|
||||
|
||||
@@ -407,7 +400,7 @@ def register(url_prefix="/social"):
|
||||
items = await services.federation.get_actor_timeline(
|
||||
g.s, id, before=before,
|
||||
)
|
||||
from sexp_components import render_actor_timeline_items
|
||||
from sexp.sexp_components import render_actor_timeline_items
|
||||
return await render_actor_timeline_items(items, id, actor)
|
||||
|
||||
# -- Notifications --------------------------------------------------------
|
||||
@@ -418,7 +411,7 @@ def register(url_prefix="/social"):
|
||||
items = await services.federation.get_notifications(g.s, actor.id)
|
||||
await services.federation.mark_notifications_read(g.s, actor.id)
|
||||
from shared.sexp.page import get_template_context
|
||||
from sexp_components import render_notifications_page
|
||||
from sexp.sexp_components import render_notifications_page
|
||||
ctx = await get_template_context()
|
||||
return await render_notifications_page(ctx, items, actor)
|
||||
|
||||
|
||||
0
federation/sexp/__init__.py
Normal file
0
federation/sexp/__init__.py
Normal file
@@ -398,6 +398,30 @@ async def render_login_page(ctx: dict) -> str:
|
||||
meta_html="<title>Login \u2014 Rose Ash</title>")
|
||||
|
||||
|
||||
async def render_check_email_page(ctx: dict) -> str:
|
||||
"""Full page: check email after magic link sent."""
|
||||
email = ctx.get("email", "")
|
||||
email_error = ctx.get("email_error")
|
||||
|
||||
error_html = ""
|
||||
if email_error:
|
||||
error_html = (
|
||||
f'<div class="bg-yellow-50 border border-yellow-200 text-yellow-700 p-3 rounded mt-4">'
|
||||
f'{escape(email_error)}</div>'
|
||||
)
|
||||
content = (
|
||||
'<div class="py-8 max-w-md mx-auto text-center">'
|
||||
'<h1 class="text-2xl font-bold mb-4">Check your email</h1>'
|
||||
f'<p class="text-stone-600 mb-2">We sent a sign-in link to <strong>{escape(email)}</strong>.</p>'
|
||||
'<p class="text-stone-500 text-sm">Click the link in the email to sign in. The link expires in 15 minutes.</p>'
|
||||
f'{error_html}</div>'
|
||||
)
|
||||
|
||||
hdr = root_header_html(ctx)
|
||||
return full_page(ctx, header_rows_html=hdr, content_html=content,
|
||||
meta_html='<title>Check your email \u2014 Rose Ash</title>')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API: Timeline
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -708,3 +732,30 @@ async def render_profile_page(ctx: dict, actor: Any, activities: list,
|
||||
|
||||
return _social_page(ctx, actor, content_html=content,
|
||||
title=f"@{actor.preferred_username} \u2014 Rose Ash")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API: POST handler fragment renderers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def render_interaction_buttons(object_id: str, author_inbox: str,
|
||||
like_count: int, boost_count: int,
|
||||
liked_by_me: bool, boosted_by_me: bool,
|
||||
actor: Any) -> str:
|
||||
"""Render interaction buttons fragment for HTMX POST response."""
|
||||
from types import SimpleNamespace
|
||||
item = SimpleNamespace(
|
||||
object_id=object_id,
|
||||
author_inbox=author_inbox,
|
||||
like_count=like_count,
|
||||
boost_count=boost_count,
|
||||
liked_by_me=liked_by_me,
|
||||
boosted_by_me=boosted_by_me,
|
||||
)
|
||||
return _interaction_buttons_html(item, actor)
|
||||
|
||||
|
||||
def render_actor_card(actor_dto: Any, actor: Any, followed_urls: set,
|
||||
*, list_type: str = "following") -> str:
|
||||
"""Render a single actor card fragment for HTMX POST response."""
|
||||
return _actor_card_html(actor_dto, actor, followed_urls, list_type=list_type)
|
||||
Reference in New Issue
Block a user