Replace ~250 render_to_sx calls across all services with sync sx_call, converting many async functions to sync where no other awaits remained. Make render_to_sx/render_to_sx_with_env private (_render_to_sx). Add (post-header-ctx) IO primitive and shared post/post-admin defmacros. Convert built-in post/post-admin layouts from Python to register_sx_layout with .sx defcomps. Remove dead post_admin_mobile_nav_sx. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
149 lines
4.9 KiB
Python
149 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
from quart import Blueprint, g, redirect, url_for, request
|
|
from sqlalchemy import select, func, or_, cast, String, exists
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
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 bp.order.routes import register as register_order
|
|
|
|
from .filters.qs import makeqs_factory, decode
|
|
|
|
|
|
def register(url_prefix: str) -> Blueprint:
|
|
bp = Blueprint("orders", __name__, url_prefix=url_prefix)
|
|
bp.register_blueprint(register_order())
|
|
|
|
ORDERS_PER_PAGE = 10
|
|
|
|
@bp.before_request
|
|
def route():
|
|
g.makeqs_factory = makeqs_factory
|
|
|
|
@bp.before_request
|
|
async def _require_identity():
|
|
"""Orders require a logged-in user or at least a cart session."""
|
|
ident = current_cart_identity()
|
|
if not ident["user_id"] and not ident["session_id"]:
|
|
return redirect(url_for("auth.login_form"))
|
|
|
|
@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"]
|
|
elif ident["session_id"]:
|
|
owner_clause = Order.session_id == ident["session_id"]
|
|
else:
|
|
return redirect(url_for("auth.login_form"))
|
|
|
|
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.sx.helpers import sx_response, sx_call
|
|
from shared.utils import route_prefix
|
|
|
|
pfx = route_prefix()
|
|
order_dicts = []
|
|
for o in orders:
|
|
order_dicts.append({
|
|
"id": o.id,
|
|
"status": o.status or "pending",
|
|
"created_at_formatted": o.created_at.strftime("%-d %b %Y, %H:%M") if o.created_at else "\u2014",
|
|
"description": o.description or "",
|
|
"currency": o.currency or "GBP",
|
|
"total_formatted": f"{o.total_amount or 0:.2f}",
|
|
})
|
|
|
|
detail_prefix = pfx + url_for("defpage_order_detail", order_id=0).rsplit("0/", 1)[0]
|
|
qs_fn = makeqs_factory()
|
|
rows_url = pfx + url_for("orders.orders_rows")
|
|
|
|
# Build just the rows fragment (not full table) for infinite scroll
|
|
parts = []
|
|
for od in order_dicts:
|
|
parts.append(sx_call("order-row-pair",
|
|
order=od,
|
|
detail_url_prefix=detail_prefix))
|
|
if page < total_pages:
|
|
parts.append(sx_call("infinite-scroll",
|
|
url=rows_url + qs_fn(page=page + 1),
|
|
page=page, total_pages=total_pages,
|
|
id_prefix="orders", colspan=5))
|
|
else:
|
|
parts.append(sx_call("order-end-row"))
|
|
sx_src = "(<> " + " ".join(parts) + ")"
|
|
|
|
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)
|