Convert all 23 register_custom_layout calls to register_sx_layout across 6 services

Layout defcomps are now fully self-contained via IO-primitive auto-fetch
macros, eliminating Python layout functions that manually threaded context
values through SxExpr wrappers.

Services converted:
- Federation (1 layout): social
- Blog (7 layouts): blog, blog-settings, blog-cache, blog-snippets,
  blog-menu-items, blog-tag-groups, blog-tag-group-edit
- SX docs (2 layouts): sx, sx-section
- Cart (2 layouts): cart-page, cart-admin + orders/order-detail
- Events (9 layouts): calendar-admin, slots, slot, day-admin, entry,
  entry-admin, ticket-types, ticket-type, markets
- Market (2 layouts): market, market-admin

New IO primitives added to shared/sx/primitives_io.py:
- federation-actor-ctx, cart-page-ctx, request-view-args
- events-calendar-ctx, events-day-ctx, events-entry-ctx,
  events-slot-ctx, events-ticket-type-ctx
- market-header-ctx (pre-builds desktop/mobile nav as SxExpr)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 22:21:44 +00:00
parent ad75798ab7
commit 57a31a3b83
18 changed files with 1289 additions and 1215 deletions

View File

@@ -172,6 +172,45 @@ class CartPageService:
"summary": summary,
}
async def admin_data(self, session, **kw):
"""Populate post context for cart-admin layout headers."""
from quart import g
from shared.infrastructure.fragments import fetch_fragments
post = g.page_post
slug = post.slug if post else ""
post_id = post.id if post else None
# Fetch container_nav for post header
container_nav = ""
if post_id:
nav_params = {
"container_type": "page",
"container_id": str(post_id),
"post_slug": slug,
}
events_nav, market_nav = await fetch_fragments([
("events", "container-nav", nav_params),
("market", "container-nav", nav_params),
], required=False)
container_nav = events_nav + market_nav
return {
"post": {
"id": post_id,
"slug": slug,
"title": (post.title if post else "")[:160],
"feature_image": getattr(post, "feature_image", None),
},
"container_nav": container_nav,
}
async def payments_admin_data(self, session, **kw):
"""Admin data + payments data combined for cart-payments page."""
admin = await self.admin_data(session)
payments = await self.payments_data(session)
return {**admin, **payments}
async def payments_data(self, session, **kw):
from shared.sx.page import get_template_context

View File

