Files
rose-ash/cart/services/cart_page.py
giles 418ac9424f
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m8s
Eliminate Python page helpers from account, federation, and cart
All three services now fetch page data via (service ...) IO primitives
in .sx defpages instead of Python middleman functions.

- Account: newsletters-data → AccountPageService.newsletters_data
- Federation: 8 page helpers → FederationPageService methods
  (timeline, compose, search, following, followers, notifications)
- Cart: 4 page helpers → CartPageService methods
  (overview, page-cart, admin, payments)
- Serializers moved to service modules, thin delegates kept for routes
- ~520 lines of Python page helpers removed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 02:01:50 +00:00

188 lines
8.0 KiB
Python

"""Cart page data service — provides serialized dicts for .sx defpages."""
from __future__ import annotations
from typing import Any
def _serialize_cart_item(item: Any) -> dict:
from quart import url_for
from shared.infrastructure.urls import market_product_url
p = item.product if hasattr(item, "product") else item
slug = p.slug if hasattr(p, "slug") else ""
unit_price = getattr(p, "special_price", None) or getattr(p, "regular_price", None)
currency = getattr(p, "regular_price_currency", "GBP") or "GBP"
return {
"slug": slug,
"title": p.title if hasattr(p, "title") else "",
"image": p.image if hasattr(p, "image") else None,
"brand": getattr(p, "brand", None),
"is_deleted": getattr(item, "is_deleted", False),
"unit_price": float(unit_price) if unit_price else None,
"special_price": float(p.special_price) if getattr(p, "special_price", None) else None,
"regular_price": float(p.regular_price) if getattr(p, "regular_price", None) else None,
"currency": currency,
"quantity": item.quantity,
"product_id": p.id,
"product_url": market_product_url(slug),
"qty_url": url_for("cart_global.update_quantity", product_id=p.id),
}
def _serialize_cal_entry(e: Any) -> dict:
name = getattr(e, "name", None) or getattr(e, "calendar_name", "")
start = e.start_at if hasattr(e, "start_at") else ""
end = getattr(e, "end_at", None)
cost = getattr(e, "cost", 0) or 0
end_str = f" \u2013 {end}" if end else ""
return {
"name": name,
"date_str": f"{start}{end_str}",
"cost": float(cost),
}
def _serialize_ticket_group(tg: Any) -> dict:
name = tg.entry_name if hasattr(tg, "entry_name") else tg.get("entry_name", "")
tt_name = tg.ticket_type_name if hasattr(tg, "ticket_type_name") else tg.get("ticket_type_name", "")
price = tg.price if hasattr(tg, "price") else tg.get("price", 0)
quantity = tg.quantity if hasattr(tg, "quantity") else tg.get("quantity", 0)
line_total = tg.line_total if hasattr(tg, "line_total") else tg.get("line_total", 0)
entry_id = tg.entry_id if hasattr(tg, "entry_id") else tg.get("entry_id", "")
tt_id = tg.ticket_type_id if hasattr(tg, "ticket_type_id") else tg.get("ticket_type_id", "")
start_at = tg.entry_start_at if hasattr(tg, "entry_start_at") else tg.get("entry_start_at")
end_at = tg.entry_end_at if hasattr(tg, "entry_end_at") else tg.get("entry_end_at")
date_str = start_at.strftime("%-d %b %Y, %H:%M") if start_at else ""
if end_at:
date_str += f" \u2013 {end_at.strftime('%-d %b %Y, %H:%M')}"
return {
"entry_name": name,
"ticket_type_name": tt_name or None,
"price": float(price or 0),
"quantity": quantity,
"line_total": float(line_total or 0),
"entry_id": entry_id,
"ticket_type_id": tt_id or None,
"date_str": date_str,
}
def _serialize_page_group(grp: Any) -> dict | None:
post = grp.get("post") if isinstance(grp, dict) else getattr(grp, "post", None)
cart_items = grp.get("cart_items", []) if isinstance(grp, dict) else getattr(grp, "cart_items", [])
cal_entries = grp.get("calendar_entries", []) if isinstance(grp, dict) else getattr(grp, "calendar_entries", [])
tickets = grp.get("tickets", []) if isinstance(grp, dict) else getattr(grp, "tickets", [])
if not cart_items and not cal_entries and not tickets:
return None
post_data = None
if post:
post_data = {
"slug": post.slug if hasattr(post, "slug") else post.get("slug", ""),
"title": post.title if hasattr(post, "title") else post.get("title", ""),
"feature_image": post.feature_image if hasattr(post, "feature_image") else post.get("feature_image"),
}
market_place = grp.get("market_place") if isinstance(grp, dict) else getattr(grp, "market_place", None)
mp_data = None
if market_place:
mp_data = {"name": market_place.name if hasattr(market_place, "name") else market_place.get("name", "")}
return {
"post": post_data,
"product_count": grp.get("product_count", 0) if isinstance(grp, dict) else getattr(grp, "product_count", 0),
"calendar_count": grp.get("calendar_count", 0) if isinstance(grp, dict) else getattr(grp, "calendar_count", 0),
"ticket_count": grp.get("ticket_count", 0) if isinstance(grp, dict) else getattr(grp, "ticket_count", 0),
"total": float(grp.get("total", 0) if isinstance(grp, dict) else getattr(grp, "total", 0)),
"market_place": mp_data,
}
class CartPageService:
"""Service for cart page data, callable via (service "cart-page" ...)."""
async def overview_data(self, session, **kw):
from shared.infrastructure.urls import cart_url
from bp.cart.services import get_cart_grouped_by_page
page_groups = await get_cart_grouped_by_page(session)
grp_dicts = [d for d in (_serialize_page_group(grp) for grp in page_groups) if d]
return {
"page_groups": grp_dicts,
"cart_url_base": cart_url(""),
}
async def page_cart_data(self, session, **kw):
from quart import g, request, url_for
from shared.infrastructure.urls import login_url
from shared.utils import route_prefix
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(session, post.id)
cal_entries = await get_calendar_entries_for_page(session, post.id)
page_tickets = await get_tickets_for_page(session, post.id)
ticket_groups = group_tickets(page_tickets)
# Build summary data
product_qty = sum(ci.quantity for ci in cart) if cart else 0
ticket_qty = len(page_tickets) if page_tickets else 0
item_count = product_qty + ticket_qty
product_total = total(cart) or 0
cal_total = calendar_total(cal_entries) or 0
tk_total = ticket_total(page_tickets) or 0
grand = float(product_total) + float(cal_total) + float(tk_total)
symbol = "\u00a3"
if cart and hasattr(cart[0], "product") and getattr(cart[0].product, "regular_price_currency", None):
cur = cart[0].product.regular_price_currency
symbol = "\u00a3" if cur == "GBP" else cur
user = getattr(g, "user", None)
page_post = getattr(g, "page_post", None)
summary = {
"item_count": item_count,
"grand_total": grand,
"symbol": symbol,
"is_logged_in": bool(user),
}
if user:
if page_post:
action = url_for("page_cart.page_checkout")
else:
action = url_for("cart_global.checkout")
summary["checkout_action"] = route_prefix() + action
summary["user_email"] = user.email
else:
summary["login_href"] = login_url(request.url)
return {
"cart_items": [_serialize_cart_item(i) for i in cart],
"cal_entries": [_serialize_cal_entry(e) for e in cal_entries],
"ticket_groups": [_serialize_ticket_group(tg) for tg in ticket_groups],
"summary": summary,
}
async def payments_data(self, session, **kw):
from shared.sx.page import get_template_context
ctx = await get_template_context()
page_config = ctx.get("page_config")
pc_data = None
if page_config:
pc_data = {
"sumup_api_key": bool(getattr(page_config, "sumup_api_key", None)),
"sumup_merchant_code": getattr(page_config, "sumup_merchant_code", None) or "",
"sumup_checkout_prefix": getattr(page_config, "sumup_checkout_prefix", None) or "",
}
return {"page_config": pc_data}