Files
rose-ash/market/bp/browse/routes.py
giles 838ec982eb Phase 7: Replace render_template() with s-expression rendering in all POST/PUT/DELETE routes
Eliminates all render_template() calls from POST/PUT/DELETE handlers across
all 7 services. Moves sexp_components.py into sexp/ packages per service.

- Blog: like toggle, snippets, cache clear, features/sumup/entry panels,
  create/delete market, WYSIWYG editor panel (render_editor_panel)
- Federation: like/unlike/boost/unboost, follow/unfollow, actor card,
  interaction buttons
- Events: ticket widget, checkin, confirm/decline/provisional, tickets
  config, posts CRUD, description edit/save, calendar/slot/ticket_type
  CRUD, payments, buy tickets, day main panel, entry page
- Market: like toggle, cart add response
- Account: newsletter toggle
- Cart: checkout error pages (3 handlers)
- Orders: checkout error page (1 handler)

Remaining render_template() calls are exclusively in GET handlers and
internal services (email templates, fragment endpoints).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:15:29 +00:00

173 lines
5.2 KiB
Python

from __future__ import annotations
from quart import (
g,
Blueprint,
abort,
render_template,
render_template_string,
make_response,
current_app,
)
from shared.config import config
from .services.nav import category_context, get_nav
from .services.blacklist.category import is_category_blocked
from .services import (
_hx_fragment_request,
_productInfo,
_vary,
_current_url_without_page,
)
from shared.browser.app.redis_cacher import cache_page
from shared.browser.app.utils.htmx import is_htmx_request
def register():
browse_bp = Blueprint("browse", __name__)
from .. import register_product
browse_bp.register_blueprint(
register_product(),
)
@browse_bp.get("/")
@cache_page(tag="browse")
async def home():
"""
Market landing page.
Uses the post data hydrated by the app-level before_request (g.post_data).
"""
p_data = getattr(g, "post_data", None) or {}
# Determine which template to use based on request type
from shared.sexp.page import get_template_context
from sexp.sexp_components import render_market_home_page, render_market_home_oob
ctx = await get_template_context()
ctx.update(p_data)
if not is_htmx_request():
html = await render_market_home_page(ctx)
else:
html = await render_market_home_oob(ctx)
return await make_response(html)
@browse_bp.get("/all/")
@cache_page(tag="browse")
async def browse_all():
"""
Browse all products across all categories.
Renders full page or just product cards (HTMX pagination fragment).
"""
market = getattr(g, "market", None)
market_id = market.id if market else None
nav = await get_nav(g.s, market_id=market_id)
ctx = {
"category_label": "All Products",
"top_slug": "all",
"sub_slug": None,
}
product_info = await _productInfo()
full_context = {**product_info, **ctx}
from shared.sexp.page import get_template_context
from sexp.sexp_components import render_browse_page, render_browse_oob, render_browse_cards
tctx = await get_template_context()
tctx.update(full_context)
if not is_htmx_request():
html = await render_browse_page(tctx)
elif product_info["page"] > 1:
tctx.update(product_info)
html = await render_browse_cards(tctx)
else:
html = await render_browse_oob(tctx)
resp = await make_response(html)
resp.headers["Hx-Push-Url"] = _current_url_without_page()
return _vary(resp)
@browse_bp.get("/<top_slug>/")
@cache_page(tag="browse")
async def browse_top(top_slug: str):
"""
Browse by top-level category (e.g. /fruit).
404 if category not in allowed list or is blocked.
"""
REVERSE_CATEGORY = {v: k for k, v in config()["categories"]["allow"].items()}
if top_slug not in REVERSE_CATEGORY:
abort(404)
if is_category_blocked(top_slug):
abort(404)
market = getattr(g, "market", None)
market_id = market.id if market else None
nav = await get_nav(g.s, market_id=market_id)
ctx = category_context(top_slug, None, nav)
product_info = await _productInfo(top_slug)
full_context = {**product_info, **ctx}
from shared.sexp.page import get_template_context
from sexp.sexp_components import render_browse_page, render_browse_oob, render_browse_cards
tctx = await get_template_context()
tctx.update(full_context)
if not is_htmx_request():
html = await render_browse_page(tctx)
elif product_info["page"] > 1:
tctx.update(product_info)
html = await render_browse_cards(tctx)
else:
html = await render_browse_oob(tctx)
resp = await make_response(html)
resp.headers["Hx-Push-Url"] = _current_url_without_page()
return _vary(resp)
@browse_bp.get("/<top_slug>/<sub_slug>/")
@cache_page(tag="browse")
async def browse_sub(top_slug: str, sub_slug: str):
"""
Browse by subcategory (e.g. /fruit/citrus).
404 if blocked or unknown.
"""
REVERSE_CATEGORY = {v: k for k, v in config()["categories"]["allow"].items()}
if top_slug not in REVERSE_CATEGORY:
abort(404)
if is_category_blocked(top_slug, sub_slug):
abort(404)
market = getattr(g, "market", None)
market_id = market.id if market else None
nav = await get_nav(g.s, market_id=market_id)
ctx = category_context(top_slug, sub_slug, nav)
product_info = await _productInfo(top_slug, sub_slug)
full_context = {**product_info, **ctx}
from shared.sexp.page import get_template_context
from sexp.sexp_components import render_browse_page, render_browse_oob, render_browse_cards
tctx = await get_template_context()
tctx.update(full_context)
if not is_htmx_request():
html = await render_browse_page(tctx)
elif product_info["page"] > 1:
tctx.update(product_info)
html = await render_browse_cards(tctx)
else:
html = await render_browse_oob(tctx)
resp = await make_response(html)
resp.headers["Hx-Push-Url"] = _current_url_without_page()
return _vary(resp)
return browse_bp