Add market admin CRUD: list, create, and delete marketplaces

Replaces placeholder "Market admin" text with a functional admin panel
that lists marketplaces for a page and supports create/delete via sx,
mirroring the events calendar admin pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 19:16:39 +00:00
parent 01a67029f0
commit 755313bd29
3 changed files with 164 additions and 4 deletions

View File

@@ -1516,6 +1516,63 @@ def _market_admin_header_sx(ctx: dict, *, oob: bool = False, selected: str = "")
# Page admin (/<slug>/admin/) — post-level admin for markets
# ---------------------------------------------------------------------------
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
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("market-admin-create-form",
create_url=create_url, csrf=csrf)
list_html = _markets_admin_list_sx(ctx, markets)
return sx_call("market-admin-panel",
form=SxExpr(form_html), list=SxExpr(list_html))
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
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("market-admin-empty")
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("market-admin-item",
href=href, name=m_name, slug=m_slug,
del_url=del_url, csrf_hdr=csrf_hdr))
return "".join(parts)
async def render_markets_admin_list_panel(ctx: dict) -> str:
"""Render the markets admin panel HTML for POST/DELETE response."""
return await _markets_admin_panel_sx(ctx)
async def render_page_admin_page(ctx: dict) -> str:
"""Full page: page-level market admin."""
slug = (ctx.get("post") or {}).get("slug", "")
@@ -1523,7 +1580,8 @@ async def render_page_admin_page(ctx: dict) -> str:
hdr = root_header_sx(ctx)
child = "(<> " + _post_header_sx(ctx) + " " + admin_hdr + ")"
hdr = "(<> " + hdr + " " + header_child_sx(child) + ")"
content = '(div :id "main-panel" (div :class "p-4 text-stone-500" "Market admin"))'
content = await _markets_admin_panel_sx(ctx)
content = '(div :id "main-panel" ' + content + ')'
return full_page_sx(ctx, header_rows=hdr, content=content)
@@ -1533,7 +1591,8 @@ async def render_page_admin_oob(ctx: dict) -> str:
oobs = "(<> " + post_admin_header_sx(ctx, slug, oob=True, selected="markets") + " "
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child") + ")"
content = '(div :id "main-panel" (div :class "p-4 text-stone-500" "Market admin"))'
content = await _markets_admin_panel_sx(ctx)
content = '(div :id "main-panel" ' + content + ')'
return oob_page_sx(oobs=oobs, content=content)