Fix 500 errors and double-slash URLs found during sexp rendering testing

- events: fix ImportError for events_url (was importing from shared.utils
  instead of shared.infrastructure.urls)
- blog: add missing ~mobile-filter sexp component (details/summary panel)
- shared: fix double-slash URLs in ~auth-menu, ~cart-mini, ~header-row
  by removing redundant "/" concatenation on URLs that already have trailing slash
- blog: fix ghost_sync select UnboundLocalError caused by redundant local
  import shadowing module-level import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 01:40:02 +00:00
parent 838ec982eb
commit b52ef719bf
4 changed files with 61 additions and 20 deletions

View File

@@ -251,7 +251,6 @@ async def _upsert_post(sess: AsyncSession, gp: Dict[str, Any], author_map: Dict[
# Auto-create PageConfig for pages (blog owns this table — direct DB,
# not via HTTP, since this may run during startup before the server is ready)
if obj.is_page:
from sqlalchemy import select
from shared.models.page_config import PageConfig
existing = (await sess.execute(
select(PageConfig).where(

View File

@@ -1530,7 +1530,8 @@ async def render_all_events_page(ctx: dict, entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""Full page: all events listing."""
from quart import url_for
from shared.utils import route_prefix, events_url
from shared.utils import route_prefix
from shared.infrastructure.urls import events_url
prefix = route_prefix()
view_param = f"&view={view}" if view != "list" else ""
@@ -1549,7 +1550,8 @@ async def render_all_events_oob(ctx: dict, entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""OOB response: all events listing (htmx nav)."""
from quart import url_for
from shared.utils import route_prefix, events_url
from shared.utils import route_prefix
from shared.infrastructure.urls import events_url
prefix = route_prefix()
ticket_url = url_for("all_events.adjust_ticket")
@@ -1566,7 +1568,8 @@ async def render_all_events_cards(entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""Pagination fragment: all events cards only."""
from quart import url_for
from shared.utils import route_prefix, events_url
from shared.utils import route_prefix
from shared.infrastructure.urls import events_url
prefix = route_prefix()
ticket_url = url_for("all_events.adjust_ticket")
@@ -1586,7 +1589,8 @@ async def render_page_summary_page(ctx: dict, entries, has_more, pending_tickets
page_info, page, view) -> str:
"""Full page: page-scoped events listing."""
from quart import url_for
from shared.utils import route_prefix, events_url
from shared.utils import route_prefix
from shared.infrastructure.urls import events_url
prefix = route_prefix()
post = ctx.get("post") or {}
@@ -1611,7 +1615,8 @@ async def render_page_summary_oob(ctx: dict, entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""OOB response: page-scoped events (htmx nav)."""
from quart import url_for
from shared.utils import route_prefix, events_url
from shared.utils import route_prefix
from shared.infrastructure.urls import events_url
prefix = route_prefix()
post = ctx.get("post") or {}
@@ -1632,7 +1637,8 @@ async def render_page_summary_cards(entries, has_more, pending_tickets,
page_info, page, view, post) -> str:
"""Pagination fragment: page-scoped events cards only."""
from quart import url_for
from shared.utils import route_prefix, events_url
from shared.utils import route_prefix
from shared.infrastructure.urls import events_url
prefix = route_prefix()
ticket_url = url_for("page_summary.adjust_ticket")
@@ -2634,7 +2640,7 @@ def render_day_entries_nav_oob(confirmed_entries, calendar, day_date) -> str:
def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
"""Render OOB nav for associated entries and calendars of a post."""
from quart import g
from shared.utils import events_url
from shared.infrastructure.urls import events_url
styles = getattr(g, "styles", None) or {}
nav_btn = getattr(styles, "nav_button_less_pad", "") if hasattr(styles, "nav_button_less_pad") else styles.get("nav_button_less_pad", "")

View File

@@ -34,6 +34,7 @@ def load_shared_components() -> None:
register_components(_STATUS_PILL)
register_components(_SEARCH_MOBILE)
register_components(_SEARCH_DESKTOP)
register_components(_MOBILE_FILTER)
register_components(_ORDER_SUMMARY_CARD)
@@ -88,11 +89,11 @@ _CART_MINI = '''
:hx-swap-oob oob
(if (= cart-count 0)
(div :class "h-12 w-12 rounded-full overflow-hidden border border-stone-300 flex-shrink-0"
(a :href (str blog-url "/")
(a :href blog-url
:class "h-full w-full font-bold text-5xl flex-shrink-0 flex flex-row items-center gap-1"
(img :src (str blog-url "/static/img/logo.jpg")
(img :src (str blog-url "static/img/logo.jpg")
:class "h-full w-full rounded-full object-cover border border-stone-300 flex-shrink-0")))
(a :href (str cart-url "/")
(a :href cart-url
:class "relative inline-flex items-center justify-center text-stone-700 hover:text-emerald-700"
(i :class "fa fa-shopping-cart text-5xl" :aria-hidden "true")
(span :class "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-full bg-emerald-600 text-white text-sm w-5 h-5"
@@ -115,22 +116,22 @@ _AUTH_MENU = '''
(<>
(span :id "auth-menu-desktop" :class "hidden md:inline-flex"
(if user-email
(a :href (str account-url "/")
(a :href account-url
:class "justify-center cursor-pointer flex flex-row items-center p-3 gap-2 rounded bg-stone-200 text-black"
:data-close-details true
(i :class "fa-solid fa-user")
(span user-email))
(a :href (str account-url "/")
(a :href account-url
:class "justify-center cursor-pointer flex flex-row items-center p-3 gap-2 rounded bg-stone-200 text-black"
:data-close-details true
(i :class "fa-solid fa-key")
(span "sign in or register"))))
(span :id "auth-menu-mobile" :class "block md:hidden text-md font-bold"
(if user-email
(a :href (str account-url "/") :data-close-details true
(a :href account-url :data-close-details true
(i :class "fa-solid fa-user")
(span user-email))
(a :href (str account-url "/")
(a :href account-url
(i :class "fa-solid fa-key")
(span "sign in or register"))))))
'''
@@ -487,7 +488,7 @@ _HEADER_ROW = '''
(div :class "w-full flex flex-row items-top"
(when cart-mini-html (raw! cart-mini-html))
(div :class "font-bold text-5xl flex-1"
(a :href (str (or blog-url "") "/") :class "flex justify-center md:justify-start"
(a :href (or blog-url "/") :class "flex justify-center md:justify-start"
(h1 (or site-title ""))))
(nav :class "hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0"
(when nav-tree-html (raw! nav-tree-html))
@@ -716,6 +717,41 @@ _SEARCH_DESKTOP = '''
'''
# ---------------------------------------------------------------------------
# ~mobile-filter — mobile filter details/summary panel
# ---------------------------------------------------------------------------
# Replaces: blog/templates/_types/blog/mobile/_filter/summary.html
# + macros/layout.html details/filter_summary
#
# Usage:
# sexp('(~mobile-filter :filter-summary-html fsh :action-buttons-html abh
# :filter-details-html fdh)',
# fsh="...", abh="...", fdh="...")
# ---------------------------------------------------------------------------
_MOBILE_FILTER = '''
(defcomp ~mobile-filter (&key filter-summary-html action-buttons-html filter-details-html)
(details :class "group/filter p-2 md:hidden" :data-toggle-group "mobile-panels"
(summary :class "bg-white/90"
(div :class "flex flex-row items-start"
(div
(div :class "md:hidden mx-2 bg-stone-200 rounded"
(span :class "flex items-center justify-center text-stone-600 text-lg h-12 w-12 transition-transform group-open/filter:hidden self-start"
(i :class "fa-solid fa-filter"))
(span
(svg :aria-hidden "true" :viewBox "0 0 24 24"
:class "w-12 h-12 rotate-180 transition-transform group-open/filter:block hidden self-start"
(path :d "M6 9l6 6 6-6" :fill "currentColor")))))
(div :id "filter-summary-mobile"
:class "flex-1 md:hidden grid grid-cols-12 items-center gap-3"
(div :class "flex flex-col items-start gap-2"
(raw! filter-summary-html)))))
(raw! (or action-buttons-html ""))
(div :id "filter-details-mobile" :style "display:contents"
(raw! (or filter-details-html "")))))
'''
# ---------------------------------------------------------------------------
# ~order-summary-card — reusable order summary card
# ---------------------------------------------------------------------------

View File

@@ -21,7 +21,7 @@ class TestCartMini:
def test_empty_cart_shows_logo(self):
html = sexp(
'(~cart-mini :cart-count cart-count :blog-url blog-url :cart-url cart-url)',
**{"cart-count": 0, "blog-url": "https://blog.example.com", "cart-url": "https://cart.example.com"},
**{"cart-count": 0, "blog-url": "https://blog.example.com/", "cart-url": "https://cart.example.com/"},
)
assert 'id="cart-mini"' in html
assert "logo.jpg" in html
@@ -31,7 +31,7 @@ class TestCartMini:
def test_nonempty_cart_shows_badge(self):
html = sexp(
'(~cart-mini :cart-count cart-count :blog-url blog-url :cart-url cart-url)',
**{"cart-count": 3, "blog-url": "https://blog.example.com", "cart-url": "https://cart.example.com"},
**{"cart-count": 3, "blog-url": "https://blog.example.com/", "cart-url": "https://cart.example.com/"},
)
assert 'id="cart-mini"' in html
assert "fa-shopping-cart" in html
@@ -60,7 +60,7 @@ class TestAuthMenu:
def test_logged_in(self):
html = sexp(
'(~auth-menu :user-email user-email :account-url account-url)',
**{"user-email": "alice@example.com", "account-url": "https://account.example.com"},
**{"user-email": "alice@example.com", "account-url": "https://account.example.com/"},
)
assert 'id="auth-menu-desktop"' in html
assert 'id="auth-menu-mobile"' in html
@@ -71,7 +71,7 @@ class TestAuthMenu:
def test_logged_out(self):
html = sexp(
'(~auth-menu :account-url account-url)',
**{"account-url": "https://account.example.com"},
**{"account-url": "https://account.example.com/"},
)
assert "fa-solid fa-key" in html
assert "sign in or register" in html