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:
@@ -81,12 +81,13 @@ def create_app() -> "Quart":
|
||||
app.register_blueprint(register_actions())
|
||||
app.register_blueprint(register_data())
|
||||
|
||||
# Orders list at / (defpage routes mounted below)
|
||||
# Orders list at /
|
||||
bp = register_orders(url_prefix="/")
|
||||
from shared.sx.pages import mount_pages
|
||||
mount_pages(bp, "orders")
|
||||
app.register_blueprint(bp)
|
||||
|
||||
from shared.sx.pages import auto_mount_pages
|
||||
auto_mount_pages(app, "orders")
|
||||
|
||||
# Checkout webhook + return
|
||||
app.register_blueprint(register_checkout())
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from quart import Blueprint, g, redirect, url_for, make_response, request
|
||||
from quart import Blueprint, g, redirect, url_for, request
|
||||
from sqlalchemy import select, func, or_, cast, String, exists
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
@@ -8,7 +8,6 @@ from shared.models.order import Order, OrderItem
|
||||
|
||||
from shared.infrastructure.http_utils import vary as _vary, current_url_without_page as _current_url_without_page
|
||||
from shared.infrastructure.cart_identity import current_cart_identity
|
||||
from shared.browser.app.utils.htmx import is_htmx_request
|
||||
from bp.order.routes import register as register_order
|
||||
|
||||
from .filters.qs import makeqs_factory, decode
|
||||
@@ -31,112 +30,6 @@ def register(url_prefix: str) -> Blueprint:
|
||||
if not ident["user_id"] and not ident["session_id"]:
|
||||
return redirect(url_for("auth.login_form"))
|
||||
|
||||
@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."""
|
||||
|
||||
@@ -119,7 +119,143 @@ def _register_orders_helpers() -> None:
|
||||
})
|
||||
|
||||
|
||||
def _h_orders_list_content():
|
||||
async def _ensure_orders_list():
|
||||
"""Fetch orders list data and store in g.orders_page_data."""
|
||||
from quart import g, url_for
|
||||
if hasattr(g, "orders_page_data"):
|
||||
return
|
||||
from sqlalchemy import select, func, or_, cast, String, exists
|
||||
from shared.models.order import Order, OrderItem
|
||||
from shared.infrastructure.cart_identity import current_cart_identity
|
||||
from shared.utils import route_prefix
|
||||
|
||||
ORDERS_PER_PAGE = 10
|
||||
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:
|
||||
g.orders_page_data = None
|
||||
return
|
||||
|
||||
from bp.orders.filters.qs import makeqs_factory, decode
|
||||
q = decode()
|
||||
page, search = q.page, q.search
|
||||
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)
|
||||
|
||||
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()
|
||||
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("defpage_orders_list"),
|
||||
}
|
||||
|
||||
|
||||
async def _ensure_order_detail(order_id):
|
||||
"""Fetch order detail data and store in g.order_detail_data."""
|
||||
from quart import g, url_for, abort
|
||||
if hasattr(g, "order_detail_data"):
|
||||
return
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
from shared.models.order import Order
|
||||
from shared.infrastructure.cart_identity import current_cart_identity
|
||||
from shared.utils import route_prefix
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
|
||||
if order_id is None:
|
||||
abort(404)
|
||||
|
||||
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:
|
||||
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:
|
||||
abort(404)
|
||||
return
|
||||
|
||||
pfx = route_prefix()
|
||||
g.order_detail_data = {
|
||||
"order": order,
|
||||
"calendar_entries": None,
|
||||
"detail_url": pfx + url_for("defpage_order_detail", order_id=order.id),
|
||||
"list_url": pfx + url_for("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(),
|
||||
}
|
||||
|
||||
|
||||
async def _h_orders_list_content(**kw):
|
||||
await _ensure_orders_list()
|
||||
from quart import g
|
||||
d = getattr(g, "orders_page_data", None)
|
||||
if not d:
|
||||
@@ -131,7 +267,8 @@ def _h_orders_list_content():
|
||||
return _orders_main_panel_sx(d["orders"], rows)
|
||||
|
||||
|
||||
def _h_orders_list_filter():
|
||||
async def _h_orders_list_filter(**kw):
|
||||
await _ensure_orders_list()
|
||||
from quart import g
|
||||
from shared.sx.helpers import sx_call, SxExpr
|
||||
from shared.sx.page import SEARCH_HEADERS_MOBILE
|
||||
@@ -148,7 +285,8 @@ def _h_orders_list_filter():
|
||||
return sx_call("order-list-header", search_mobile=SxExpr(search_mobile))
|
||||
|
||||
|
||||
def _h_orders_list_aside():
|
||||
async def _h_orders_list_aside(**kw):
|
||||
await _ensure_orders_list()
|
||||
from quart import g
|
||||
from shared.sx.helpers import sx_call
|
||||
from shared.sx.page import SEARCH_HEADERS_DESKTOP
|
||||
@@ -164,13 +302,15 @@ def _h_orders_list_aside():
|
||||
)
|
||||
|
||||
|
||||
def _h_orders_list_url():
|
||||
async def _h_orders_list_url(**kw):
|
||||
await _ensure_orders_list()
|
||||
from quart import g
|
||||
d = getattr(g, "orders_page_data", None)
|
||||
return d["list_url"] if d else "/"
|
||||
|
||||
|
||||
def _h_order_detail_content():
|
||||
async def _h_order_detail_content(order_id=None, **kw):
|
||||
await _ensure_order_detail(order_id)
|
||||
from quart import g
|
||||
d = getattr(g, "order_detail_data", None)
|
||||
if not d:
|
||||
@@ -179,7 +319,8 @@ def _h_order_detail_content():
|
||||
return _order_main_sx(d["order"], d["calendar_entries"])
|
||||
|
||||
|
||||
def _h_order_detail_filter():
|
||||
async def _h_order_detail_filter(order_id=None, **kw):
|
||||
await _ensure_order_detail(order_id)
|
||||
from quart import g
|
||||
d = getattr(g, "order_detail_data", None)
|
||||
if not d:
|
||||
@@ -189,13 +330,15 @@ def _h_order_detail_filter():
|
||||
d["pay_url"], d["csrf_token"])
|
||||
|
||||
|
||||
def _h_order_detail_url():
|
||||
async def _h_order_detail_url(order_id=None, **kw):
|
||||
await _ensure_order_detail(order_id)
|
||||
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():
|
||||
async def _h_order_list_url_from_detail(order_id=None, **kw):
|
||||
await _ensure_order_detail(order_id)
|
||||
from quart import g
|
||||
d = getattr(g, "order_detail_data", None)
|
||||
return d["list_url"] if d else "/"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
: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))
|
||||
:list-url (order-list-url-from-detail order-id)
|
||||
:detail-url (order-detail-url order-id))
|
||||
:filter (order-detail-filter order-id)
|
||||
:content (order-detail-content order-id))
|
||||
|
||||
Reference in New Issue
Block a user