From f2910ad7674df78651040b4c6f13fe55fcc200b0 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 4 Mar 2026 09:20:47 +0000 Subject: [PATCH] Replace fragment render functions with .sx defcomps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Snippets list: render_snippets_list → render_to_sx("blog-snippets-content") - Menu items list: render_menu_items_list → _render_menu_items_list helper - Features panel: render_features_panel → render_to_sx("blog-features-panel-content") - Markets panel: render_markets_panel → render_to_sx("blog-markets-panel-content") Co-Authored-By: Claude Opus 4.6 --- blog/bp/menu_items/routes.py | 36 +++++++++++++------ blog/bp/post/admin/routes.py | 70 ++++++++++++++++++++++++------------ blog/bp/snippets/routes.py | 30 +++++----------- blog/sx/settings.sx | 37 +++++++++++++++++++ 4 files changed, 119 insertions(+), 54 deletions(-) diff --git a/blog/bp/menu_items/routes.py b/blog/bp/menu_items/routes.py index 166a81c..19be754 100644 --- a/blog/bp/menu_items/routes.py +++ b/blog/bp/menu_items/routes.py @@ -1,6 +1,6 @@ from __future__ import annotations -from quart import Blueprint, make_response, request, jsonify, g +from quart import Blueprint, make_response, request, jsonify, g, url_for from shared.browser.app.authz import require_admin from .services.menu_items import ( @@ -12,7 +12,27 @@ from .services.menu_items import ( search_pages, MenuItemError, ) -from shared.sx.helpers import sx_response +from shared.sx.helpers import sx_response, render_to_sx +from shared.browser.app.csrf import generate_csrf_token + + +async def _render_menu_items_list(menu_items): + """Serialize ORM menu items and render via .sx defcomp.""" + csrf = generate_csrf_token() + items = [] + for item in menu_items: + items.append({ + "feature_image": getattr(item, "feature_image", None), + "label": getattr(item, "label", "") or "", + "url": getattr(item, "url", "") or "", + "sort_order": getattr(item, "position", 0) or 0, + "edit_url": url_for("menu_items.edit_menu_item", item_id=item.id), + "delete_url": url_for("menu_items.delete_menu_item_route", item_id=item.id), + }) + new_url = url_for("menu_items.new_menu_item") + return await render_to_sx("blog-menu-items-content", + menu_items=items, new_url=new_url, csrf=csrf) + def register(): bp = Blueprint("menu_items", __name__, url_prefix='/settings/menu_items') @@ -50,8 +70,7 @@ def register(): # Get updated list and nav OOB menu_items = await get_all_menu_items(g.s) - from sx.sx_components import render_menu_items_list - html = await render_menu_items_list(menu_items) + html = await _render_menu_items_list(menu_items) nav_oob = await get_menu_items_nav_oob_async(menu_items) return sx_response(html + nav_oob) @@ -90,8 +109,7 @@ def register(): # Get updated list and nav OOB menu_items = await get_all_menu_items(g.s) - from sx.sx_components import render_menu_items_list - html = await render_menu_items_list(menu_items) + html = await _render_menu_items_list(menu_items) nav_oob = await get_menu_items_nav_oob_async(menu_items) return sx_response(html + nav_oob) @@ -111,8 +129,7 @@ def register(): # Get updated list and nav OOB menu_items = await get_all_menu_items(g.s) - from sx.sx_components import render_menu_items_list - html = await render_menu_items_list(menu_items) + html = await _render_menu_items_list(menu_items) nav_oob = await get_menu_items_nav_oob_async(menu_items) return sx_response(html + nav_oob) @@ -152,8 +169,7 @@ def register(): # Get updated list and nav OOB menu_items = await get_all_menu_items(g.s) - from sx.sx_components import render_menu_items_list - html = await render_menu_items_list(menu_items) + html = await _render_menu_items_list(menu_items) nav_oob = await get_menu_items_nav_oob_async(menu_items) return sx_response(html + nav_oob) diff --git a/blog/bp/post/admin/routes.py b/blog/bp/post/admin/routes.py index 4479cd1..a779e8a 100644 --- a/blog/bp/post/admin/routes.py +++ b/blog/bp/post/admin/routes.py @@ -10,7 +10,7 @@ from quart import ( url_for, ) from shared.browser.app.authz import require_admin, require_post_author -from shared.sx.helpers import sx_response +from shared.sx.helpers import sx_response, render_to_sx from shared.utils import host_url def _post_to_edit_dict(post) -> dict: @@ -51,6 +51,36 @@ def _post_to_edit_dict(post) -> dict: return d +async def _render_features(features, post, result): + """Render features panel via .sx defcomp.""" + slug = post.get("slug", "") + return await render_to_sx("blog-features-panel-content", + features_url=host_url(url_for("blog.post.admin.update_features", slug=slug)), + calendar_checked=bool(features.get("calendar")), + market_checked=bool(features.get("market")), + show_sumup=bool(features.get("calendar") or features.get("market")), + sumup_url=host_url(url_for("blog.post.admin.update_sumup", slug=slug)), + merchant_code=result.get("sumup_merchant_code") or "", + placeholder="\u2022" * 8 if result.get("sumup_configured") else "sup_sk_...", + sumup_configured=result.get("sumup_configured", False), + checkout_prefix=result.get("sumup_checkout_prefix") or "", + ) + + +def _serialize_markets(markets, slug): + """Serialize ORM/DTO market objects to dicts for .sx defcomp.""" + result = [] + for m in markets: + m_name = getattr(m, "name", "") if hasattr(m, "name") else m.get("name", "") + m_slug = getattr(m, "slug", "") if hasattr(m, "slug") else m.get("slug", "") + result.append({ + "name": m_name, "slug": m_slug, + "delete_url": host_url(url_for("blog.post.admin.delete_market", + slug=slug, market_slug=m_slug)), + }) + return result + + def register(): bp = Blueprint("admin", __name__, url_prefix='/admin') @@ -88,14 +118,7 @@ def register(): }) features = result.get("features", {}) - - from sx.sx_components import render_features_panel - html = await render_features_panel( - features, post, - sumup_configured=result.get("sumup_configured", False), - sumup_merchant_code=result.get("sumup_merchant_code") or "", - sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "", - ) + html = await _render_features(features, post, result) return sx_response(html) @bp.put("/admin/sumup/") @@ -128,13 +151,7 @@ def register(): result = await call_action("blog", "update-page-config", payload=payload) features = result.get("features", {}) - from sx.sx_components import render_features_panel - html = await render_features_panel( - features, post, - sumup_configured=result.get("sumup_configured", False), - sumup_merchant_code=result.get("sumup_merchant_code") or "", - sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "", - ) + html = await _render_features(features, post, result) return sx_response(html) @bp.get("/entries/calendar//") @@ -435,8 +452,11 @@ def register(): page_markets = await _fetch_page_markets(post_id) - from sx.sx_components import render_markets_panel - return sx_response(await render_markets_panel(page_markets, post)) + slug = post.get("slug", "") + create_url = host_url(url_for("blog.post.admin.create_market", slug=slug)) + html = await render_to_sx("blog-markets-panel-content", + markets=_serialize_markets(page_markets, slug), create_url=create_url) + return sx_response(html) @bp.post("/markets/new/") @require_admin @@ -461,8 +481,11 @@ def register(): # Return updated markets list page_markets = await _fetch_page_markets(post_id) - from sx.sx_components import render_markets_panel - return sx_response(await render_markets_panel(page_markets, post)) + slug = post.get("slug", "") + create_url = host_url(url_for("blog.post.admin.create_market", slug=slug)) + html = await render_to_sx("blog-markets-panel-content", + markets=_serialize_markets(page_markets, slug), create_url=create_url) + return sx_response(html) @bp.delete("/markets//") @require_admin @@ -481,7 +504,10 @@ def register(): # Return updated markets list page_markets = await _fetch_page_markets(post_id) - from sx.sx_components import render_markets_panel - return sx_response(await render_markets_panel(page_markets, post)) + slug = post.get("slug", "") + create_url = host_url(url_for("blog.post.admin.create_market", slug=slug)) + html = await render_to_sx("blog-markets-panel-content", + markets=_serialize_markets(page_markets, slug), create_url=create_url) + return sx_response(html) return bp diff --git a/blog/bp/snippets/routes.py b/blog/bp/snippets/routes.py index 6e47d59..8f79406 100644 --- a/blog/bp/snippets/routes.py +++ b/blog/bp/snippets/routes.py @@ -1,30 +1,20 @@ from __future__ import annotations from quart import Blueprint, request, g, abort -from sqlalchemy import select, or_ from shared.browser.app.authz import require_login -from shared.sx.helpers import sx_response +from shared.sx.helpers import sx_response, render_to_sx from models import Snippet VALID_VISIBILITY = frozenset({"private", "shared", "admin"}) -async def _visible_snippets(session): - """Return snippets visible to the current user (own + shared + admin-if-admin).""" - uid = g.user.id - is_admin = g.rights.get("admin") - - filters = [Snippet.user_id == uid, Snippet.visibility == "shared"] - if is_admin: - filters.append(Snippet.visibility == "admin") - - rows = (await session.execute( - select(Snippet).where(or_(*filters)).order_by(Snippet.name) - )).scalars().all() - - return rows +async def _render_snippets(): + """Render snippets list via service data + .sx defcomp.""" + from shared.services.registry import services + data = await services.get("blog_page").snippets_data(g.s) + return await render_to_sx("blog-snippets-content", **data) def register(): @@ -45,9 +35,7 @@ def register(): await g.s.delete(snippet) await g.s.flush() - snippets = await _visible_snippets(g.s) - from sx.sx_components import render_snippets_list - return sx_response(await render_snippets_list(snippets, is_admin)) + return sx_response(await _render_snippets()) @bp.patch("//visibility/") @require_login @@ -69,8 +57,6 @@ def register(): snippet.visibility = visibility await g.s.flush() - snippets = await _visible_snippets(g.s) - from sx.sx_components import render_snippets_list - return sx_response(await render_snippets_list(snippets, True)) + return sx_response(await _render_snippets()) return bp diff --git a/blog/sx/settings.sx b/blog/sx/settings.sx index 7b54fe3..41358f9 100644 --- a/blog/sx/settings.sx +++ b/blog/sx/settings.sx @@ -54,6 +54,43 @@ (button :type "submit" :class "bg-stone-800 text-white px-4 py-1.5 rounded text-sm hover:bg-stone-700" "Create")))) +;; --------------------------------------------------------------------------- +;; Data-driven composition defcomps — replace Python render_* functions +;; --------------------------------------------------------------------------- + +;; Features panel composition — replaces render_features_panel +(defcomp ~blog-features-panel-content (&key features-url calendar-checked market-checked + show-sumup sumup-url merchant-code placeholder + sumup-configured checkout-prefix) + (~blog-features-panel + :form (~blog-features-form + :features-url features-url + :calendar-checked calendar-checked + :market-checked market-checked + :hs-trigger "on change trigger submit on closest
") + :sumup (when show-sumup + (~blog-sumup-form + :sumup-url sumup-url + :merchant-code merchant-code + :placeholder placeholder + :sumup-configured sumup-configured + :checkout-prefix checkout-prefix)))) + +;; Markets panel composition — replaces render_markets_panel +(defcomp ~blog-markets-panel-content (&key markets create-url) + (~blog-markets-panel + :list (if (empty? (or markets (list))) + (~blog-markets-empty) + (~blog-markets-list + :items (map (lambda (m) + (~blog-market-item + :name (get m "name") + :slug (get m "slug") + :delete-url (get m "delete_url") + :confirm-text (str "Delete market '" (get m "name") "'?"))) + (or markets (list))))) + :create-url create-url)) + ;; Associated entries (defcomp ~blog-entry-image (&key src title)