Auto-mount defpages: eliminate Python route stubs across all 9 services
Defpages are now declared with absolute paths in .sx files and auto-mounted directly on the Quart app, removing ~850 lines of blueprint mount_pages calls, before_request hooks, and g.* wrapper boilerplate. A new page = one defpage declaration, nothing else. Infrastructure: - async_eval awaits coroutine results from callable dispatch - auto_mount_pages() mounts all registered defpages on the app - g._defpage_ctx pattern passes helper data to layout context Migrated: sx, account, orders, federation, cart, market, events, blog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
15
cart/app.py
15
cart/app.py
@@ -185,8 +185,6 @@ def create_app() -> "Quart":
|
||||
from sxc.pages import setup_cart_pages
|
||||
setup_cart_pages()
|
||||
|
||||
from shared.sx.pages import mount_pages
|
||||
|
||||
# --- Blueprint registration ---
|
||||
# Static prefixes first, dynamic (page_slug) last
|
||||
|
||||
@@ -196,21 +194,22 @@ def create_app() -> "Quart":
|
||||
url_prefix="/",
|
||||
)
|
||||
|
||||
# Cart overview at GET /
|
||||
# Cart overview blueprint (no defpage routes, just action endpoints)
|
||||
overview_bp = register_cart_overview(url_prefix="/")
|
||||
mount_pages(overview_bp, "cart", names=["cart-overview"])
|
||||
app.register_blueprint(overview_bp, url_prefix="/")
|
||||
|
||||
# Page admin at /<page_slug>/admin/ (before page_cart catch-all)
|
||||
# Page admin (PUT /payments/ etc.)
|
||||
admin_bp = register_page_admin()
|
||||
mount_pages(admin_bp, "cart", names=["cart-admin", "cart-payments"])
|
||||
app.register_blueprint(admin_bp, url_prefix="/<page_slug>/admin")
|
||||
|
||||
# Page cart at /<page_slug>/ (dynamic, matched last)
|
||||
# Page cart (POST /checkout/ etc.)
|
||||
page_cart_bp = register_page_cart(url_prefix="/")
|
||||
mount_pages(page_cart_bp, "cart", names=["page-cart-view"])
|
||||
app.register_blueprint(page_cart_bp, url_prefix="/<page_slug>")
|
||||
|
||||
# Auto-mount all defpages with absolute paths
|
||||
from shared.sx.pages import auto_mount_pages
|
||||
auto_mount_pages(app, "cart")
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
||||
@@ -56,9 +56,9 @@ def register(url_prefix: str) -> Blueprint:
|
||||
|
||||
if request.headers.get("SX-Request") == "true" or request.headers.get("HX-Request") == "true":
|
||||
# Redirect to overview for HTMX
|
||||
return redirect(url_for("cart_overview.defpage_cart_overview"))
|
||||
return redirect(url_for("defpage_cart_overview"))
|
||||
|
||||
return redirect(url_for("cart_overview.defpage_cart_overview"))
|
||||
return redirect(url_for("defpage_cart_overview"))
|
||||
|
||||
@bp.post("/quantity/<int:product_id>/")
|
||||
async def update_quantity(product_id: int):
|
||||
@@ -137,7 +137,7 @@ def register(url_prefix: str) -> Blueprint:
|
||||
tickets = await get_ticket_cart_entries(g.s)
|
||||
|
||||
if not cart and not calendar_entries and not tickets:
|
||||
return redirect(url_for("cart_overview.defpage_cart_overview"))
|
||||
return redirect(url_for("defpage_cart_overview"))
|
||||
|
||||
product_total = total(cart) or 0
|
||||
calendar_amount = calendar_total(calendar_entries) or 0
|
||||
@@ -145,7 +145,7 @@ def register(url_prefix: str) -> Blueprint:
|
||||
cart_total = product_total + calendar_amount + ticket_amount
|
||||
|
||||
if cart_total <= 0:
|
||||
return redirect(url_for("cart_overview.defpage_cart_overview"))
|
||||
return redirect(url_for("defpage_cart_overview"))
|
||||
|
||||
try:
|
||||
page_config = await resolve_page_config(g.s, cart, calendar_entries, tickets)
|
||||
|
||||
@@ -3,24 +3,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from quart import Blueprint, g, request
|
||||
|
||||
from .services import get_cart_grouped_by_page
|
||||
from quart import Blueprint
|
||||
|
||||
|
||||
def register(url_prefix: str) -> Blueprint:
|
||||
bp = Blueprint("cart_overview", __name__, url_prefix=url_prefix)
|
||||
|
||||
@bp.before_request
|
||||
async def _prepare_page_data():
|
||||
"""Load overview data for defpage route."""
|
||||
endpoint = request.endpoint or ""
|
||||
if not endpoint.endswith("defpage_cart_overview"):
|
||||
return
|
||||
from shared.sx.page import get_template_context
|
||||
from sx.sx_components import _overview_main_panel_sx
|
||||
page_groups = await get_cart_grouped_by_page(g.s)
|
||||
ctx = await get_template_context()
|
||||
g.overview_content = _overview_main_panel_sx(page_groups, ctx)
|
||||
|
||||
return bp
|
||||
|
||||
@@ -19,26 +19,6 @@ from .services import current_cart_identity
|
||||
def register(url_prefix: str) -> Blueprint:
|
||||
bp = Blueprint("page_cart", __name__, url_prefix=url_prefix)
|
||||
|
||||
@bp.before_request
|
||||
async def _prepare_page_data():
|
||||
"""Load page cart data for defpage route."""
|
||||
endpoint = request.endpoint or ""
|
||||
if not endpoint.endswith("defpage_page_cart_view"):
|
||||
return
|
||||
post = g.page_post
|
||||
cart = await get_cart_for_page(g.s, post.id)
|
||||
cal_entries = await get_calendar_entries_for_page(g.s, post.id)
|
||||
page_tickets = await get_tickets_for_page(g.s, post.id)
|
||||
ticket_groups = group_tickets(page_tickets)
|
||||
|
||||
from shared.sx.page import get_template_context
|
||||
from sx.sx_components import _page_cart_main_panel_sx
|
||||
ctx = await get_template_context()
|
||||
g.page_cart_content = _page_cart_main_panel_sx(
|
||||
ctx, cart, cal_entries, page_tickets, ticket_groups,
|
||||
total, calendar_total, ticket_total,
|
||||
)
|
||||
|
||||
@bp.post("/checkout/")
|
||||
async def page_checkout():
|
||||
post = g.page_post
|
||||
@@ -48,7 +28,7 @@ def register(url_prefix: str) -> Blueprint:
|
||||
page_tickets = await get_tickets_for_page(g.s, post.id)
|
||||
|
||||
if not cart and not cal_entries and not page_tickets:
|
||||
return redirect(url_for("page_cart.defpage_page_cart_view"))
|
||||
return redirect(url_for("defpage_page_cart_view"))
|
||||
|
||||
product_total_val = total(cart) or 0
|
||||
calendar_amount = calendar_total(cal_entries) or 0
|
||||
@@ -56,7 +36,7 @@ def register(url_prefix: str) -> Blueprint:
|
||||
cart_total = product_total_val + calendar_amount + ticket_amount
|
||||
|
||||
if cart_total <= 0:
|
||||
return redirect(url_for("page_cart.defpage_page_cart_view"))
|
||||
return redirect(url_for("defpage_page_cart_view"))
|
||||
|
||||
ident = current_cart_identity()
|
||||
|
||||
|
||||
@@ -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
|
||||
from sqlalchemy import select, func, or_, cast, String, exists
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
|
||||
@@ -13,23 +13,6 @@ from shared.sx.helpers import sx_response
|
||||
def register():
|
||||
bp = Blueprint("page_admin", __name__)
|
||||
|
||||
@bp.before_request
|
||||
async def _prepare_page_data():
|
||||
"""Pre-render admin content for defpage routes."""
|
||||
endpoint = request.endpoint or ""
|
||||
if request.method != "GET":
|
||||
return
|
||||
if endpoint.endswith("defpage_cart_admin"):
|
||||
from shared.sx.page import get_template_context
|
||||
from sx.sx_components import _cart_admin_main_panel_sx
|
||||
ctx = await get_template_context()
|
||||
g.cart_admin_content = _cart_admin_main_panel_sx(ctx)
|
||||
elif endpoint.endswith("defpage_cart_payments"):
|
||||
from shared.sx.page import get_template_context
|
||||
from sx.sx_components import _cart_payments_main_panel_sx
|
||||
ctx = await get_template_context()
|
||||
g.cart_payments_content = _cart_payments_main_panel_sx(ctx)
|
||||
|
||||
@bp.put("/payments/")
|
||||
@require_admin
|
||||
async def update_sumup(**kwargs):
|
||||
|
||||
@@ -771,7 +771,7 @@ def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
|
||||
def _cart_admin_main_panel_sx(ctx: dict) -> str:
|
||||
"""Admin overview panel -- links to sub-admin pages."""
|
||||
from quart import url_for
|
||||
payments_href = url_for("page_admin.defpage_cart_payments")
|
||||
payments_href = url_for("defpage_cart_payments")
|
||||
return (
|
||||
'(div :id "main-panel"'
|
||||
' (div :class "flex items-center justify-between p-3 border-b"'
|
||||
|
||||
@@ -90,32 +90,48 @@ def _register_cart_helpers() -> None:
|
||||
})
|
||||
|
||||
|
||||
def _h_overview_content():
|
||||
async def _h_overview_content(**kw):
|
||||
from quart import g
|
||||
page_groups = getattr(g, "overview_page_groups", [])
|
||||
from shared.sx.page import get_template_context
|
||||
from sx.sx_components import _overview_main_panel_sx
|
||||
# _overview_main_panel_sx needs ctx for url helpers — use g-based approach
|
||||
# The function reads cart_url from ctx, which we can get from template context
|
||||
from shared.sx.page import get_template_context
|
||||
import asyncio
|
||||
# Page helpers are sync — we pre-compute in before_request
|
||||
return getattr(g, "overview_content", "")
|
||||
from bp.cart.services import get_cart_grouped_by_page
|
||||
page_groups = await get_cart_grouped_by_page(g.s)
|
||||
ctx = await get_template_context()
|
||||
return _overview_main_panel_sx(page_groups, ctx)
|
||||
|
||||
|
||||
def _h_page_cart_content():
|
||||
async def _h_page_cart_content(page_slug=None, **kw):
|
||||
from quart import g
|
||||
return getattr(g, "page_cart_content", "")
|
||||
from shared.sx.page import get_template_context
|
||||
from sx.sx_components import _page_cart_main_panel_sx
|
||||
from bp.cart.services import total, calendar_total, ticket_total
|
||||
from bp.cart.services.page_cart import (
|
||||
get_cart_for_page, get_calendar_entries_for_page, get_tickets_for_page,
|
||||
)
|
||||
from bp.cart.services.ticket_groups import group_tickets
|
||||
|
||||
post = g.page_post
|
||||
cart = await get_cart_for_page(g.s, post.id)
|
||||
cal_entries = await get_calendar_entries_for_page(g.s, post.id)
|
||||
page_tickets = await get_tickets_for_page(g.s, post.id)
|
||||
ticket_groups = group_tickets(page_tickets)
|
||||
|
||||
ctx = await get_template_context()
|
||||
return _page_cart_main_panel_sx(
|
||||
ctx, cart, cal_entries, page_tickets, ticket_groups,
|
||||
total, calendar_total, ticket_total,
|
||||
)
|
||||
|
||||
|
||||
def _h_cart_admin_content():
|
||||
async def _h_cart_admin_content(page_slug=None, **kw):
|
||||
from shared.sx.page import get_template_context
|
||||
from sx.sx_components import _cart_admin_main_panel_sx
|
||||
ctx = await get_template_context()
|
||||
return _cart_admin_main_panel_sx(ctx)
|
||||
|
||||
|
||||
async def _h_cart_payments_content(page_slug=None, **kw):
|
||||
from shared.sx.page import get_template_context
|
||||
# Sync helper — _cart_admin_main_panel_sx is sync, but needs ctx
|
||||
# We can pre-compute in before_request, or use get_template_context_sync-like pattern
|
||||
from quart import g
|
||||
return getattr(g, "cart_admin_content", "")
|
||||
|
||||
|
||||
def _h_cart_payments_content():
|
||||
from quart import g
|
||||
return getattr(g, "cart_payments_content", "")
|
||||
from sx.sx_components import _cart_payments_main_panel_sx
|
||||
ctx = await get_template_context()
|
||||
return _cart_payments_main_panel_sx(ctx)
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
:content (overview-content))
|
||||
|
||||
(defpage page-cart-view
|
||||
:path "/"
|
||||
:path "/<page_slug>/"
|
||||
:auth :public
|
||||
:layout :cart-page
|
||||
:content (page-cart-content))
|
||||
|
||||
(defpage cart-admin
|
||||
:path "/"
|
||||
:path "/<page_slug>/admin/"
|
||||
:auth :admin
|
||||
:layout :cart-admin
|
||||
:content (cart-admin-content))
|
||||
|
||||
(defpage cart-payments
|
||||
:path "/payments/"
|
||||
:path "/<page_slug>/admin/payments/"
|
||||
:auth :admin
|
||||
:layout (:cart-admin :selected "payments")
|
||||
:content (cart-payments-content))
|
||||
|
||||
Reference in New Issue
Block a user