SxExpr is now a str subclass so it works everywhere a plain string does (join, isinstance, f-strings) while serialize() still emits it unquoted. sx_call() and all internal render functions (_render_to_sx, async_eval_to_sx, etc.) return SxExpr, eliminating the "forgot to wrap" bug class that caused the sx_content leak and list serialization bugs. - Phase 0: SxExpr(str) with .source property, __add__/__radd__ - Phase 1: sx_call returns SxExpr (drop-in, all 200+ sites unchanged) - Phase 2: async_eval_to_sx, async_eval_slot_to_sx, _render_to_sx, mobile_menu_sx return SxExpr; remove isinstance(str) workaround - Phase 3: Remove ~150 redundant SxExpr() wrappings across 45 files - Phase 4: serialize() docstring, handler return docs, ;; returns: sx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
168 lines
6.3 KiB
Python
168 lines
6.3 KiB
Python
"""Page helpers for market defpage system."""
|
|
from __future__ import annotations
|
|
|
|
from .cards import (
|
|
_market_cards_sx, _markets_grid, _no_markets_sx,
|
|
_market_landing_content_sx,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Page admin panel (used by _h_page_admin_content)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
async def _markets_admin_panel_sx(ctx: dict) -> str:
|
|
"""Render the markets list + create form panel."""
|
|
from quart import g, url_for
|
|
from shared.services.registry import services
|
|
from shared.sx.helpers import sx_call
|
|
from shared.sx.parser import SxExpr
|
|
|
|
rights = ctx.get("rights") or {}
|
|
is_admin = getattr(rights, "admin", False) if hasattr(rights, "admin") else rights.get("admin", False)
|
|
has_access = ctx.get("has_access")
|
|
can_create = has_access("page_admin.create_market") if callable(has_access) else is_admin
|
|
csrf_token = ctx.get("csrf_token")
|
|
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
|
|
|
|
post = ctx.get("post") or {}
|
|
post_id = post.get("id")
|
|
markets = await services.market.marketplaces_for_container(g.s, "page", post_id) if post_id else []
|
|
|
|
form_html = ""
|
|
if can_create:
|
|
create_url = url_for("page_admin.create_market")
|
|
form_html = sx_call("crud-create-form",
|
|
create_url=create_url, csrf=csrf,
|
|
errors_id="market-create-errors",
|
|
list_id="markets-list",
|
|
placeholder="e.g. Suma, Craft Fair",
|
|
btn_label="Add market")
|
|
|
|
list_html = _markets_admin_list_sx(ctx, markets)
|
|
return sx_call("crud-panel",
|
|
form=SxExpr(form_html), list=SxExpr(list_html),
|
|
list_id="markets-list")
|
|
|
|
|
|
def _markets_admin_list_sx(ctx: dict, markets: list) -> str:
|
|
"""Render the markets list items."""
|
|
from quart import url_for
|
|
from shared.utils import route_prefix
|
|
from shared.sx.helpers import sx_call
|
|
csrf_token = ctx.get("csrf_token")
|
|
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
|
|
prefix = route_prefix()
|
|
|
|
if not markets:
|
|
return sx_call("empty-state",
|
|
message="No markets yet. Create one above.",
|
|
cls="text-gray-500 mt-4")
|
|
|
|
parts = []
|
|
for m in markets:
|
|
m_slug = getattr(m, "slug", "") or (m.get("slug", "") if isinstance(m, dict) else "")
|
|
m_name = getattr(m, "name", "") or (m.get("name", "") if isinstance(m, dict) else "")
|
|
post_slug = (ctx.get("post") or {}).get("slug", "")
|
|
href = prefix + f"/{post_slug}/{m_slug}/"
|
|
del_url = url_for("page_admin.delete_market", market_slug=m_slug)
|
|
csrf_hdr = f'{{"X-CSRFToken":"{csrf}"}}'
|
|
parts.append(sx_call("crud-item",
|
|
href=href, name=m_name, slug=m_slug,
|
|
del_url=del_url, csrf_hdr=csrf_hdr,
|
|
list_id="markets-list",
|
|
confirm_title="Delete market?",
|
|
confirm_text="Products will be hidden (soft delete)"))
|
|
return "".join(parts)
|
|
|
|
|
|
# ===========================================================================
|
|
# Page helpers
|
|
# ===========================================================================
|
|
|
|
def _register_market_helpers() -> None:
|
|
from shared.sx.pages import register_page_helpers
|
|
|
|
register_page_helpers("market", {
|
|
"all-markets-content": _h_all_markets_content,
|
|
"page-markets-content": _h_page_markets_content,
|
|
"page-admin-content": _h_page_admin_content,
|
|
"market-home-content": _h_market_home_content,
|
|
"market-admin-content": _h_market_admin_content,
|
|
})
|
|
|
|
|
|
async def _h_all_markets_content(**kw):
|
|
from quart import g, url_for, request
|
|
from shared.utils import route_prefix
|
|
from shared.services.registry import services
|
|
from shared.infrastructure.data_client import fetch_data
|
|
from shared.contracts.dtos import PostDTO, dto_from_dict
|
|
|
|
page = int(request.args.get("page", 1))
|
|
markets, has_more = await services.market.list_marketplaces(
|
|
g.s, page=page, per_page=20,
|
|
)
|
|
|
|
page_info = {}
|
|
if markets:
|
|
post_ids = list({m.container_id for m in markets if m.container_type == "page"})
|
|
if post_ids:
|
|
raw_posts = await fetch_data("blog", "posts-by-ids",
|
|
params={"ids": ",".join(str(i) for i in post_ids)},
|
|
required=False) or []
|
|
for raw_p in raw_posts:
|
|
p = dto_from_dict(PostDTO, raw_p)
|
|
page_info[p.id] = {"title": p.title, "slug": p.slug}
|
|
|
|
if not markets:
|
|
return _no_markets_sx()
|
|
|
|
prefix = route_prefix()
|
|
next_url = prefix + url_for("all_markets.markets_fragment", page=page + 1)
|
|
|
|
cards = _market_cards_sx(markets, page_info, page, has_more, next_url)
|
|
return _markets_grid(cards)
|
|
|
|
|
|
async def _h_page_markets_content(slug=None, **kw):
|
|
from quart import g, url_for, request
|
|
from shared.utils import route_prefix
|
|
from shared.services.registry import services
|
|
|
|
post = g.post_data["post"]
|
|
page = int(request.args.get("page", 1))
|
|
markets, has_more = await services.market.list_marketplaces(
|
|
g.s, "page", post["id"], page=page, per_page=20,
|
|
)
|
|
post_slug = post.get("slug", "")
|
|
|
|
if not markets:
|
|
return _no_markets_sx("No markets for this page")
|
|
|
|
prefix = route_prefix()
|
|
next_url = prefix + url_for("page_markets.markets_fragment", page=page + 1)
|
|
|
|
cards = _market_cards_sx(markets, {}, page, has_more, next_url,
|
|
show_page_badge=False, post_slug=post_slug)
|
|
return _markets_grid(cards)
|
|
|
|
|
|
async def _h_page_admin_content(slug=None, **kw):
|
|
from shared.sx.page import get_template_context
|
|
from shared.sx.helpers import sx_call
|
|
ctx = await get_template_context()
|
|
content = await _markets_admin_panel_sx(ctx)
|
|
return sx_call("market-admin-content-wrap", inner=content)
|
|
|
|
|
|
def _h_market_home_content(page_slug=None, market_slug=None, **kw):
|
|
from quart import g
|
|
post_data = getattr(g, "post_data", {})
|
|
post = post_data.get("post", {})
|
|
return _market_landing_content_sx(post)
|
|
|
|
|
|
def _h_market_admin_content(page_slug=None, market_slug=None, **kw):
|
|
return '"market admin"'
|