Move market composition from Python to .sx defcomps (Phase 8)
Convert 5 market page helpers from returning sx_call() strings to returning data dicts. Defpages now use :data + :content pattern. Admin panel uses inline map/fn for CRUD item composition. Removed market-admin-content helper (placeholder inlined in defpage). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,98 +1,33 @@
|
||||
"""Page helpers for market defpage system."""
|
||||
"""Market page helpers — data-only.
|
||||
|
||||
All helpers return data values (dicts, lists) — no sx_call().
|
||||
Markup composition lives entirely in .sx defpage and .sx defcomp files.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from .cards import (
|
||||
_market_cards_sx, _markets_grid, _no_markets_sx,
|
||||
_market_landing_content_sx,
|
||||
)
|
||||
from .cards import _market_card_data
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Page admin panel (used by _h_page_admin_content)
|
||||
# Registration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
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,
|
||||
"all-markets-data": _h_all_markets_data,
|
||||
"page-markets-data": _h_page_markets_data,
|
||||
"page-admin-data": _h_page_admin_data,
|
||||
"market-home-data": _h_market_home_data,
|
||||
})
|
||||
|
||||
|
||||
async def _h_all_markets_content(**kw):
|
||||
# ---------------------------------------------------------------------------
|
||||
# All markets (global view)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _h_all_markets_data(**kw) -> dict:
|
||||
from quart import g, url_for, request
|
||||
from shared.utils import route_prefix
|
||||
from shared.services.registry import services
|
||||
@@ -116,16 +51,26 @@ async def _h_all_markets_content(**kw):
|
||||
page_info[p.id] = {"title": p.title, "slug": p.slug}
|
||||
|
||||
if not markets:
|
||||
return _no_markets_sx()
|
||||
return {"no-markets": True}
|
||||
|
||||
prefix = route_prefix()
|
||||
next_url = prefix + url_for("all_markets.markets_fragment", page=page + 1)
|
||||
market_data = [_market_card_data(m, page_info) for m in markets]
|
||||
|
||||
cards = _market_cards_sx(markets, page_info, page, has_more, next_url)
|
||||
return _markets_grid(cards)
|
||||
return {
|
||||
"no-markets": False,
|
||||
"market-data": market_data,
|
||||
"market-page": page,
|
||||
"has-more": has_more,
|
||||
"next-url": next_url,
|
||||
}
|
||||
|
||||
|
||||
async def _h_page_markets_content(slug=None, **kw):
|
||||
# ---------------------------------------------------------------------------
|
||||
# Page markets (markets for a single page)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _h_page_markets_data(slug=None, **kw) -> dict:
|
||||
from quart import g, url_for, request
|
||||
from shared.utils import route_prefix
|
||||
from shared.services.registry import services
|
||||
@@ -138,30 +83,76 @@ async def _h_page_markets_content(slug=None, **kw):
|
||||
post_slug = post.get("slug", "")
|
||||
|
||||
if not markets:
|
||||
return _no_markets_sx("No markets for this page")
|
||||
return {"no-markets": True}
|
||||
|
||||
prefix = route_prefix()
|
||||
next_url = prefix + url_for("page_markets.markets_fragment", page=page + 1)
|
||||
market_data = [_market_card_data(m, {}, show_page_badge=False,
|
||||
post_slug=post_slug) for m in markets]
|
||||
|
||||
cards = _market_cards_sx(markets, {}, page, has_more, next_url,
|
||||
show_page_badge=False, post_slug=post_slug)
|
||||
return _markets_grid(cards)
|
||||
return {
|
||||
"no-markets": False,
|
||||
"market-data": market_data,
|
||||
"market-page": page,
|
||||
"has-more": has_more,
|
||||
"next-url": next_url,
|
||||
}
|
||||
|
||||
|
||||
async def _h_page_admin_content(slug=None, **kw):
|
||||
# ---------------------------------------------------------------------------
|
||||
# Page admin (CRUD panel for markets under a page)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _h_page_admin_data(slug=None, **kw) -> dict:
|
||||
from quart import g, url_for
|
||||
from shared.sx.page import get_template_context
|
||||
from shared.sx.helpers import sx_call
|
||||
from shared.services.registry import services
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
from shared.utils import route_prefix
|
||||
|
||||
ctx = await get_template_context()
|
||||
content = await _markets_admin_panel_sx(ctx)
|
||||
return sx_call("market-admin-content-wrap", inner=content)
|
||||
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 = generate_csrf_token()
|
||||
|
||||
post = ctx.get("post") or {}
|
||||
post_id = post.get("id")
|
||||
post_slug = post.get("slug", "")
|
||||
markets_raw = await services.market.marketplaces_for_container(g.s, "page", post_id) if post_id else []
|
||||
|
||||
prefix = route_prefix()
|
||||
markets = []
|
||||
for m in markets_raw:
|
||||
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 "")
|
||||
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}"}}'
|
||||
markets.append({
|
||||
"href": href, "name": m_name, "slug": m_slug,
|
||||
"del-url": del_url, "csrf-hdr": csrf_hdr,
|
||||
})
|
||||
|
||||
return {
|
||||
"can-create": can_create,
|
||||
"create-url": url_for("page_admin.create_market") if can_create else None,
|
||||
"csrf": csrf,
|
||||
"admin-markets": markets,
|
||||
}
|
||||
|
||||
|
||||
def _h_market_home_content(page_slug=None, market_slug=None, **kw):
|
||||
# ---------------------------------------------------------------------------
|
||||
# Market landing page
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _h_market_home_data(page_slug=None, market_slug=None, **kw) -> dict:
|
||||
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"'
|
||||
return {
|
||||
"excerpt": post.get("custom_excerpt") or None,
|
||||
"feature-image": post.get("feature_image") or None,
|
||||
"html": post.get("html") or None,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
;; Market app defpage declarations.
|
||||
;; All helpers return data dicts — markup composition in SX.
|
||||
;;
|
||||
;; all-markets-index: / — global view across all pages
|
||||
;; page-markets-index: /<slug>/ — markets for a single page
|
||||
@@ -10,28 +11,64 @@
|
||||
:path "/"
|
||||
:auth :public
|
||||
:layout :root
|
||||
:content (all-markets-content))
|
||||
:data (all-markets-data)
|
||||
:content (if no-markets
|
||||
(~empty-state :icon "fa fa-store" :message "No markets available"
|
||||
:cls "px-3 py-12 text-center text-stone-400")
|
||||
(~market-markets-grid
|
||||
:cards (~market-cards-content
|
||||
:markets market-data :page market-page
|
||||
:has-more has-more :next-url next-url))))
|
||||
|
||||
(defpage page-markets-index
|
||||
:path "/<slug>/"
|
||||
:auth :public
|
||||
:layout :post
|
||||
:content (page-markets-content))
|
||||
:data (page-markets-data)
|
||||
:content (if no-markets
|
||||
(~empty-state :message "No markets for this page"
|
||||
:cls "px-3 py-12 text-center text-stone-400")
|
||||
(~market-markets-grid
|
||||
:cards (~market-cards-content
|
||||
:markets market-data :page market-page
|
||||
:has-more has-more :next-url next-url))))
|
||||
|
||||
(defpage page-admin
|
||||
:path "/<slug>/admin/"
|
||||
:auth :admin
|
||||
:layout (:post-admin :selected "markets")
|
||||
:content (page-admin-content))
|
||||
:data (page-admin-data)
|
||||
:content (~market-admin-content-wrap
|
||||
:inner (~crud-panel
|
||||
:list-id "markets-list"
|
||||
:form (when can-create
|
||||
(~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 (if admin-markets
|
||||
(<> (map (fn (m)
|
||||
(~crud-item
|
||||
:href (get m "href") :name (get m "name") :slug (get m "slug")
|
||||
:del-url (get m "del-url") :csrf-hdr (get m "csrf-hdr")
|
||||
:list-id "markets-list"
|
||||
:confirm-title "Delete market?"
|
||||
:confirm-text "Products will be hidden (soft delete)"))
|
||||
admin-markets))
|
||||
(~empty-state
|
||||
:message "No markets yet. Create one above."
|
||||
:cls "text-gray-500 mt-4")))))
|
||||
|
||||
(defpage market-home
|
||||
:path "/<page_slug>/<market_slug>/"
|
||||
:auth :public
|
||||
:layout :market
|
||||
:content (market-home-content))
|
||||
:data (market-home-data)
|
||||
:content (~market-landing-from-data
|
||||
:excerpt excerpt :feature-image feature-image :html html))
|
||||
|
||||
(defpage market-admin
|
||||
:path "/<page_slug>/<market_slug>/admin/"
|
||||
:auth :admin
|
||||
:layout (:market-admin :selected "markets")
|
||||
:content (market-admin-content))
|
||||
:content "market admin")
|
||||
|
||||
Reference in New Issue
Block a user