Files
rose-ash/shared/infrastructure/context.py
giles 6c44a5f3d0 Add app label to root header and auto-reload sexp templates in dev
Show current subdomain name (blog, cart, events, etc.) next to the site
title in the root header row. Remove the redundant second "cart" menu row
from cart overview and checkout error pages.

Add dev-mode hot-reload for sexp templates: track file mtimes and re-read
changed files per-request when RELOAD=true, so .sexp edits are picked up
without restarting services.

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

106 lines
3.5 KiB
Python

"""
Base template context shared by all apps.
This module no longer imports cart or menu_items services directly.
Each app provides its own context_fn that calls this base and adds
app-specific variables (cart data, menu_items, etc.).
"""
from __future__ import annotations
from datetime import datetime
from quart import request, g, current_app
from shared.config import config
from shared.utils import host_url
from shared.browser.app.utils import current_route_relative_path
def _qs_filter_fn():
"""Build a qs_filter(dict) wrapper for sexp components, or None.
Sexp components call ``qs_fn({"page": 2})``, ``qs_fn({"sort": "az"})``,
``qs_fn({"labels": ["organic", "local"]})``, etc.
Simple keys (page, sort, search, liked, clear_filters) are forwarded
to ``makeqs(**kwargs)``. List-valued keys (labels, stickers, brands)
represent *replacement* sets, so we rebuild the querystring from the
current base with those overridden.
"""
factory = getattr(g, "makeqs_factory", None)
if not factory:
return None
makeqs = factory()
def _qs(d: dict) -> str:
from shared.browser.app.filters.qs_base import build_qs
# Collect list-valued overrides
list_overrides = {}
for plural, singular in (("labels", "label"), ("stickers", "sticker"), ("brands", "brand")):
if plural in d:
list_overrides[singular] = list(d[plural] or [])
simple = {k: v for k, v in d.items()
if k in ("page", "sort", "search", "liked", "clear_filters")}
if not list_overrides:
return makeqs(**simple)
# For list overrides: get the base qs, parse out the overridden keys,
# then rebuild with the new values.
base_qs = makeqs(**simple)
from urllib.parse import parse_qsl, urlencode
params = [(k, v) for k, v in parse_qsl(base_qs.lstrip("?"))
if k not in list_overrides]
for singular, vals in list_overrides.items():
for v in vals:
params.append((singular, v))
return ("?" + urlencode(params)) if params else ""
return _qs
async def base_context() -> dict:
"""
Common template variables available in every app.
Does NOT include cart, calendar_cart_entries, total, calendar_total,
or menu_items — those are added by each app's context_fn.
"""
is_htmx = request.headers.get("HX-Request") == "true"
search = request.headers.get("X-Search", "")
zap_filter = is_htmx and search == ""
def base_url():
return host_url()
hx_select = "#main-panel"
hx_select_search = (
hx_select
+ ", #search-mobile, #search-count-mobile, #search-desktop, #search-count-desktop, #menu-items-nav-wrapper"
)
return {
"is_htmx": is_htmx,
"request": request,
"now": datetime.now(),
"current_local_href": current_route_relative_path(),
"config": config(),
"asset_url": current_app.jinja_env.globals.get("asset_url", lambda p: ""),
"sort_options": [
("az", "A\u2013Z", "order/a-z.svg"),
("za", "Z\u2013A", "order/z-a.svg"),
("price-asc", "\u00a3 low\u2192high", "order/l-h.svg"),
("price-desc", "\u00a3 high\u2192low", "order/h-l.svg"),
],
"zap_filter": zap_filter,
"qs_filter": _qs_filter_fn(),
"print": print,
"base_url": base_url,
"app_label": current_app.name,
"base_title": config()["title"],
"hx_select": hx_select,
"hx_select_search": hx_select_search,
}