@@ -1,25 +1,78 @@
;; Cart layout defcomps — fully self-contained via IO primitives.
;; Registered via register_sx_layout in __init__.py.
;; --- cart-page layout: root + cart row + page-cart row ---
;; ---------------------------------------------------------------------------
;; Auto-fetching cart page header macros
;; ---------------------------------------------------------------------------
(defcomp ~cart-page-layout-full (&key cart-row page-cart-row)
(defmacro ~cart-page-header-auto (oob)
"Cart page header: cart-row + page-cart-row using (cart-page-ctx)."
(quasiquote
(let ((__cpctx (cart-page-ctx)))
(<>
(~menu-row-sx :id "cart-row" :level 1 :colour "sky"
:link-href (get __cpctx "cart-url")
:link-label "cart" :icon "fa fa-shopping-cart"
:child-id "cart-header-child")
(~header-child-sx :id "cart-header-child"
:inner (~menu-row-sx :id "page-cart-row" :level 2 :colour "sky"
:link-href (get __cpctx "page-cart-url")
:link-label-content (~cart-page-label
:feature-image (get __cpctx "feature-image")
:title (get __cpctx "title"))
:nav (~cart-all-carts-link :href (get __cpctx "cart-url"))
:oob (unquote oob)))))))
(defmacro ~cart-page-header-oob ()
"Cart page OOB: individual oob rows."
(quasiquote
(let ((__cpctx (cart-page-ctx)))
(<>
(~menu-row-sx :id "page-cart-row" :level 2 :colour "sky"
:link-href (get __cpctx "page-cart-url")
:link-label-content (~cart-page-label
:feature-image (get __cpctx "feature-image")
:title (get __cpctx "title"))
:nav (~cart-all-carts-link :href (get __cpctx "cart-url"))
:oob true)
(~menu-row-sx :id "cart-row" :level 1 :colour "sky"
:link-href (get __cpctx "cart-url")
:link-label "cart" :icon "fa fa-shopping-cart"
:child-id "cart-header-child"
:oob true)))))
;; ---------------------------------------------------------------------------
;; cart-page layout: root + cart row + page-cart row
;; ---------------------------------------------------------------------------
(defcomp ~cart-page-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
:inner (<> cart-row
(~header-child-sx :id "cart-header-child" :inner page-cart-row)))))
:inner (~cart-page-header-auto))))
(defcomp ~cart-page-layout-oob (&key root-header-oob cart-row-oob page-cart-row)
(<> (~oob-header-sx :parent-id "cart-header-child" :row page-cart-row)
cart-row-oob
root-header-oob))
(defcomp ~cart-page-layout-oob ()
(<> (~cart-page-header-oob)
(~root-header-auto true)))
;; --- cart-admin layout: root + post header + admin header ---
;; ---------------------------------------------------------------------------
;; cart-admin layout: root + post header + admin header
;; Uses (post-header-ctx) — requires :data handler to populate g._defpage_ctx
;; ---------------------------------------------------------------------------
(defcomp ~cart-admin-layout-full (&key post-header admin-header)
(defcomp ~cart-admin-layout-full (&key selected)
(<> (~root-header-auto)
post-header admin-header))
(~header-child-sx
:inner (~post-header-auto nil))))
;; --- orders-within-cart: root + auth-simple + orders ---
(defcomp ~cart-admin-layout-oob (&key selected)
(<> (~post-header-auto true)
(~oob-header-sx :parent-id "post-header-child"
:row (~post-admin-header-auto nil selected))
(~root-header-auto true)))
;; ---------------------------------------------------------------------------
;; orders-within-cart: root + auth-simple + orders
;; ---------------------------------------------------------------------------
(defcomp ~cart-orders-layout-full (&key list-url)
(<> (~root-header-auto)
@@ -35,7 +88,9 @@
:row (~orders-header-row :list-url list-url))
(~root-header-auto true)))
;; --- order-detail-within-cart: root + auth-simple + orders + order ---
;; ---------------------------------------------------------------------------
;; order-detail-within-cart: root + auth-simple + orders + order
;; ---------------------------------------------------------------------------
(defcomp ~cart-order-detail-layout-full (&key list-url detail-url order-label)
(<> (~root-header-auto)

View File

@@ -32,12 +32,13 @@
:path "/<page_slug>/admin/"
:auth :admin
:layout :cart-admin
:data (service "cart-page" "admin-data")
:content (~cart-admin-content))
(defpage cart-payments
:path "/<page_slug>/admin/payments/"
:auth :admin
:layout (:cart-admin :selected "payments")
:data (service "cart-page" "payments-data")
:data (service "cart-page" "payments-admin-data")
:content (~cart-payments-content
:page-config page-config))

View File

@@ -1,135 +1,8 @@
"""Cart layout registration and header builders."""
"""Cart layout registration — all layouts delegate to .sx defcomps."""
from __future__ import annotations
from typing import Any
from shared.sx.parser import SxExpr
def _register_cart_layouts() -> None:
from shared.sx.layouts import register_custom_layout
register_custom_layout("cart-page", _cart_page_full, _cart_page_oob)
register_custom_layout("cart-admin", _cart_admin_full, _cart_admin_oob)
# ---------------------------------------------------------------------------
# Header helpers
# ---------------------------------------------------------------------------
def _ensure_post_ctx(ctx: dict, page_post: Any) -> dict:
"""Ensure ctx has a 'post' dict from page_post DTO."""
if ctx.get("post") or not page_post:
return ctx
return {**ctx, "post": {
"id": getattr(page_post, "id", None),
"slug": getattr(page_post, "slug", ""),
"title": getattr(page_post, "title", ""),
"feature_image": getattr(page_post, "feature_image", None),
}}
async def _ensure_container_nav(ctx: dict) -> dict:
"""Fetch container_nav if not already present."""
if ctx.get("container_nav"):
return ctx
post = ctx.get("post") or {}
post_id = post.get("id")
if not post_id:
return ctx
slug = post.get("slug", "")
from shared.infrastructure.fragments import fetch_fragments
nav_params = {
"container_type": "page",
"container_id": str(post_id),
"post_slug": slug,
}
events_nav, market_nav = await fetch_fragments([
("events", "container-nav", nav_params),
("market", "container-nav", nav_params),
], required=False)
return {**ctx, "container_nav": events_nav + market_nav}
async def _post_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
from shared.sx.helpers import post_header_sx as _shared_post_header_sx
ctx = _ensure_post_ctx(ctx, page_post)
ctx = await _ensure_container_nav(ctx)
return await _shared_post_header_sx(ctx, oob=oob)
def _cart_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import sx_call, call_url
return sx_call(
"menu-row-sx",
id="cart-row", level=1, colour="sky",
link_href=call_url(ctx, "cart_url", "/"),
link_label="cart", icon="fa fa-shopping-cart",
child_id="cart-header-child", oob=oob,
)
def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
from shared.sx.helpers import sx_call, call_url
slug = page_post.slug if page_post else ""
title = ((page_post.title if page_post else None) or "")[:160]
label_sx = sx_call("cart-page-label",
feature_image=page_post.feature_image if page_post else None,
title=title)
nav_sx = sx_call("cart-all-carts-link", href=call_url(ctx, "cart_url", "/"))
return sx_call(
"menu-row-sx",
id="page-cart-row", level=2, colour="sky",
link_href=call_url(ctx, "cart_url", f"/{slug}/"),
link_label_content=SxExpr(label_sx),
nav=SxExpr(nav_sx), oob=oob,
)
async def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
selected: str = "") -> str:
from shared.sx.helpers import post_admin_header_sx
slug = page_post.slug if page_post else ""
ctx = _ensure_post_ctx(ctx, page_post)
return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
# ---------------------------------------------------------------------------
# Layout functions
# ---------------------------------------------------------------------------
async def _cart_page_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
page_post = ctx.get("page_post")
env = {}
return await render_to_sx_with_env("cart-page-layout-full", env,
cart_row=SxExpr(_cart_header_sx(ctx)),
page_cart_row=SxExpr(_page_cart_header_sx(ctx, page_post)),
)
async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, root_header_sx
page_post = ctx.get("page_post")
env = {}
return await render_to_sx_with_env("cart-page-layout-oob", env,
root_header_oob=SxExpr(await root_header_sx(ctx, oob=True)),
cart_row_oob=SxExpr(_cart_header_sx(ctx, oob=True)),
page_cart_row=SxExpr(_page_cart_header_sx(ctx, page_post)),
)
async def _cart_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
page_post = ctx.get("page_post")
selected = kw.get("selected", "")
env = {}
return await render_to_sx_with_env("cart-admin-layout-full", env,
post_header=SxExpr(await _post_header_sx(ctx, page_post)),
admin_header=SxExpr(await _cart_page_admin_header_sx(ctx, page_post, selected=selected)),
)
async def _cart_admin_oob(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post")
selected = kw.get("selected", "")
return await _cart_page_admin_header_sx(ctx, page_post, oob=True, selected=selected)
from shared.sx.layouts import register_sx_layout
register_sx_layout("cart-page", "cart-page-layout-full", "cart-page-layout-oob")
register_sx_layout("cart-admin", "cart-admin-layout-full", "cart-admin-layout-oob")