Migrate all apps to defpage declarative page routes
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:
@@ -70,16 +70,22 @@ def create_app() -> "Quart":
|
||||
app.jinja_loader,
|
||||
])
|
||||
|
||||
# Load orders-specific s-expression components
|
||||
from sx.sx_components import load_orders_components
|
||||
load_orders_components()
|
||||
# Load orders-specific s-expression components (loaded at import time)
|
||||
import sx.sx_components # noqa: F811
|
||||
|
||||
# Setup defpage routes
|
||||
from sxc.pages import setup_orders_pages
|
||||
setup_orders_pages()
|
||||
|
||||
app.register_blueprint(register_fragments())
|
||||
app.register_blueprint(register_actions())
|
||||
app.register_blueprint(register_data())
|
||||
|
||||
# Orders list at /
|
||||
app.register_blueprint(register_orders(url_prefix="/"))
|
||||
# Orders list at / (defpage routes mounted below)
|
||||
bp = register_orders(url_prefix="/")
|
||||
from shared.sx.pages import mount_pages
|
||||
mount_pages(bp, "orders")
|
||||
app.register_blueprint(bp)
|
||||
|
||||
# Checkout webhook + return
|
||||
app.register_blueprint(register_checkout())
|
||||
|
||||
@@ -2,16 +2,13 @@ from __future__ import annotations
|
||||
|
||||
from quart import Blueprint, g, redirect, url_for, make_response
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from shared.models.order import Order
|
||||
from shared.browser.app.payments.sumup import create_checkout as sumup_create_checkout
|
||||
from shared.config import config
|
||||
|
||||
from shared.infrastructure.cart_identity import current_cart_identity
|
||||
from shared.sx.page import get_template_context
|
||||
from services.check_sumup_status import check_sumup_status
|
||||
from shared.browser.app.utils.htmx import is_htmx_request
|
||||
|
||||
from .filters.qs import makeqs_factory, decode
|
||||
|
||||
@@ -33,34 +30,6 @@ def register() -> Blueprint:
|
||||
def route():
|
||||
g.makeqs_factory = makeqs_factory
|
||||
|
||||
@bp.get("/")
|
||||
async def order_detail(order_id: int):
|
||||
"""Show a single order + items."""
|
||||
owner = _owner_filter()
|
||||
if owner is None:
|
||||
return await make_response("Order not found", 404)
|
||||
result = await g.s.execute(
|
||||
select(Order)
|
||||
.options(selectinload(Order.items))
|
||||
.where(Order.id == order_id, owner)
|
||||
)
|
||||
order = result.scalar_one_or_none()
|
||||
if not order:
|
||||
return await make_response("Order not found", 404)
|
||||
|
||||
from sx.sx_components import render_order_page, render_order_oob
|
||||
|
||||
ctx = await get_template_context()
|
||||
calendar_entries = ctx.get("calendar_entries")
|
||||
|
||||
if not is_htmx_request():
|
||||
html = await render_order_page(ctx, order, calendar_entries, url_for)
|
||||
return await make_response(html)
|
||||
else:
|
||||
from shared.sx.helpers import sx_response
|
||||
sx_src = await render_order_oob(ctx, order, calendar_entries, url_for)
|
||||
return sx_response(sx_src)
|
||||
|
||||
@bp.get("/pay/")
|
||||
async def order_pay(order_id: int):
|
||||
"""Re-open the SumUp payment page for this order."""
|
||||
@@ -73,7 +42,7 @@ def register() -> Blueprint:
|
||||
return await make_response("Order not found", 404)
|
||||
|
||||
if order.status == "paid":
|
||||
return redirect(url_for("orders.order.order_detail", order_id=order.id))
|
||||
return redirect(url_for("orders.defpage_order_detail", order_id=order.id))
|
||||
|
||||
if order.sumup_hosted_url:
|
||||
return redirect(order.sumup_hosted_url)
|
||||
@@ -120,13 +89,13 @@ def register() -> Blueprint:
|
||||
return await make_response("Order not found", 404)
|
||||
|
||||
if not order.sumup_checkout_id:
|
||||
return redirect(url_for("orders.order.order_detail", order_id=order.id))
|
||||
return redirect(url_for("orders.defpage_order_detail", order_id=order.id))
|
||||
|
||||
try:
|
||||
await check_sumup_status(g.s, order)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return redirect(url_for("orders.order.order_detail", order_id=order.id))
|
||||
return redirect(url_for("orders.defpage_order_detail", order_id=order.id))
|
||||
|
||||
return bp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from quart import Blueprint, g, render_template, redirect, url_for, make_response
|
||||
from quart import Blueprint, g, redirect, url_for, make_response, request
|
||||
from sqlalchemy import select, func, or_, cast, String, exists
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
@@ -20,18 +20,6 @@ def register(url_prefix: str) -> Blueprint:
|
||||
|
||||
ORDERS_PER_PAGE = 10
|
||||
|
||||
oob = {
|
||||
"extends": "_types/root/_index.html",
|
||||
"child_id": "auth-header-child",
|
||||
"header": "_types/auth/header/_header.html",
|
||||
"nav": "_types/auth/_nav.html",
|
||||
"main": "_types/auth/_main_panel.html",
|
||||
}
|
||||
|
||||
@bp.context_processor
|
||||
def inject_oob():
|
||||
return {"oob": oob}
|
||||
|
||||
@bp.before_request
|
||||
def route():
|
||||
g.makeqs_factory = makeqs_factory
|
||||
@@ -43,8 +31,115 @@ def register(url_prefix: str) -> Blueprint:
|
||||
if not ident["user_id"] and not ident["session_id"]:
|
||||
return redirect(url_for("auth.login_form"))
|
||||
|
||||
@bp.get("/")
|
||||
async def list_orders():
|
||||
@bp.before_request
|
||||
async def _prepare_page_data():
|
||||
"""Load data for defpage routes into g.*."""
|
||||
if request.method != "GET":
|
||||
return
|
||||
|
||||
endpoint = request.endpoint or ""
|
||||
|
||||
# Orders list page
|
||||
if endpoint.endswith("defpage_orders_list"):
|
||||
ident = current_cart_identity()
|
||||
if ident["user_id"]:
|
||||
owner_clause = Order.user_id == ident["user_id"]
|
||||
elif ident["session_id"]:
|
||||
owner_clause = Order.session_id == ident["session_id"]
|
||||
else:
|
||||
return
|
||||
|
||||
q = decode()
|
||||
page, search = q.page, q.search
|
||||
if page < 1:
|
||||
page = 1
|
||||
|
||||
where_clause = _search_clause(search) if search else None
|
||||
|
||||
count_stmt = select(func.count()).select_from(Order).where(owner_clause)
|
||||
if where_clause is not None:
|
||||
count_stmt = count_stmt.where(where_clause)
|
||||
|
||||
total_count_result = await g.s.execute(count_stmt)
|
||||
total_count = total_count_result.scalar_one() or 0
|
||||
total_pages = max(1, (total_count + ORDERS_PER_PAGE - 1) // ORDERS_PER_PAGE)
|
||||
|
||||
if page > total_pages:
|
||||
page = total_pages
|
||||
|
||||
offset = (page - 1) * ORDERS_PER_PAGE
|
||||
stmt = (
|
||||
select(Order)
|
||||
.where(owner_clause)
|
||||
.order_by(Order.created_at.desc())
|
||||
.offset(offset)
|
||||
.limit(ORDERS_PER_PAGE)
|
||||
)
|
||||
if where_clause is not None:
|
||||
stmt = stmt.where(where_clause)
|
||||
|
||||
result = await g.s.execute(stmt)
|
||||
orders = result.scalars().all()
|
||||
|
||||
from shared.utils import route_prefix
|
||||
pfx = route_prefix()
|
||||
qs_fn = makeqs_factory()
|
||||
|
||||
g.orders_page_data = {
|
||||
"orders": orders,
|
||||
"page": page,
|
||||
"total_pages": total_pages,
|
||||
"search": search,
|
||||
"search_count": total_count,
|
||||
"url_for_fn": url_for,
|
||||
"qs_fn": qs_fn,
|
||||
"list_url": pfx + url_for("orders.defpage_orders_list"),
|
||||
}
|
||||
|
||||
# Order detail page
|
||||
elif endpoint.endswith("defpage_order_detail"):
|
||||
order_id = request.view_args.get("order_id")
|
||||
if order_id is None:
|
||||
return
|
||||
|
||||
ident = current_cart_identity()
|
||||
if ident["user_id"]:
|
||||
owner = Order.user_id == ident["user_id"]
|
||||
elif ident["session_id"]:
|
||||
owner = Order.session_id == ident["session_id"]
|
||||
else:
|
||||
from quart import abort
|
||||
abort(404)
|
||||
return
|
||||
|
||||
result = await g.s.execute(
|
||||
select(Order)
|
||||
.options(selectinload(Order.items))
|
||||
.where(Order.id == order_id, owner)
|
||||
)
|
||||
order = result.scalar_one_or_none()
|
||||
if not order:
|
||||
from quart import abort
|
||||
abort(404)
|
||||
return
|
||||
|
||||
from shared.utils import route_prefix
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
pfx = route_prefix()
|
||||
|
||||
g.order_detail_data = {
|
||||
"order": order,
|
||||
"calendar_entries": None,
|
||||
"detail_url": pfx + url_for("orders.defpage_order_detail", order_id=order.id),
|
||||
"list_url": pfx + url_for("orders.defpage_orders_list"),
|
||||
"recheck_url": pfx + url_for("orders.order.order_recheck", order_id=order.id),
|
||||
"pay_url": pfx + url_for("orders.order.order_pay", order_id=order.id),
|
||||
"csrf_token": generate_csrf_token(),
|
||||
}
|
||||
|
||||
@bp.get("/rows")
|
||||
async def orders_rows():
|
||||
"""Pagination endpoint — returns order rows for page > 1."""
|
||||
ident = current_cart_identity()
|
||||
if ident["user_id"]:
|
||||
owner_clause = Order.user_id == ident["user_id"]
|
||||
@@ -58,38 +153,7 @@ def register(url_prefix: str) -> Blueprint:
|
||||
if page < 1:
|
||||
page = 1
|
||||
|
||||
where_clause = None
|
||||
if search:
|
||||
term = f"%{search.strip()}%"
|
||||
conditions = [
|
||||
Order.status.ilike(term),
|
||||
Order.currency.ilike(term),
|
||||
Order.sumup_checkout_id.ilike(term),
|
||||
Order.sumup_status.ilike(term),
|
||||
Order.description.ilike(term),
|
||||
]
|
||||
conditions.append(
|
||||
exists(
|
||||
select(1)
|
||||
.select_from(OrderItem)
|
||||
.where(
|
||||
OrderItem.order_id == Order.id,
|
||||
or_(
|
||||
OrderItem.product_title.ilike(term),
|
||||
OrderItem.product_slug.ilike(term),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
try:
|
||||
search_id = int(search)
|
||||
except (TypeError, ValueError):
|
||||
search_id = None
|
||||
if search_id is not None:
|
||||
conditions.append(Order.id == search_id)
|
||||
else:
|
||||
conditions.append(cast(Order.id, String).ilike(term))
|
||||
where_clause = or_(*conditions)
|
||||
where_clause = _search_clause(search) if search else None
|
||||
|
||||
count_stmt = select(func.count()).select_from(Order).where(owner_clause)
|
||||
if where_clause is not None:
|
||||
@@ -116,38 +180,47 @@ def register(url_prefix: str) -> Blueprint:
|
||||
result = await g.s.execute(stmt)
|
||||
orders = result.scalars().all()
|
||||
|
||||
from shared.sx.page import get_template_context
|
||||
from sx.sx_components import (
|
||||
render_orders_page,
|
||||
render_orders_rows,
|
||||
render_orders_oob,
|
||||
)
|
||||
from sx.sx_components import _orders_rows_sx
|
||||
from shared.sx.helpers import sx_response
|
||||
|
||||
ctx = await get_template_context()
|
||||
qs_fn = makeqs_factory()
|
||||
|
||||
if not is_htmx_request():
|
||||
html = await render_orders_page(
|
||||
ctx, orders, page, total_pages, search, total_count,
|
||||
url_for, qs_fn,
|
||||
)
|
||||
resp = await make_response(html)
|
||||
elif page > 1:
|
||||
# Sx wire format — client renders order rows
|
||||
from shared.sx.helpers import sx_response
|
||||
sx_src = await render_orders_rows(
|
||||
ctx, orders, page, total_pages, url_for, qs_fn,
|
||||
)
|
||||
resp = sx_response(sx_src)
|
||||
else:
|
||||
from shared.sx.helpers import sx_response
|
||||
sx_src = await render_orders_oob(
|
||||
ctx, orders, page, total_pages, search, total_count,
|
||||
url_for, qs_fn,
|
||||
)
|
||||
resp = sx_response(sx_src)
|
||||
|
||||
sx_src = _orders_rows_sx(orders, page, total_pages, url_for, qs_fn)
|
||||
resp = sx_response(sx_src)
|
||||
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
||||
return _vary(resp)
|
||||
|
||||
return bp
|
||||
|
||||
|
||||
def _search_clause(search: str):
|
||||
"""Build an OR search clause across order fields."""
|
||||
term = f"%{search.strip()}%"
|
||||
conditions = [
|
||||
Order.status.ilike(term),
|
||||
Order.currency.ilike(term),
|
||||
Order.sumup_checkout_id.ilike(term),
|
||||
Order.sumup_status.ilike(term),
|
||||
Order.description.ilike(term),
|
||||
]
|
||||
conditions.append(
|
||||
exists(
|
||||
select(1)
|
||||
.select_from(OrderItem)
|
||||
.where(
|
||||
OrderItem.order_id == Order.id,
|
||||
or_(
|
||||
OrderItem.product_title.ilike(term),
|
||||
OrderItem.product_slug.ilike(term),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
try:
|
||||
search_id = int(search)
|
||||
except (TypeError, ValueError):
|
||||
search_id = None
|
||||
if search_id is not None:
|
||||
conditions.append(Order.id == search_id)
|
||||
else:
|
||||
conditions.append(cast(Order.id, String).ilike(term))
|
||||
return or_(*conditions)
|
||||
|
||||
@@ -12,10 +12,8 @@ from typing import Any
|
||||
|
||||
from shared.sx.jinja_bridge import load_service_components
|
||||
from shared.sx.helpers import (
|
||||
call_url, root_header_sx,
|
||||
full_page_sx, header_child_sx, oob_page_sx,
|
||||
call_url,
|
||||
sx_call, SxExpr,
|
||||
search_mobile_sx, search_desktop_sx,
|
||||
)
|
||||
from shared.infrastructure.urls import market_product_url, cart_url
|
||||
|
||||
@@ -103,7 +101,7 @@ def _orders_rows_sx(orders: list, page: int, total_pages: int,
|
||||
|
||||
parts = []
|
||||
for o in orders:
|
||||
d = _order_row_data(o, pfx + url_for_fn("orders.order.order_detail", order_id=o.id))
|
||||
d = _order_row_data(o, pfx + url_for_fn("orders.defpage_order_detail", order_id=o.id))
|
||||
parts.append(sx_call("order-row-desktop",
|
||||
oid=d["oid"], created=d["created"],
|
||||
desc=d["desc"], total=d["total"],
|
||||
@@ -115,7 +113,7 @@ def _orders_rows_sx(orders: list, page: int, total_pages: int,
|
||||
status=d["status"], url=d["url"]))
|
||||
|
||||
if page < total_pages:
|
||||
next_url = pfx + url_for_fn("orders.list_orders") + qs_fn(page=page + 1)
|
||||
next_url = pfx + url_for_fn("orders.orders_rows") + qs_fn(page=page + 1)
|
||||
parts.append(sx_call("infinite-scroll",
|
||||
url=next_url, page=page,
|
||||
total_pages=total_pages,
|
||||
@@ -143,63 +141,8 @@ def _orders_summary_sx(ctx: dict) -> str:
|
||||
# Public API: orders list
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def render_orders_page(ctx: dict, orders: list, page: int,
|
||||
total_pages: int, search: str | None,
|
||||
search_count: int, url_for_fn: Any,
|
||||
qs_fn: Any) -> str:
|
||||
"""Full page: orders list (sx wire format)."""
|
||||
from shared.utils import route_prefix
|
||||
|
||||
ctx["search"] = search
|
||||
ctx["search_count"] = search_count
|
||||
list_url = route_prefix() + url_for_fn("orders.list_orders")
|
||||
|
||||
rows = _orders_rows_sx(orders, page, total_pages, url_for_fn, qs_fn)
|
||||
main = _orders_main_panel_sx(orders, rows)
|
||||
|
||||
hdr = root_header_sx(ctx)
|
||||
inner = "(<> " + _auth_header_sx(ctx) + " " + _orders_header_sx(ctx, list_url) + ")"
|
||||
hdr = "(<> " + hdr + " " + header_child_sx(inner) + ")"
|
||||
|
||||
return full_page_sx(ctx, header_rows=hdr,
|
||||
filter=_orders_summary_sx(ctx),
|
||||
aside=search_desktop_sx(ctx),
|
||||
content=main)
|
||||
|
||||
|
||||
async def render_orders_rows(ctx: dict, orders: list, page: int,
|
||||
total_pages: int, url_for_fn: Any,
|
||||
qs_fn: Any) -> str:
|
||||
"""Pagination: just the table rows (sx wire format)."""
|
||||
return _orders_rows_sx(orders, page, total_pages, url_for_fn, qs_fn)
|
||||
|
||||
|
||||
|
||||
async def render_orders_oob(ctx: dict, orders: list, page: int,
|
||||
total_pages: int, search: str | None,
|
||||
search_count: int, url_for_fn: Any,
|
||||
qs_fn: Any) -> str:
|
||||
"""OOB response for HTMX navigation to orders list (sx)."""
|
||||
from shared.utils import route_prefix
|
||||
|
||||
ctx["search"] = search
|
||||
ctx["search_count"] = search_count
|
||||
list_url = route_prefix() + url_for_fn("orders.list_orders")
|
||||
|
||||
rows = _orders_rows_sx(orders, page, total_pages, url_for_fn, qs_fn)
|
||||
main = _orders_main_panel_sx(orders, rows)
|
||||
|
||||
auth_hdr = _auth_header_sx(ctx, oob=True)
|
||||
auth_child_oob = sx_call("oob-header-sx",
|
||||
parent_id="auth-header-child",
|
||||
row=SxExpr(_orders_header_sx(ctx, list_url)))
|
||||
root_hdr = root_header_sx(ctx, oob=True)
|
||||
oobs = "(<> " + auth_hdr + " " + auth_child_oob + " " + root_hdr + ")"
|
||||
|
||||
return oob_page_sx(oobs=oobs,
|
||||
filter=_orders_summary_sx(ctx),
|
||||
aside=search_desktop_sx(ctx),
|
||||
content=main)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -300,68 +243,8 @@ def _order_filter_sx(order: Any, list_url: str, recheck_url: str,
|
||||
)
|
||||
|
||||
|
||||
async def render_order_page(ctx: dict, order: Any,
|
||||
calendar_entries: list | None,
|
||||
url_for_fn: Any) -> str:
|
||||
"""Full page: single order detail (sx wire format)."""
|
||||
from shared.utils import route_prefix
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
|
||||
pfx = route_prefix()
|
||||
detail_url = pfx + url_for_fn("orders.order.order_detail", order_id=order.id)
|
||||
list_url = pfx + url_for_fn("orders.list_orders")
|
||||
recheck_url = pfx + url_for_fn("orders.order.order_recheck", order_id=order.id)
|
||||
pay_url = pfx + url_for_fn("orders.order.order_pay", order_id=order.id)
|
||||
|
||||
main = _order_main_sx(order, calendar_entries)
|
||||
filt = _order_filter_sx(order, list_url, recheck_url, pay_url, generate_csrf_token())
|
||||
|
||||
# Header stack: root -> auth -> orders -> order
|
||||
hdr = root_header_sx(ctx)
|
||||
order_row = sx_call(
|
||||
"menu-row-sx",
|
||||
id="order-row", level=3, colour="sky", link_href=detail_url,
|
||||
link_label="Order", icon="fa fa-gbp",
|
||||
)
|
||||
detail_header = sx_call(
|
||||
"order-detail-header-stack",
|
||||
auth=SxExpr(_auth_header_sx(ctx)),
|
||||
orders=SxExpr(_orders_header_sx(ctx, list_url)),
|
||||
order=SxExpr(order_row),
|
||||
)
|
||||
hdr = "(<> " + hdr + " " + detail_header + ")"
|
||||
|
||||
return full_page_sx(ctx, header_rows=hdr, filter=filt, content=main)
|
||||
|
||||
|
||||
async def render_order_oob(ctx: dict, order: Any,
|
||||
calendar_entries: list | None,
|
||||
url_for_fn: Any) -> str:
|
||||
"""OOB response for single order detail (sx)."""
|
||||
from shared.utils import route_prefix
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
|
||||
pfx = route_prefix()
|
||||
detail_url = pfx + url_for_fn("orders.order.order_detail", order_id=order.id)
|
||||
list_url = pfx + url_for_fn("orders.list_orders")
|
||||
recheck_url = pfx + url_for_fn("orders.order.order_recheck", order_id=order.id)
|
||||
pay_url = pfx + url_for_fn("orders.order.order_pay", order_id=order.id)
|
||||
|
||||
main = _order_main_sx(order, calendar_entries)
|
||||
filt = _order_filter_sx(order, list_url, recheck_url, pay_url, generate_csrf_token())
|
||||
|
||||
order_row_oob = sx_call(
|
||||
"menu-row-sx",
|
||||
id="order-row", level=3, colour="sky", link_href=detail_url,
|
||||
link_label="Order", icon="fa fa-gbp", oob=True,
|
||||
)
|
||||
header_child_oob = sx_call("oob-header-sx",
|
||||
parent_id="orders-header-child",
|
||||
row=SxExpr(order_row_oob))
|
||||
root_hdr = root_header_sx(ctx, oob=True)
|
||||
oobs = "(<> " + header_child_oob + " " + root_hdr + ")"
|
||||
|
||||
return oob_page_sx(oobs=oobs, filter=filt, content=main)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
0
orders/sxc/__init__.py
Normal file
0
orders/sxc/__init__.py
Normal file
201
orders/sxc/pages/__init__.py
Normal file
201
orders/sxc/pages/__init__.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""Orders defpage setup — registers layouts, page helpers, and loads .sx pages."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def setup_orders_pages() -> None:
|
||||
"""Register orders-specific layouts, page helpers, and load page definitions."""
|
||||
_register_orders_layouts()
|
||||
_register_orders_helpers()
|
||||
_load_orders_page_files()
|
||||
|
||||
|
||||
def _load_orders_page_files() -> None:
|
||||
"""Load defpage definitions from orders/sxc/pages/*.sx."""
|
||||
import os
|
||||
from shared.sx.pages import load_page_dir
|
||||
load_page_dir(os.path.dirname(__file__), "orders")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Layouts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _register_orders_layouts() -> None:
|
||||
from shared.sx.layouts import register_custom_layout
|
||||
register_custom_layout("orders", _orders_full, _orders_oob, _orders_mobile)
|
||||
register_custom_layout("order-detail", _order_detail_full, _order_detail_oob, _order_detail_mobile)
|
||||
|
||||
|
||||
def _orders_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, header_child_sx
|
||||
from sx.sx_components import _auth_header_sx, _orders_header_sx
|
||||
|
||||
list_url = kw.get("list_url", "/")
|
||||
root_hdr = root_header_sx(ctx)
|
||||
inner = "(<> " + _auth_header_sx(ctx) + " " + _orders_header_sx(ctx, list_url) + ")"
|
||||
return "(<> " + root_hdr + " " + header_child_sx(inner) + ")"
|
||||
|
||||
|
||||
def _orders_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, sx_call, SxExpr
|
||||
from sx.sx_components import _auth_header_sx, _orders_header_sx
|
||||
|
||||
list_url = kw.get("list_url", "/")
|
||||
auth_hdr = _auth_header_sx(ctx, oob=True)
|
||||
auth_child_oob = sx_call("oob-header-sx",
|
||||
parent_id="auth-header-child",
|
||||
row=SxExpr(_orders_header_sx(ctx, list_url)))
|
||||
root_hdr = root_header_sx(ctx, oob=True)
|
||||
return "(<> " + auth_hdr + " " + auth_child_oob + " " + root_hdr + ")"
|
||||
|
||||
|
||||
def _orders_mobile(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import mobile_menu_sx, mobile_root_nav_sx
|
||||
return mobile_menu_sx(mobile_root_nav_sx(ctx))
|
||||
|
||||
|
||||
def _order_detail_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, sx_call, SxExpr
|
||||
from sx.sx_components import _auth_header_sx, _orders_header_sx
|
||||
|
||||
list_url = kw.get("list_url", "/")
|
||||
detail_url = kw.get("detail_url", "/")
|
||||
root_hdr = root_header_sx(ctx)
|
||||
order_row = sx_call(
|
||||
"menu-row-sx",
|
||||
id="order-row", level=3, colour="sky", link_href=detail_url,
|
||||
link_label="Order", icon="fa fa-gbp",
|
||||
)
|
||||
detail_header = sx_call(
|
||||
"order-detail-header-stack",
|
||||
auth=SxExpr(_auth_header_sx(ctx)),
|
||||
orders=SxExpr(_orders_header_sx(ctx, list_url)),
|
||||
order=SxExpr(order_row),
|
||||
)
|
||||
return "(<> " + root_hdr + " " + detail_header + ")"
|
||||
|
||||
|
||||
def _order_detail_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, sx_call, SxExpr
|
||||
|
||||
detail_url = kw.get("detail_url", "/")
|
||||
order_row_oob = sx_call(
|
||||
"menu-row-sx",
|
||||
id="order-row", level=3, colour="sky", link_href=detail_url,
|
||||
link_label="Order", icon="fa fa-gbp", oob=True,
|
||||
)
|
||||
header_child_oob = sx_call("oob-header-sx",
|
||||
parent_id="orders-header-child",
|
||||
row=SxExpr(order_row_oob))
|
||||
root_hdr = root_header_sx(ctx, oob=True)
|
||||
return "(<> " + header_child_oob + " " + root_hdr + ")"
|
||||
|
||||
|
||||
def _order_detail_mobile(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import mobile_menu_sx, mobile_root_nav_sx
|
||||
return mobile_menu_sx(mobile_root_nav_sx(ctx))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Page helpers — Python functions callable from defpage expressions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _register_orders_helpers() -> None:
|
||||
from shared.sx.pages import register_page_helpers
|
||||
|
||||
register_page_helpers("orders", {
|
||||
# Orders list
|
||||
"orders-list-content": _h_orders_list_content,
|
||||
"orders-list-filter": _h_orders_list_filter,
|
||||
"orders-list-aside": _h_orders_list_aside,
|
||||
"orders-list-url": _h_orders_list_url,
|
||||
# Order detail
|
||||
"order-detail-content": _h_order_detail_content,
|
||||
"order-detail-filter": _h_order_detail_filter,
|
||||
"order-detail-url": _h_order_detail_url,
|
||||
"order-list-url-from-detail": _h_order_list_url_from_detail,
|
||||
})
|
||||
|
||||
|
||||
def _h_orders_list_content():
|
||||
from quart import g
|
||||
d = getattr(g, "orders_page_data", None)
|
||||
if not d:
|
||||
from shared.sx.helpers import sx_call
|
||||
return sx_call("order-empty-state")
|
||||
from sx.sx_components import _orders_rows_sx, _orders_main_panel_sx
|
||||
rows = _orders_rows_sx(d["orders"], d["page"], d["total_pages"],
|
||||
d["url_for_fn"], d["qs_fn"])
|
||||
return _orders_main_panel_sx(d["orders"], rows)
|
||||
|
||||
|
||||
def _h_orders_list_filter():
|
||||
from quart import g
|
||||
from shared.sx.helpers import sx_call, SxExpr
|
||||
from shared.sx.page import SEARCH_HEADERS_MOBILE
|
||||
d = getattr(g, "orders_page_data", None)
|
||||
search = d.get("search", "") if d else ""
|
||||
search_count = d.get("search_count", "") if d else ""
|
||||
search_mobile = sx_call("search-mobile",
|
||||
current_local_href="/",
|
||||
search=search or "",
|
||||
search_count=search_count or "",
|
||||
hx_select="#main-panel",
|
||||
search_headers_mobile=SEARCH_HEADERS_MOBILE,
|
||||
)
|
||||
return sx_call("order-list-header", search_mobile=SxExpr(search_mobile))
|
||||
|
||||
|
||||
def _h_orders_list_aside():
|
||||
from quart import g
|
||||
from shared.sx.helpers import sx_call
|
||||
from shared.sx.page import SEARCH_HEADERS_DESKTOP
|
||||
d = getattr(g, "orders_page_data", None)
|
||||
search = d.get("search", "") if d else ""
|
||||
search_count = d.get("search_count", "") if d else ""
|
||||
return sx_call("search-desktop",
|
||||
current_local_href="/",
|
||||
search=search or "",
|
||||
search_count=search_count or "",
|
||||
hx_select="#main-panel",
|
||||
search_headers_desktop=SEARCH_HEADERS_DESKTOP,
|
||||
)
|
||||
|
||||
|
||||
def _h_orders_list_url():
|
||||
from quart import g
|
||||
d = getattr(g, "orders_page_data", None)
|
||||
return d["list_url"] if d else "/"
|
||||
|
||||
|
||||
def _h_order_detail_content():
|
||||
from quart import g
|
||||
d = getattr(g, "order_detail_data", None)
|
||||
if not d:
|
||||
return ""
|
||||
from sx.sx_components import _order_main_sx
|
||||
return _order_main_sx(d["order"], d["calendar_entries"])
|
||||
|
||||
|
||||
def _h_order_detail_filter():
|
||||
from quart import g
|
||||
d = getattr(g, "order_detail_data", None)
|
||||
if not d:
|
||||
return ""
|
||||
from sx.sx_components import _order_filter_sx
|
||||
return _order_filter_sx(d["order"], d["list_url"], d["recheck_url"],
|
||||
d["pay_url"], d["csrf_token"])
|
||||
|
||||
|
||||
def _h_order_detail_url():
|
||||
from quart import g
|
||||
d = getattr(g, "order_detail_data", None)
|
||||
return d["detail_url"] if d else "/"
|
||||
|
||||
|
||||
def _h_order_list_url_from_detail():
|
||||
from quart import g
|
||||
d = getattr(g, "order_detail_data", None)
|
||||
return d["list_url"] if d else "/"
|
||||
27
orders/sxc/pages/orders.sx
Normal file
27
orders/sxc/pages/orders.sx
Normal file
@@ -0,0 +1,27 @@
|
||||
;; Orders app — declarative page definitions
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Orders list
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage orders-list
|
||||
:path "/"
|
||||
:auth :public
|
||||
:layout (:orders
|
||||
:list-url (orders-list-url))
|
||||
:filter (orders-list-filter)
|
||||
:aside (orders-list-aside)
|
||||
:content (orders-list-content))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Order detail
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage order-detail
|
||||
:path "/<int:order_id>/"
|
||||
:auth :public
|
||||
:layout (:order-detail
|
||||
:list-url (order-list-url-from-detail)
|
||||
:detail-url (order-detail-url))
|
||||
:filter (order-detail-filter)
|
||||
:content (order-detail-content))
|
||||
Reference in New Issue
Block a user