Migrate all apps to defpage declarative page routes
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m41s

Replace Python GET page handlers with declarative defpage definitions in .sx
files across all 8 apps (sx docs, orders, account, market, cart, federation,
events, blog). Each app now has sxc/pages/ with setup functions, layout
registrations, page helpers, and .sx defpage declarations.

Core infrastructure: add g I/O primitive, PageDef support for auth/layout/
data/content/filter/aside/menu slots, post_author auth level, and custom
layout registration. Remove ~1400 lines of render_*_page/render_*_oob
boilerplate. Update all endpoint references in routes, sx_components, and
templates to defpage_* naming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 14:52:34 +00:00
parent 5b4cacaf19
commit c243d17eeb
108 changed files with 3598 additions and 2851 deletions

View File

@@ -26,6 +26,10 @@ from shared.sx.helpers import (
search_mobile_sx,
search_desktop_sx,
full_page_sx,
mobile_menu_sx,
mobile_root_nav_sx,
post_mobile_nav_sx,
post_admin_mobile_nav_sx,
)
# Load blog service .sx component definitions + handler definitions
@@ -76,6 +80,15 @@ def _post_admin_header_sx(ctx: dict, *, oob: bool = False, selected: str = "") -
return post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
def _post_admin_mobile_menu(ctx: dict, selected: str = "") -> str:
"""Full mobile menu for any post admin page (admin + post + root)."""
slug = (ctx.get("post") or {}).get("slug", "")
return mobile_menu_sx(
post_admin_mobile_nav_sx(ctx, slug, selected),
post_mobile_nav_sx(ctx),
mobile_root_nav_sx(ctx),
)
# ---------------------------------------------------------------------------
# Settings header (root-header-child -> root-settings-header-child)
@@ -85,7 +98,7 @@ def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Settings header row with admin icon and nav links (sx)."""
from quart import url_for as qurl
settings_href = qurl("settings.home")
settings_href = qurl("settings.defpage_settings_home")
label_sx = sx_call("blog-admin-label")
nav_sx = _settings_nav_sx(ctx)
@@ -107,10 +120,10 @@ def _settings_nav_sx(ctx: dict) -> str:
parts = []
for endpoint, icon, label in [
("menu_items.list_menu_items", "bars", "Menu Items"),
("snippets.list_snippets", "puzzle-piece", "Snippets"),
("blog.tag_groups_admin.index", "tags", "Tag Groups"),
("settings.cache", "refresh", "Cache"),
("menu_items.defpage_menu_items_page", "bars", "Menu Items"),
("snippets.defpage_snippets_page", "puzzle-piece", "Snippets"),
("blog.tag_groups_admin.defpage_tag_groups_page", "tags", "Tag Groups"),
("settings.defpage_cache_page", "refresh", "Cache"),
]:
href = qurl(endpoint)
parts.append(sx_call("nav-link",
@@ -679,7 +692,7 @@ def _post_main_panel_sx(ctx: dict) -> str:
if post.get("status") == "draft":
edit_sx = ""
if is_admin or (user and post.get("user_id") == getattr(user, "id", None)):
edit_href = qurl("blog.post.admin.edit", slug=slug)
edit_href = qurl("blog.post.admin.defpage_post_edit", slug=slug)
edit_sx = sx_call("blog-detail-edit-link",
href=edit_href, hx_select=hx_select,
)
@@ -951,7 +964,7 @@ def _tag_groups_main_panel_sx(ctx: dict) -> str:
g_colour = getattr(group, "colour", None) if hasattr(group, "colour") else group.get("colour")
g_sort = getattr(group, "sort_order", 0) if hasattr(group, "sort_order") else group.get("sort_order", 0)
edit_href = qurl("blog.tag_groups_admin.edit", id=g_id)
edit_href = qurl("blog.tag_groups_admin.defpage_tag_group_edit", id=g_id)
if g_fi:
icon = sx_call("blog-tag-group-icon-image", src=g_fi, name=g_name)
@@ -1053,7 +1066,7 @@ async def render_home_page(ctx: dict) -> str:
header_rows = "(<> " + root_hdr + " " + post_hdr + ")"
content = _home_main_panel_sx(ctx)
meta = _post_meta_sx(ctx)
menu = ctx.get("nav_sx", "") or ""
menu = mobile_menu_sx(post_mobile_nav_sx(ctx), mobile_root_nav_sx(ctx))
return full_page_sx(ctx, header_rows=header_rows, content=content,
meta=meta, menu=menu)
@@ -1088,9 +1101,8 @@ async def render_blog_oob(ctx: dict) -> str:
content = _blog_main_panel_sx(ctx)
aside = _blog_aside_sx(ctx)
filter_sx = _blog_filter_sx(ctx)
nav = ctx.get("nav_sx", "") or ""
return oob_page_sx(oobs=header_oob, content=content, aside=aside,
filter=filter_sx, menu=nav)
filter=filter_sx)
async def render_blog_cards(ctx: dict) -> str:
@@ -1304,15 +1316,6 @@ async def render_new_post_page(ctx: dict) -> str:
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_new_post_oob(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
blog_hdr = _blog_header_sx(ctx)
rows = "(<> " + root_hdr + " " + blog_hdr + ")"
header_oob = _oob_header_sx("root-header-child", "blog-header-child", rows)
content = ctx.get("editor_html", "")
return oob_page_sx(oobs=header_oob, content=content)
# ---- Post detail ----
async def render_post_page(ctx: dict) -> str:
@@ -1321,7 +1324,7 @@ async def render_post_page(ctx: dict) -> str:
header_rows = "(<> " + root_hdr + " " + post_hdr + ")"
content = _post_main_panel_sx(ctx)
meta = _post_meta_sx(ctx)
menu = ctx.get("nav_sx", "") or ""
menu = mobile_menu_sx(post_mobile_nav_sx(ctx), mobile_root_nav_sx(ctx))
return full_page_sx(ctx, header_rows=header_rows, content=content,
meta=meta, menu=menu)
@@ -1332,35 +1335,14 @@ async def render_post_oob(ctx: dict) -> str:
rows = "(<> " + root_hdr + " " + post_hdr + ")"
post_oob = _oob_header_sx("root-header-child", "post-header-child", rows)
content = _post_main_panel_sx(ctx)
menu = ctx.get("nav_sx", "") or ""
menu = mobile_menu_sx(post_mobile_nav_sx(ctx), mobile_root_nav_sx(ctx))
oobs = post_oob
return oob_page_sx(oobs=oobs, content=content, menu=menu)
# ---- Post admin ----
async def render_post_admin_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = _post_admin_header_sx(ctx)
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
content = _post_admin_main_panel_sx(ctx)
menu = ctx.get("nav_sx", "") or ""
return full_page_sx(ctx, header_rows=header_rows, content=content,
menu=menu)
async def render_post_admin_oob(ctx: dict) -> str:
post_hdr_oob = _post_header_sx(ctx, oob=True)
admin_oob = _oob_header_sx("post-header-child", "post-admin-header-child",
_post_admin_header_sx(ctx))
content = _post_admin_main_panel_sx(ctx)
menu = ctx.get("nav_sx", "") or ""
oobs = "(<> " + post_hdr_oob + " " + admin_oob + ")"
return oob_page_sx(oobs=oobs, content=content, menu=menu)
# ---- Post data ----
# ===========================================================================
def _post_data_content_sx(ctx: dict) -> str:
"""Build post data inspector panel natively (replaces _types/post_data/_main_panel.html)."""
@@ -1478,22 +1460,7 @@ def _post_data_content_sx(ctx: dict) -> str:
return _raw_html_sx(html)
async def render_post_data_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = _post_admin_header_sx(ctx, selected="data")
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
content = _post_data_content_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_post_data_oob(ctx: dict) -> str:
admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="data")
content = _post_data_content_sx(ctx)
return oob_page_sx(oobs=admin_hdr_oob, content=content)
# ---- Post preview ----
# ===========================================================================
def _preview_main_panel_sx(ctx: dict) -> str:
"""Build the preview panel with 4 expandable sections."""
@@ -1540,22 +1507,7 @@ def _preview_main_panel_sx(ctx: dict) -> str:
return sx_call("blog-preview-panel", sections=SxExpr(f"(<> {inner})"))
async def render_post_preview_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = _post_admin_header_sx(ctx, selected="preview")
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
content = _preview_main_panel_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_post_preview_oob(ctx: dict) -> str:
admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="preview")
content = _preview_main_panel_sx(ctx)
return oob_page_sx(oobs=admin_hdr_oob, content=content)
# ---- Post entries ----
# ===========================================================================
def _post_entries_content_sx(ctx: dict) -> str:
"""Build post entries panel natively (replaces _types/post_entries/_main_panel.html)."""
@@ -1613,21 +1565,6 @@ def _post_entries_content_sx(ctx: dict) -> str:
)
async def render_post_entries_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = _post_admin_header_sx(ctx, selected="entries")
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
content = _post_entries_content_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_post_entries_oob(ctx: dict) -> str:
admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="entries")
content = _post_entries_content_sx(ctx)
return oob_page_sx(oobs=admin_hdr_oob, content=content)
# ---- Calendar view (for entries browser) ----
def render_calendar_view(
@@ -2045,22 +1982,7 @@ def _post_edit_content_sx(ctx: dict) -> str:
return _raw_html_sx("".join(parts))
async def render_post_edit_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = _post_admin_header_sx(ctx, selected="edit")
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
content = _post_edit_content_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_post_edit_oob(ctx: dict) -> str:
admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="edit")
content = _post_edit_content_sx(ctx)
return oob_page_sx(oobs=admin_hdr_oob, content=content)
# ---- Post settings ----
# ===========================================================================
def _post_settings_content_sx(ctx: dict) -> str:
"""Build settings form natively (replaces _types/post_settings/_main_panel.html)."""
@@ -2195,189 +2117,17 @@ def _post_settings_content_sx(ctx: dict) -> str:
return _raw_html_sx(html)
async def render_post_settings_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = _post_admin_header_sx(ctx, selected="settings")
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
content = _post_settings_content_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
# ===========================================================================
# ===========================================================================
async def render_post_settings_oob(ctx: dict) -> str:
admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="settings")
content = _post_settings_content_sx(ctx)
return oob_page_sx(oobs=admin_hdr_oob, content=content)
# ===========================================================================
# ===========================================================================
# ---- Settings home ----
async def render_settings_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
settings_hdr = _settings_header_sx(ctx)
header_rows = "(<> " + root_hdr + " " + settings_hdr + ")"
content = _settings_main_panel_sx(ctx)
menu = _settings_nav_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content,
menu=menu)
async def render_settings_oob(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
settings_hdr = _settings_header_sx(ctx)
rows = "(<> " + root_hdr + " " + settings_hdr + ")"
header_oob = _oob_header_sx("root-header-child", "root-settings-header-child", rows)
content = _settings_main_panel_sx(ctx)
menu = _settings_nav_sx(ctx)
return oob_page_sx(oobs=header_oob, content=content, menu=menu)
# ---- Cache ----
async def render_cache_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
settings_hdr = _settings_header_sx(ctx)
from quart import url_for as qurl
cache_hdr = _sub_settings_header_sx(
"cache-row", "cache-header-child",
qurl("settings.cache"), "refresh", "Cache", ctx,
)
header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + cache_hdr + ")"
content = _cache_main_panel_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_cache_oob(ctx: dict) -> str:
settings_hdr_oob = _settings_header_sx(ctx, oob=True)
from quart import url_for as qurl
cache_hdr = _sub_settings_header_sx(
"cache-row", "cache-header-child",
qurl("settings.cache"), "refresh", "Cache", ctx,
)
cache_oob = _oob_header_sx("root-settings-header-child", "cache-header-child",
cache_hdr)
content = _cache_main_panel_sx(ctx)
oobs = "(<> " + settings_hdr_oob + " " + cache_oob + ")"
return oob_page_sx(oobs=oobs, content=content)
# ---- Snippets ----
async def render_snippets_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
settings_hdr = _settings_header_sx(ctx)
from quart import url_for as qurl
snippets_hdr = _sub_settings_header_sx(
"snippets-row", "snippets-header-child",
qurl("snippets.list_snippets"), "puzzle-piece", "Snippets", ctx,
)
header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + snippets_hdr + ")"
content = _snippets_main_panel_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_snippets_oob(ctx: dict) -> str:
settings_hdr_oob = _settings_header_sx(ctx, oob=True)
from quart import url_for as qurl
snippets_hdr = _sub_settings_header_sx(
"snippets-row", "snippets-header-child",
qurl("snippets.list_snippets"), "puzzle-piece", "Snippets", ctx,
)
snippets_oob = _oob_header_sx("root-settings-header-child", "snippets-header-child",
snippets_hdr)
content = _snippets_main_panel_sx(ctx)
oobs = "(<> " + settings_hdr_oob + " " + snippets_oob + ")"
return oob_page_sx(oobs=oobs, content=content)
# ---- Menu items ----
async def render_menu_items_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
settings_hdr = _settings_header_sx(ctx)
from quart import url_for as qurl
mi_hdr = _sub_settings_header_sx(
"menu_items-row", "menu_items-header-child",
qurl("menu_items.list_menu_items"), "bars", "Menu Items", ctx,
)
header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + mi_hdr + ")"
content = _menu_items_main_panel_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_menu_items_oob(ctx: dict) -> str:
settings_hdr_oob = _settings_header_sx(ctx, oob=True)
from quart import url_for as qurl
mi_hdr = _sub_settings_header_sx(
"menu_items-row", "menu_items-header-child",
qurl("menu_items.list_menu_items"), "bars", "Menu Items", ctx,
)
mi_oob = _oob_header_sx("root-settings-header-child", "menu_items-header-child",
mi_hdr)
content = _menu_items_main_panel_sx(ctx)
oobs = "(<> " + settings_hdr_oob + " " + mi_oob + ")"
return oob_page_sx(oobs=oobs, content=content)
# ---- Tag groups ----
async def render_tag_groups_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
settings_hdr = _settings_header_sx(ctx)
from quart import url_for as qurl
tg_hdr = _sub_settings_header_sx(
"tag-groups-row", "tag-groups-header-child",
qurl("blog.tag_groups_admin.index"), "tags", "Tag Groups", ctx,
)
header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + tg_hdr + ")"
content = _tag_groups_main_panel_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_tag_groups_oob(ctx: dict) -> str:
settings_hdr_oob = _settings_header_sx(ctx, oob=True)
from quart import url_for as qurl
tg_hdr = _sub_settings_header_sx(
"tag-groups-row", "tag-groups-header-child",
qurl("blog.tag_groups_admin.index"), "tags", "Tag Groups", ctx,
)
tg_oob = _oob_header_sx("root-settings-header-child", "tag-groups-header-child",
tg_hdr)
content = _tag_groups_main_panel_sx(ctx)
oobs = "(<> " + settings_hdr_oob + " " + tg_oob + ")"
return oob_page_sx(oobs=oobs, content=content)
# ---- Tag group edit ----
async def render_tag_group_edit_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
settings_hdr = _settings_header_sx(ctx)
from quart import url_for as qurl
g_id = (ctx.get("group") or {}).get("id") or getattr(ctx.get("group"), "id", None)
tg_hdr = _sub_settings_header_sx(
"tag-groups-row", "tag-groups-header-child",
qurl("blog.tag_groups_admin.edit", id=g_id), "tags", "Tag Groups", ctx,
)
header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + tg_hdr + ")"
content = _tag_groups_edit_main_panel_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_tag_group_edit_oob(ctx: dict) -> str:
settings_hdr_oob = _settings_header_sx(ctx, oob=True)
from quart import url_for as qurl
g_id = (ctx.get("group") or {}).get("id") or getattr(ctx.get("group"), "id", None)
tg_hdr = _sub_settings_header_sx(
"tag-groups-row", "tag-groups-header-child",
qurl("blog.tag_groups_admin.edit", id=g_id), "tags", "Tag Groups", ctx,
)
tg_oob = _oob_header_sx("root-settings-header-child", "tag-groups-header-child",
tg_hdr)
content = _tag_groups_edit_main_panel_sx(ctx)
oobs = "(<> " + settings_hdr_oob + " " + tg_oob + ")"
return oob_page_sx(oobs=oobs, content=content)
# ===========================================================================
# ===========================================================================
# ===========================================================================
# PUBLIC API — HTMX fragment renderers for POST/PUT/DELETE handlers