Migrate all apps to defpage declarative page routes

Replace Python GET page handlers with declarative defpage definitions in .sx
files across all 8 apps (sx docs, orders, account, market, cart, federation,
events, blog). Each app now has sxc/pages/ with setup functions, layout
registrations, page helpers, and .sx defpage declarations.

Core infrastructure: add g I/O primitive, PageDef support for auth/layout/
data/content/filter/aside/menu slots, post_author auth level, and custom
layout registration. Remove ~1400 lines of render_*_page/render_*_oob
boilerplate. Update all endpoint references in routes, sx_components, and
templates to defpage_* naming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 14:52:34 +00:00
parent 5b4cacaf19
commit c243d17eeb
108 changed files with 3598 additions and 2851 deletions

View File

@@ -78,6 +78,10 @@ def create_app() -> "Quart":
app.jinja_loader,
])
# --- defpage setup ---
from sxc.pages import setup_events_pages
setup_events_pages()
# All events: / — global view across all pages
app.register_blueprint(
register_all_events(),
@@ -169,11 +173,16 @@ def create_app() -> "Quart":
# Tickets blueprint — user-facing ticket views and QR codes
from bp.tickets.routes import register as register_tickets
app.register_blueprint(register_tickets())
tickets_bp = register_tickets()
from shared.sx.pages import mount_pages
mount_pages(tickets_bp, "events", names=["my-tickets", "ticket-detail"])
app.register_blueprint(tickets_bp)
# Ticket admin — check-in interface (admin only)
from bp.ticket_admin.routes import register as register_ticket_admin
app.register_blueprint(register_ticket_admin())
ticket_admin_bp = register_ticket_admin()
mount_pages(ticket_admin_bp, "events", names=["ticket-admin"])
app.register_blueprint(ticket_admin_bp)
# --- oEmbed endpoint ---
@app.get("/oembed")

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from quart import (
request, render_template, make_response, Blueprint, g
request, Blueprint, g
)
@@ -14,23 +14,18 @@ from shared.sx.helpers import sx_response
def register():
bp = Blueprint("admin", __name__, url_prefix='/admin')
# ---------- Pages ----------
@bp.get("/")
@require_admin
async def admin(calendar_slug: str, **kwargs):
from shared.browser.app.utils.htmx import is_htmx_request
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
from shared.sx.page import get_template_context
from sx.sx_components import render_calendar_admin_page, render_calendar_admin_oob
from sx.sx_components import _calendar_admin_main_panel_html
ctx = await get_template_context()
g.calendar_admin_content = _calendar_admin_main_panel_html(ctx)
tctx = await get_template_context()
if not is_htmx_request():
html = await render_calendar_admin_page(tctx)
return await make_response(html)
else:
sx_src = await render_calendar_admin_oob(tctx)
return sx_response(sx_src)
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["calendar-admin"])
@bp.get("/description/")
@require_admin

View File

@@ -1,29 +1,23 @@
from __future__ import annotations
from quart import (
make_response, Blueprint
request, Blueprint, g
)
from shared.browser.app.authz import require_admin
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.helpers import sx_response
def register():
bp = Blueprint("admin", __name__, url_prefix='/admin')
# ---------- Pages ----------
@bp.get("/")
@require_admin
async def admin(entry_id: int, **kwargs):
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
from shared.sx.page import get_template_context
from sx.sx_components import render_entry_admin_page, render_entry_admin_oob
from sx.sx_components import _entry_admin_main_panel_html
ctx = await get_template_context()
g.entry_admin_content = _entry_admin_main_panel_html(ctx)
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["entry-admin"])
tctx = await get_template_context()
if not is_htmx_request():
html = await render_entry_admin_page(tctx)
return await make_response(html)
else:
sx_src = await render_entry_admin_oob(tctx)
return sx_response(sx_src)
return bp

View File

@@ -238,20 +238,18 @@ def register():
"user_ticket_counts_by_type": user_ticket_counts_by_type,
"container_nav": container_nav,
}
@bp.get("/")
@require_admin
async def get(entry_id: int, **rest):
from shared.browser.app.utils.htmx import is_htmx_request
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
from shared.sx.page import get_template_context
from sx.sx_components import render_entry_page, render_entry_oob
from sx.sx_components import _entry_main_panel_html, _entry_nav_html
ctx = await get_template_context()
g.entry_content = _entry_main_panel_html(ctx)
g.entry_menu = _entry_nav_html(ctx)
tctx = await get_template_context()
if not is_htmx_request():
html = await render_entry_page(tctx)
return await make_response(html, 200)
else:
sx_src = await render_entry_oob(tctx)
return sx_response(sx_src)
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["entry-detail"])
@bp.get("/edit/")
@require_admin
@@ -435,10 +433,10 @@ def register():
nav_oob = await get_day_nav_oob(year, month, day)
from shared.sx.page import get_template_context
from sx.sx_components import render_entry_page
from sx.sx_components import _entry_main_panel_html
tctx = await get_template_context()
html = await render_entry_page(tctx)
html = _entry_main_panel_html(tctx)
return sx_response(html + nav_oob)

View File

@@ -1,31 +1,21 @@
from __future__ import annotations
from quart import (
render_template, make_response, Blueprint
request, Blueprint, g
)
from shared.browser.app.authz import require_admin
from shared.sx.helpers import sx_response
def register():
bp = Blueprint("admin", __name__, url_prefix='/admin')
# ---------- Pages ----------
@bp.get("/")
@require_admin
async def admin(year: int, month: int, day: int, **kwargs):
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.page import get_template_context
from sx.sx_components import render_day_admin_page, render_day_admin_oob
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
from sx.sx_components import _day_admin_main_panel_html
g.day_admin_content = _day_admin_main_panel_html({})
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["day-admin"])
tctx = await get_template_context()
if not is_htmx_request():
html = await render_day_admin_page(tctx)
return await make_response(html)
else:
sx_src = await render_day_admin_oob(tctx)
return sx_response(sx_src)
return bp

View File

@@ -9,9 +9,8 @@ from .services.markets import (
soft_delete as svc_soft_delete,
)
from shared.browser.app.redis_cacher import cache_page, clear_cache
from shared.browser.app.redis_cacher import clear_cache
from shared.browser.app.authz import require_admin
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.helpers import sx_response
@@ -22,18 +21,17 @@ def register():
async def inject_root():
return {}
@bp.get("/")
async def home(**kwargs):
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
from shared.sx.page import get_template_context
from sx.sx_components import render_markets_page, render_markets_oob
from sx.sx_components import _markets_main_panel_html
ctx = await get_template_context()
if not is_htmx_request():
html = await render_markets_page(ctx)
return await make_response(html)
else:
sx_src = await render_markets_oob(ctx)
return sx_response(sx_src)
g.markets_content = _markets_main_panel_html(ctx)
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["events-markets"])
@bp.post("/new/")
@require_admin

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from quart import (
request, render_template, make_response, Blueprint, g, jsonify
request, make_response, Blueprint, g, jsonify
)
from sqlalchemy.exc import IntegrityError
@@ -23,33 +23,32 @@ from shared.browser.app.utils import (
parse_time,
parse_cost
)
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.helpers import sx_response
def register():
bp = Blueprint("slot", __name__, url_prefix='/<int:slot_id>')
# ---------- Pages ----------
@bp.get("/")
@require_admin
async def get(slot_id: int, **kwargs):
slot = await svc_get_slot(g.s, slot_id)
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
slot_id = (request.view_args or {}).get("slot_id")
slot = await svc_get_slot(g.s, slot_id) if slot_id else None
if not slot:
return await make_response("Not found", 404)
from shared.sx.page import get_template_context
from sx.sx_components import render_slot_page, render_slot_oob
from quart import abort
abort(404)
g.slot = slot
calendar = getattr(g, "calendar", None)
from sx.sx_components import render_slot_main_panel
g.slot_content = render_slot_main_panel(slot, calendar)
tctx = await get_template_context()
if not is_htmx_request():
html = await render_slot_page(tctx)
return await make_response(html)
else:
sx_src = await render_slot_oob(tctx)
return sx_response(sx_src)
@bp.context_processor
async def _inject_slot():
return {"slot": getattr(g, "slot", None)}
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["slot-detail"])
@bp.get("/edit/")
@require_admin

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from quart import (
request, render_template, make_response, Blueprint, g, jsonify
request, Blueprint, g, jsonify
)
from sqlalchemy.exc import IntegrityError
@@ -19,21 +19,16 @@ from shared.browser.app.utils import (
parse_time,
parse_cost
)
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.helpers import sx_response
def register():
bp = Blueprint("slots", __name__, url_prefix='/slots')
# ---------- Pages ----------
bp.register_blueprint(
register_slot()
)
@bp.context_processor
async def get_slots():
calendar = getattr(g, "calendar", None)
@@ -43,19 +38,17 @@ def register():
}
return {"slots": []}
@bp.get("/")
async def get(**kwargs):
from shared.sx.page import get_template_context
from sx.sx_components import render_slots_page, render_slots_oob
tctx = await get_template_context()
if not is_htmx_request():
html = await render_slots_page(tctx)
return await make_response(html)
else:
sx_src = await render_slots_oob(tctx)
return sx_response(sx_src)
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
calendar = getattr(g, "calendar", None)
slots = await svc_list_slots(g.s, calendar.id) if calendar else []
from sx.sx_components import render_slots_table
g.slots_content = render_slots_table(slots, calendar)
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["slots-listing"])
@bp.post("/")
@require_admin

View File

@@ -12,7 +12,7 @@ from __future__ import annotations
import logging
from quart import (
Blueprint, g, request, render_template, make_response, jsonify,
Blueprint, g, request, make_response,
)
from sqlalchemy import select, func
from sqlalchemy.orm import selectinload
@@ -34,12 +34,10 @@ logger = logging.getLogger(__name__)
def register() -> Blueprint:
bp = Blueprint("ticket_admin", __name__, url_prefix="/admin/tickets")
@bp.get("/")
@require_admin
async def dashboard():
"""Ticket admin dashboard with QR scanner and recent tickets."""
from shared.browser.app.utils.htmx import is_htmx_request
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
# Get recent tickets
result = await g.s.execute(
select(Ticket)
@@ -72,15 +70,9 @@ def register() -> Blueprint:
}
from shared.sx.page import get_template_context
from sx.sx_components import render_ticket_admin_page, render_ticket_admin_oob
from sx.sx_components import _ticket_admin_main_panel_html
ctx = await get_template_context()
if not is_htmx_request():
html = await render_ticket_admin_page(ctx, tickets, stats)
return await make_response(html, 200)
else:
sx_src = await render_ticket_admin_oob(ctx, tickets, stats)
return sx_response(sx_src)
g.ticket_admin_content = _ticket_admin_main_panel_html(ctx, tickets, stats)
@bp.get("/entry/<int:entry_id>/")
@require_admin

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from quart import (
request, render_template, make_response, Blueprint, g, jsonify
request, make_response, Blueprint, g, jsonify
)
from shared.browser.app.authz import require_admin
@@ -16,30 +16,37 @@ from .services.ticket import (
from ..ticket_types.services.tickets import (
list_ticket_types as svc_list_ticket_types,
)
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.helpers import sx_response
def register():
bp = Blueprint("ticket_type", __name__, url_prefix='/<int:ticket_type_id>')
@bp.get("/")
@require_admin
async def get(ticket_type_id: int, **kwargs):
"""View a single ticket type."""
ticket_type = await svc_get_ticket_type(g.s, ticket_type_id)
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
ticket_type_id = (request.view_args or {}).get("ticket_type_id")
ticket_type = await svc_get_ticket_type(g.s, ticket_type_id) if ticket_type_id else None
if not ticket_type:
return await make_response("Not found", 404)
from shared.sx.page import get_template_context
from sx.sx_components import render_ticket_type_page, render_ticket_type_oob
from quart import abort
abort(404)
g.ticket_type = ticket_type
entry = getattr(g, "entry", None)
calendar = getattr(g, "calendar", None)
va = request.view_args or {}
from sx.sx_components import render_ticket_type_main_panel
g.ticket_type_content = render_ticket_type_main_panel(
ticket_type, entry, calendar,
va.get("day"), va.get("month"), va.get("year"),
)
tctx = await get_template_context()
if not is_htmx_request():
html = await render_ticket_type_page(tctx)
return await make_response(html)
else:
sx_src = await render_ticket_type_oob(tctx)
return sx_response(sx_src)
@bp.context_processor
async def _inject_ticket_type():
return {"ticket_type": getattr(g, "ticket_type", None)}
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["ticket-type-detail"])
@bp.get("/edit/")
@require_admin

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from quart import (
request, render_template, make_response, Blueprint, g, jsonify
request, Blueprint, g, jsonify
)
from shared.browser.app.authz import require_admin
@@ -14,7 +14,6 @@ from .services.tickets import (
from ..ticket_type.routes import register as register_ticket_type
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.helpers import sx_response
@@ -36,19 +35,22 @@ def register():
}
return {"ticket_types": []}
@bp.get("/")
async def get(**kwargs):
"""List all ticket types for the current entry."""
from shared.sx.page import get_template_context
from sx.sx_components import render_ticket_types_page, render_ticket_types_oob
@bp.before_request
async def _prepare_page_data():
if "defpage_" not in (request.endpoint or ""):
return
entry = getattr(g, "entry", None)
calendar = getattr(g, "calendar", None)
ticket_types = await svc_list_ticket_types(g.s, entry.id) if entry else []
va = request.view_args or {}
from sx.sx_components import render_ticket_types_table
g.ticket_types_content = render_ticket_types_table(
ticket_types, entry, calendar,
va.get("day"), va.get("month"), va.get("year"),
)
tctx = await get_template_context()
if not is_htmx_request():
html = await render_ticket_types_page(tctx)
return await make_response(html)
else:
sx_src = await render_ticket_types_oob(tctx)
return sx_response(sx_src)
from shared.sx.pages import mount_pages
mount_pages(bp, "events", names=["ticket-types-listing"])
@bp.post("/")
@require_admin

View File

@@ -12,7 +12,7 @@ from __future__ import annotations
import logging
from quart import (
Blueprint, g, request, render_template, make_response,
Blueprint, g, request, make_response,
)
from sqlalchemy import select
from sqlalchemy.orm import selectinload
@@ -39,59 +39,43 @@ logger = logging.getLogger(__name__)
def register() -> Blueprint:
bp = Blueprint("tickets", __name__, url_prefix="/tickets")
@bp.get("/")
async def my_tickets():
"""List all tickets for the current user/session."""
from shared.browser.app.utils.htmx import is_htmx_request
ident = current_cart_identity()
tickets = await get_user_tickets(
g.s,
user_id=ident["user_id"],
session_id=ident["session_id"],
)
from shared.sx.page import get_template_context
from sx.sx_components import render_tickets_page, render_tickets_oob
ctx = await get_template_context()
if not is_htmx_request():
html = await render_tickets_page(ctx, tickets)
return await make_response(html, 200)
else:
sx_src = await render_tickets_oob(ctx, tickets)
return sx_response(sx_src)
@bp.get("/<code>/")
async def ticket_detail(code: str):
"""View a single ticket with QR code."""
from shared.browser.app.utils.htmx import is_htmx_request
ticket = await get_ticket_by_code(g.s, code)
if not ticket:
return await make_response("Ticket not found", 404)
# Verify ownership
ident = current_cart_identity()
if ident["user_id"] is not None:
if ticket.user_id != ident["user_id"]:
return await make_response("Ticket not found", 404)
elif ident["session_id"] is not None:
if ticket.session_id != ident["session_id"]:
return await make_response("Ticket not found", 404)
else:
return await make_response("Ticket not found", 404)
from shared.sx.page import get_template_context
from sx.sx_components import render_ticket_detail_page, render_ticket_detail_oob
ctx = await get_template_context()
if not is_htmx_request():
html = await render_ticket_detail_page(ctx, ticket)
return await make_response(html, 200)
else:
sx_src = await render_ticket_detail_oob(ctx, ticket)
return sx_response(sx_src)
@bp.before_request
async def _prepare_page_data():
ep = request.endpoint or ""
if "defpage_my_tickets" in ep:
ident = current_cart_identity()
tickets = await get_user_tickets(
g.s,
user_id=ident["user_id"],
session_id=ident["session_id"],
)
from shared.sx.page import get_template_context
from sx.sx_components import _tickets_main_panel_html
ctx = await get_template_context()
g.tickets_content = _tickets_main_panel_html(ctx, tickets)
elif "defpage_ticket_detail" in ep:
code = (request.view_args or {}).get("code")
ticket = await get_ticket_by_code(g.s, code) if code else None
if not ticket:
from quart import abort
abort(404)
# Verify ownership
ident = current_cart_identity()
if ident["user_id"] is not None:
if ticket.user_id != ident["user_id"]:
from quart import abort
abort(404)
elif ident["session_id"] is not None:
if ticket.session_id != ident["session_id"]:
from quart import abort
abort(404)
else:
from quart import abort
abort(404)
from shared.sx.page import get_template_context
from sx.sx_components import _ticket_detail_panel_html
ctx = await get_template_context()
g.ticket_detail_content = _ticket_detail_panel_html(ctx, ticket)
@bp.post("/buy/")
@clear_cache(tag="calendars", tag_scope="all")

View File

@@ -191,11 +191,11 @@ def _calendar_nav_sx(ctx: dict) -> str:
select_colours = ctx.get("select_colours", "")
parts = []
slots_href = url_for("calendar.slots.get", calendar_slug=cal_slug)
slots_href = url_for("calendar.slots.defpage_slots_listing", calendar_slug=cal_slug)
parts.append(sx_call("nav-link", href=slots_href, icon="fa fa-clock",
label="Slots", select_colours=select_colours))
if is_admin:
admin_href = url_for("calendar.admin.admin", calendar_slug=cal_slug)
admin_href = url_for("calendar.admin.defpage_calendar_admin", calendar_slug=cal_slug)
parts.append(sx_call("nav-link", href=admin_href, icon="fa fa-cog",
select_colours=select_colours))
return "".join(parts)
@@ -319,7 +319,7 @@ def _calendar_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
nav_parts = []
if cal_slug:
for endpoint, label in [
("calendar.slots.get", "slots"),
("calendar.slots.defpage_slots_listing", "slots"),
("calendar.admin.calendar_description_edit", "description"),
]:
href = url_for(endpoint, calendar_slug=cal_slug)
@@ -339,7 +339,7 @@ def _calendar_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
def _markets_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the markets section header row."""
from quart import url_for
link_href = url_for("markets.home")
link_href = url_for("markets.defpage_events_markets")
return sx_call("menu-row-sx", id="markets-row", level=3,
link_href=link_href,
link_label_content=SxExpr(sx_call("events-markets-label")),
@@ -594,7 +594,7 @@ def _day_row_html(ctx: dict, entry) -> str:
# Slot/Time
slot = getattr(entry, "slot", None)
if slot:
slot_href = url_for("calendar.slots.slot.get", calendar_slug=cal_slug, slot_id=slot.id)
slot_href = url_for("calendar.slots.slot.defpage_slot_detail", calendar_slug=cal_slug, slot_id=slot.id)
time_start = slot.time_start.strftime("%H:%M") if slot.time_start else ""
time_end = f" \u2192 {slot.time_end.strftime('%H:%M')}" if slot.time_end else ""
slot_html = sx_call("events-day-row-slot",
@@ -774,7 +774,7 @@ def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
ticket_cards = []
if tickets:
for ticket in tickets:
href = url_for("tickets.ticket_detail", code=ticket.code)
href = url_for("tickets.defpage_ticket_detail", code=ticket.code)
entry = getattr(ticket, "entry", None)
entry_name = entry.name if entry else "Unknown event"
tt = getattr(ticket, "ticket_type", None)
@@ -819,7 +819,7 @@ def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
bg_map = {"confirmed": "bg-emerald-50", "checked_in": "bg-blue-50", "reserved": "bg-amber-50"}
header_bg = bg_map.get(state, "bg-stone-50")
entry_name = entry.name if entry else "Ticket"
back_href = url_for("tickets.my_tickets")
back_href = url_for("tickets.defpage_my_tickets")
# Badge with larger sizing
badge = _ticket_state_badge_html(state).replace('px-2 py-0.5 text-xs', 'px-3 py-1 text-sm')
@@ -1400,42 +1400,7 @@ async def render_day_oob(ctx: dict) -> str:
# ---------------------------------------------------------------------------
# Day admin
# ---------------------------------------------------------------------------
async def render_day_admin_page(ctx: dict) -> str:
"""Full page: day admin."""
content = _day_admin_main_panel_html(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = (admin_hdr + _calendar_header_sx(ctx) + _day_header_sx(ctx)
+ _day_admin_header_sx(ctx))
hdr = root_hdr + post_hdr + header_child_sx(child)
return full_page_sx(ctx, header_rows=hdr, content=content)
async def render_day_admin_oob(ctx: dict) -> str:
"""OOB response: day admin."""
content = _day_admin_main_panel_html(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _calendar_header_sx(ctx, oob=True))
oobs += oob_header_sx("day-header-child", "day-admin-header-child",
_day_admin_header_sx(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child",
"day-admin-row", "day-admin-header-child")
return oob_page_sx(oobs=oobs, content=content)
# ---------------------------------------------------------------------------
# Calendar admin
# Calendar admin helper
# ---------------------------------------------------------------------------
def _events_post_admin_header_sx(ctx: dict, *, oob: bool = False,
@@ -1445,140 +1410,6 @@ def _events_post_admin_header_sx(ctx: dict, *, oob: bool = False,
return post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
async def render_calendar_admin_page(ctx: dict) -> str:
"""Full page: calendar admin."""
content = _calendar_admin_main_panel_html(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = admin_hdr + _calendar_header_sx(ctx) + _calendar_admin_header_sx(ctx)
hdr = root_hdr + post_hdr + header_child_sx(child)
return full_page_sx(ctx, header_rows=hdr, content=content)
async def render_calendar_admin_oob(ctx: dict) -> str:
"""OOB response: calendar admin."""
content = _calendar_admin_main_panel_html(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _calendar_header_sx(ctx, oob=True))
oobs += oob_header_sx("calendar-header-child", "calendar-admin-header-child",
_calendar_admin_header_sx(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"calendar-admin-row", "calendar-admin-header-child")
return oob_page_sx(oobs=oobs, content=content)
# ---------------------------------------------------------------------------
# Slots
# ---------------------------------------------------------------------------
async def render_slots_page(ctx: dict) -> str:
"""Full page: slots listing."""
from quart import g
slots = ctx.get("slots") or []
calendar = ctx.get("calendar")
content = render_slots_table(slots, calendar)
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = admin_hdr + _calendar_header_sx(ctx) + _calendar_admin_header_sx(ctx)
hdr = root_hdr + post_hdr + header_child_sx(child)
return full_page_sx(ctx, header_rows=hdr, content=content)
async def render_slots_oob(ctx: dict) -> str:
"""OOB response: slots listing."""
slots = ctx.get("slots") or []
calendar = ctx.get("calendar")
content = render_slots_table(slots, calendar)
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _calendar_admin_header_sx(ctx, oob=True))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"calendar-admin-row", "calendar-admin-header-child")
return oob_page_sx(oobs=oobs, content=content)
# ---------------------------------------------------------------------------
# Tickets
# ---------------------------------------------------------------------------
async def render_tickets_page(ctx: dict, tickets: list) -> str:
"""Full page: my tickets."""
content = _tickets_main_panel_html(ctx, tickets)
hdr = root_header_sx(ctx)
return full_page_sx(ctx, header_rows=hdr, content=content)
async def render_tickets_oob(ctx: dict, tickets: list) -> str:
"""OOB response: my tickets."""
content = _tickets_main_panel_html(ctx, tickets)
return oob_page_sx(content=content)
async def render_ticket_detail_page(ctx: dict, ticket) -> str:
"""Full page: ticket detail with QR."""
content = _ticket_detail_panel_html(ctx, ticket)
hdr = root_header_sx(ctx)
return full_page_sx(ctx, header_rows=hdr, content=content)
async def render_ticket_detail_oob(ctx: dict, ticket) -> str:
"""OOB response: ticket detail."""
content = _ticket_detail_panel_html(ctx, ticket)
return oob_page_sx(content=content)
# ---------------------------------------------------------------------------
# Ticket admin
# ---------------------------------------------------------------------------
async def render_ticket_admin_page(ctx: dict, tickets: list, stats: dict) -> str:
"""Full page: ticket admin dashboard."""
content = _ticket_admin_main_panel_html(ctx, tickets, stats)
hdr = root_header_sx(ctx)
return full_page_sx(ctx, header_rows=hdr, content=content)
async def render_ticket_admin_oob(ctx: dict, tickets: list, stats: dict) -> str:
"""OOB response: ticket admin dashboard."""
content = _ticket_admin_main_panel_html(ctx, tickets, stats)
return oob_page_sx(content=content)
# ---------------------------------------------------------------------------
# Markets
# ---------------------------------------------------------------------------
async def render_markets_page(ctx: dict) -> str:
"""Full page: markets listing."""
content = _markets_main_panel_html(ctx)
hdr = root_header_sx(ctx)
child = _post_header_sx(ctx) + _markets_header_sx(ctx)
hdr += header_child_sx(child)
return full_page_sx(ctx, header_rows=hdr, content=content)
async def render_markets_oob(ctx: dict) -> str:
"""OOB response: markets listing."""
content = _markets_main_panel_html(ctx)
oobs = _post_header_sx(ctx, oob=True)
oobs += oob_header_sx("post-header-child", "markets-header-child",
_markets_header_sx(ctx))
return oob_page_sx(oobs=oobs, content=content)
# ===========================================================================
# POST / PUT / DELETE response components
# ===========================================================================
@@ -1939,36 +1770,6 @@ def _entry_nav_html(ctx: dict) -> str:
return "".join(parts)
# ---------------------------------------------------------------------------
# Entry page / OOB rendering
# ---------------------------------------------------------------------------
async def render_entry_page(ctx: dict) -> str:
"""Full page: entry detail."""
content = _entry_main_panel_html(ctx)
hdr = root_header_sx(ctx)
child = (_post_header_sx(ctx)
+ _calendar_header_sx(ctx) + _day_header_sx(ctx)
+ _entry_header_html(ctx))
hdr += header_child_sx(child)
nav_html = _entry_nav_html(ctx)
return full_page_sx(ctx, header_rows=hdr, content=content, menu=nav_html)
async def render_entry_oob(ctx: dict) -> str:
"""OOB response: entry detail."""
content = _entry_main_panel_html(ctx)
oobs = _day_header_sx(ctx, oob=True)
oobs += oob_header_sx("day-header-child", "entry-header-child",
_entry_header_html(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child",
"entry-row", "entry-header-child")
nav_html = _entry_nav_html(ctx)
return oob_page_sx(oobs=oobs, content=content, menu=nav_html)
# ---------------------------------------------------------------------------
# Entry optioned (confirm/decline/provisional response)
# ---------------------------------------------------------------------------
@@ -2364,7 +2165,7 @@ def render_slots_table(slots, calendar) -> str:
rows_html = ""
if slots:
for s in slots:
slot_href = url_for("calendar.slots.slot.get", calendar_slug=cal_slug, slot_id=s.id)
slot_href = url_for("calendar.slots.slot.defpage_slot_detail", calendar_slug=cal_slug, slot_id=s.id)
del_url = url_for("calendar.slots.slot.slot_delete", calendar_slug=cal_slug, slot_id=s.id)
desc = getattr(s, "description", "") or ""
@@ -2508,7 +2309,7 @@ def render_buy_result(entry, created_tickets, remaining, cart_count) -> str:
tickets_html = ""
for ticket in created_tickets:
href = url_for("tickets.ticket_detail", code=ticket.code)
href = url_for("tickets.defpage_ticket_detail", code=ticket.code)
tickets_html += sx_call("events-buy-result-ticket",
href=href, code_short=ticket.code[:12] + "...")
@@ -2518,7 +2319,7 @@ def render_buy_result(entry, created_tickets, remaining, cart_count) -> str:
remaining_html = sx_call("events-buy-result-remaining",
text=f"{remaining} ticket{r_suffix} remaining")
my_href = url_for("tickets.my_tickets")
my_href = url_for("tickets.defpage_my_tickets")
return cart_html + sx_call("events-buy-result",
entry_id=str(entry.id),
@@ -2610,7 +2411,7 @@ def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket
return _adj_form(1, sx_call("events-adjust-cart-plus"),
extra_cls="flex items-center")
my_tickets_href = url_for("tickets.my_tickets")
my_tickets_href = url_for("tickets.defpage_my_tickets")
minus = _adj_form(count - 1, sx_call("events-adjust-minus"))
cart_icon = sx_call("events-adjust-cart-icon",
href=my_tickets_href, count=str(count))
@@ -2960,40 +2761,6 @@ def _entry_admin_main_panel_html(ctx: dict) -> str:
is_selected=False)
async def render_entry_admin_page(ctx: dict) -> str:
"""Full page: entry admin."""
content = _entry_admin_main_panel_html(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = (admin_hdr + _calendar_header_sx(ctx) + _day_header_sx(ctx)
+ _entry_header_html(ctx) + _entry_admin_header_html(ctx))
hdr = root_hdr + post_hdr + header_child_sx(child)
nav_html = sx_call("events-admin-placeholder-nav")
return full_page_sx(ctx, header_rows=hdr, content=content, menu=nav_html)
async def render_entry_admin_oob(ctx: dict) -> str:
"""OOB response: entry admin."""
content = _entry_admin_main_panel_html(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _entry_header_html(ctx, oob=True))
oobs += oob_header_sx("entry-header-child", "entry-admin-header-child",
_entry_admin_header_html(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child",
"entry-row", "entry-header-child",
"entry-admin-row", "entry-admin-header-child")
nav_html = sx_call("events-admin-placeholder-nav")
return oob_page_sx(oobs=oobs, content=content, menu=nav_html)
# ===========================================================================
# Slot page / OOB (extends slots)
# ===========================================================================
@@ -3027,45 +2794,6 @@ def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
child_id="slot-header-child", oob=oob)
async def render_slot_page(ctx: dict) -> str:
"""Full page: slot detail (extends slots page)."""
slot = ctx.get("slot")
calendar = ctx.get("calendar")
if not slot or not calendar:
return ""
content = render_slot_main_panel(slot, calendar)
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = (admin_hdr + _calendar_header_sx(ctx) + _calendar_admin_header_sx(ctx)
+ _slot_header_html(ctx))
hdr = root_hdr + post_hdr + header_child_sx(child)
return full_page_sx(ctx, header_rows=hdr, content=content)
async def render_slot_oob(ctx: dict) -> str:
"""OOB response: slot detail."""
slot = ctx.get("slot")
calendar = ctx.get("calendar")
if not slot or not calendar:
return ""
content = render_slot_main_panel(slot, calendar)
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _calendar_admin_header_sx(ctx, oob=True))
oobs += oob_header_sx("calendar-admin-header-child", "slot-header-child",
_slot_header_html(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"calendar-admin-row", "calendar-admin-header-child",
"slot-row", "slot-header-child")
return oob_page_sx(oobs=oobs, content=content)
# ===========================================================================
# Slot edit form
# ===========================================================================
@@ -3243,40 +2971,6 @@ def _ticket_types_header_html(ctx: dict, *, oob: bool = False) -> str:
nav=SxExpr(nav_html) if nav_html else None, child_id="ticket_type-header-child", oob=oob)
async def render_ticket_types_page(ctx: dict) -> str:
"""Full page: ticket types listing (extends entry admin)."""
ticket_types = ctx.get("ticket_types") or []
entry = ctx.get("entry")
calendar = ctx.get("calendar")
day = ctx.get("day")
month = ctx.get("month")
year = ctx.get("year")
content = render_ticket_types_table(ticket_types, entry, calendar, day, month, year)
hdr = root_header_sx(ctx)
child = (_post_header_sx(ctx)
+ _calendar_header_sx(ctx) + _day_header_sx(ctx)
+ _entry_header_html(ctx) + _entry_admin_header_html(ctx)
+ _ticket_types_header_html(ctx))
hdr += header_child_sx(child)
nav_html = sx_call("events-admin-placeholder-nav")
return full_page_sx(ctx, header_rows=hdr, content=content, menu=nav_html)
async def render_ticket_types_oob(ctx: dict) -> str:
"""OOB response: ticket types listing."""
ticket_types = ctx.get("ticket_types") or []
entry = ctx.get("entry")
calendar = ctx.get("calendar")
day = ctx.get("day")
month = ctx.get("month")
year = ctx.get("year")
content = render_ticket_types_table(ticket_types, entry, calendar, day, month, year)
oobs = _entry_admin_header_html(ctx, oob=True)
oobs += oob_header_sx("entry-admin-header-child", "ticket_types-header-child",
_ticket_types_header_html(ctx))
nav_html = sx_call("events-admin-placeholder-nav")
return oob_page_sx(oobs=oobs, content=content, menu=nav_html)
# ===========================================================================
# Ticket type page / OOB
@@ -3317,41 +3011,6 @@ def _ticket_type_header_html(ctx: dict, *, oob: bool = False) -> str:
nav=SxExpr(nav_html) if nav_html else None, child_id="ticket_type-header-child-inner", oob=oob)
async def render_ticket_type_page(ctx: dict) -> str:
"""Full page: single ticket type detail (extends ticket types)."""
ticket_type = ctx.get("ticket_type")
entry = ctx.get("entry")
calendar = ctx.get("calendar")
day = ctx.get("day")
month = ctx.get("month")
year = ctx.get("year")
content = render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year)
hdr = root_header_sx(ctx)
child = (_post_header_sx(ctx)
+ _calendar_header_sx(ctx) + _day_header_sx(ctx)
+ _entry_header_html(ctx) + _entry_admin_header_html(ctx)
+ _ticket_types_header_html(ctx) + _ticket_type_header_html(ctx))
hdr += header_child_sx(child)
nav_html = sx_call("events-admin-placeholder-nav")
return full_page_sx(ctx, header_rows=hdr, content=content, menu=nav_html)
async def render_ticket_type_oob(ctx: dict) -> str:
"""OOB response: single ticket type detail."""
ticket_type = ctx.get("ticket_type")
entry = ctx.get("entry")
calendar = ctx.get("calendar")
day = ctx.get("day")
month = ctx.get("month")
year = ctx.get("year")
content = render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year)
oobs = _ticket_types_header_html(ctx, oob=True)
oobs += oob_header_sx("ticket_types-header-child", "ticket_type-header-child",
_ticket_type_header_html(ctx))
nav_html = sx_call("events-admin-placeholder-nav")
return oob_page_sx(oobs=oobs, content=content, menu=nav_html)
# ===========================================================================
# Ticket type edit form
# ===========================================================================

View File

@@ -0,0 +1,406 @@
"""Events defpage setup — registers layouts, page helpers, and loads .sx pages."""
from __future__ import annotations
from typing import Any
def setup_events_pages() -> None:
"""Register events-specific layouts, page helpers, and load page definitions."""
_register_events_layouts()
_register_events_helpers()
_load_events_page_files()
def _load_events_page_files() -> None:
import os
from shared.sx.pages import load_page_dir
load_page_dir(os.path.dirname(__file__), "events")
# ---------------------------------------------------------------------------
# Layouts
# ---------------------------------------------------------------------------
def _register_events_layouts() -> None:
from shared.sx.layouts import register_custom_layout
register_custom_layout("events-calendar-admin", _cal_admin_full, _cal_admin_oob)
register_custom_layout("events-slots", _slots_full, _slots_oob)
register_custom_layout("events-slot", _slot_full, _slot_oob)
register_custom_layout("events-day-admin", _day_admin_full, _day_admin_oob)
register_custom_layout("events-entry", _entry_full, _entry_oob)
register_custom_layout("events-entry-admin", _entry_admin_full, _entry_admin_oob)
register_custom_layout("events-ticket-types", _ticket_types_full, _ticket_types_oob)
register_custom_layout("events-ticket-type", _ticket_type_full, _ticket_type_oob)
register_custom_layout("events-markets", _markets_full, _markets_oob)
# --- Calendar admin layout (root + post + child(post-admin + calendar + cal-admin)) ---
async def _cal_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, post_admin_header_sx, header_child_sx
from sx.sx_components import (
_ensure_container_nav, _post_header_sx,
_calendar_header_sx, _calendar_admin_header_sx,
)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = admin_hdr + _calendar_header_sx(ctx) + _calendar_admin_header_sx(ctx)
return root_hdr + post_hdr + header_child_sx(child)
async def _cal_admin_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import post_admin_header_sx, oob_header_sx
from sx.sx_components import (
_ensure_container_nav, _calendar_header_sx,
_calendar_admin_header_sx, _clear_deeper_oob,
)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _calendar_header_sx(ctx, oob=True))
oobs += oob_header_sx("calendar-header-child", "calendar-admin-header-child",
_calendar_admin_header_sx(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"calendar-admin-row", "calendar-admin-header-child")
return oobs
# --- Slots layout (same full as cal-admin but different OOB) ---
async def _slots_full(ctx: dict, **kw: Any) -> str:
return await _cal_admin_full({**ctx, "is_admin_section": True}, **kw)
async def _slots_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import post_admin_header_sx
from sx.sx_components import (
_ensure_container_nav, _calendar_admin_header_sx, _clear_deeper_oob,
)
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _calendar_admin_header_sx(ctx, oob=True))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"calendar-admin-row", "calendar-admin-header-child")
return oobs
# --- Slot detail layout (extends cal-admin with slot header) ---
async def _slot_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, post_admin_header_sx, header_child_sx
from sx.sx_components import (
_ensure_container_nav, _post_header_sx,
_calendar_header_sx, _calendar_admin_header_sx, _slot_header_html,
)
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = (admin_hdr + _calendar_header_sx(ctx)
+ _calendar_admin_header_sx(ctx) + _slot_header_html(ctx))
return root_hdr + post_hdr + header_child_sx(child)
async def _slot_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import post_admin_header_sx, oob_header_sx
from sx.sx_components import (
_ensure_container_nav, _calendar_admin_header_sx,
_slot_header_html, _clear_deeper_oob,
)
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _calendar_admin_header_sx(ctx, oob=True))
oobs += oob_header_sx("calendar-admin-header-child", "slot-header-child",
_slot_header_html(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"calendar-admin-row", "calendar-admin-header-child",
"slot-row", "slot-header-child")
return oobs
# --- Day admin layout (root + post + post-admin + child(cal + day + day-admin)) ---
async def _day_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, post_admin_header_sx, header_child_sx
from sx.sx_components import (
_ensure_container_nav, _post_header_sx,
_calendar_header_sx, _day_header_sx, _day_admin_header_sx,
)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = (admin_hdr + _calendar_header_sx(ctx) + _day_header_sx(ctx)
+ _day_admin_header_sx(ctx))
return root_hdr + post_hdr + header_child_sx(child)
async def _day_admin_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import post_admin_header_sx, oob_header_sx
from sx.sx_components import (
_ensure_container_nav, _calendar_header_sx,
_day_admin_header_sx, _clear_deeper_oob,
)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _calendar_header_sx(ctx, oob=True))
oobs += oob_header_sx("day-header-child", "day-admin-header-child",
_day_admin_header_sx(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child",
"day-admin-row", "day-admin-header-child")
return oobs
# --- Entry layout (root + child(post + cal + day + entry), + menu) ---
def _entry_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, header_child_sx
from sx.sx_components import (
_post_header_sx, _calendar_header_sx,
_day_header_sx, _entry_header_html,
)
root_hdr = root_header_sx(ctx)
child = (_post_header_sx(ctx) + _calendar_header_sx(ctx)
+ _day_header_sx(ctx) + _entry_header_html(ctx))
return root_hdr + header_child_sx(child)
def _entry_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import oob_header_sx
from sx.sx_components import (
_day_header_sx, _entry_header_html, _clear_deeper_oob,
)
oobs = _day_header_sx(ctx, oob=True)
oobs += oob_header_sx("day-header-child", "entry-header-child",
_entry_header_html(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child",
"entry-row", "entry-header-child")
return oobs
# --- Entry admin layout (root + post + child(post-admin + cal + day + entry + entry-admin), + menu) ---
async def _entry_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, post_admin_header_sx, header_child_sx
from sx.sx_components import (
_ensure_container_nav, _post_header_sx,
_calendar_header_sx, _day_header_sx,
_entry_header_html, _entry_admin_header_html,
)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
child = (admin_hdr + _calendar_header_sx(ctx) + _day_header_sx(ctx)
+ _entry_header_html(ctx) + _entry_admin_header_html(ctx))
return root_hdr + post_hdr + header_child_sx(child)
async def _entry_admin_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import post_admin_header_sx, oob_header_sx
from sx.sx_components import (
_ensure_container_nav, _entry_header_html,
_entry_admin_header_html, _clear_deeper_oob,
)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
oobs = (post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ _entry_header_html(ctx, oob=True))
oobs += oob_header_sx("entry-header-child", "entry-admin-header-child",
_entry_admin_header_html(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child",
"entry-row", "entry-header-child",
"entry-admin-row", "entry-admin-header-child")
return oobs
# --- Ticket types layout (extends entry admin with ticket-types header, + menu) ---
def _ticket_types_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, header_child_sx
from sx.sx_components import (
_post_header_sx, _calendar_header_sx, _day_header_sx,
_entry_header_html, _entry_admin_header_html,
_ticket_types_header_html,
)
root_hdr = root_header_sx(ctx)
child = (_post_header_sx(ctx) + _calendar_header_sx(ctx)
+ _day_header_sx(ctx) + _entry_header_html(ctx)
+ _entry_admin_header_html(ctx) + _ticket_types_header_html(ctx))
return root_hdr + header_child_sx(child)
def _ticket_types_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import oob_header_sx
from sx.sx_components import (
_entry_admin_header_html, _ticket_types_header_html, _clear_deeper_oob,
)
oobs = _entry_admin_header_html(ctx, oob=True)
oobs += oob_header_sx("entry-admin-header-child", "ticket_types-header-child",
_ticket_types_header_html(ctx))
return oobs
# --- Ticket type detail layout (extends ticket types with ticket-type header, + menu) ---
def _ticket_type_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, header_child_sx
from sx.sx_components import (
_post_header_sx, _calendar_header_sx, _day_header_sx,
_entry_header_html, _entry_admin_header_html,
_ticket_types_header_html, _ticket_type_header_html,
)
root_hdr = root_header_sx(ctx)
child = (_post_header_sx(ctx) + _calendar_header_sx(ctx)
+ _day_header_sx(ctx) + _entry_header_html(ctx)
+ _entry_admin_header_html(ctx) + _ticket_types_header_html(ctx)
+ _ticket_type_header_html(ctx))
return root_hdr + header_child_sx(child)
def _ticket_type_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import oob_header_sx
from sx.sx_components import (
_ticket_types_header_html, _ticket_type_header_html,
)
oobs = _ticket_types_header_html(ctx, oob=True)
oobs += oob_header_sx("ticket_types-header-child", "ticket_type-header-child",
_ticket_type_header_html(ctx))
return oobs
# --- Markets layout (root + child(post + markets)) ---
def _markets_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, header_child_sx
from sx.sx_components import _post_header_sx, _markets_header_sx
root_hdr = root_header_sx(ctx)
child = _post_header_sx(ctx) + _markets_header_sx(ctx)
return root_hdr + header_child_sx(child)
def _markets_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import oob_header_sx
from sx.sx_components import _post_header_sx, _markets_header_sx
oobs = _post_header_sx(ctx, oob=True)
oobs += oob_header_sx("post-header-child", "markets-header-child",
_markets_header_sx(ctx))
return oobs
# ---------------------------------------------------------------------------
# Page helpers
# ---------------------------------------------------------------------------
def _register_events_helpers() -> None:
from shared.sx.pages import register_page_helpers
register_page_helpers("events", {
"calendar-admin-content": _h_calendar_admin_content,
"day-admin-content": _h_day_admin_content,
"slots-content": _h_slots_content,
"slot-content": _h_slot_content,
"entry-content": _h_entry_content,
"entry-menu": _h_entry_menu,
"entry-admin-content": _h_entry_admin_content,
"admin-menu": _h_admin_menu,
"ticket-types-content": _h_ticket_types_content,
"ticket-type-content": _h_ticket_type_content,
"tickets-content": _h_tickets_content,
"ticket-detail-content": _h_ticket_detail_content,
"ticket-admin-content": _h_ticket_admin_content,
"markets-content": _h_markets_content,
})
def _h_calendar_admin_content():
from quart import g
return getattr(g, "calendar_admin_content", "")
def _h_day_admin_content():
from quart import g
return getattr(g, "day_admin_content", "")
def _h_slots_content():
from quart import g
return getattr(g, "slots_content", "")
def _h_slot_content():
from quart import g
return getattr(g, "slot_content", "")
def _h_entry_content():
from quart import g
return getattr(g, "entry_content", "")
def _h_entry_menu():
from quart import g
return getattr(g, "entry_menu", "")
def _h_entry_admin_content():
from quart import g
return getattr(g, "entry_admin_content", "")
def _h_admin_menu():
from shared.sx.helpers import sx_call
return sx_call("events-admin-placeholder-nav")
def _h_ticket_types_content():
from quart import g
return getattr(g, "ticket_types_content", "")
def _h_ticket_type_content():
from quart import g
return getattr(g, "ticket_type_content", "")
def _h_tickets_content():
from quart import g
return getattr(g, "tickets_content", "")
def _h_ticket_detail_content():
from quart import g
return getattr(g, "ticket_detail_content", "")
def _h_ticket_admin_content():
from quart import g
return getattr(g, "ticket_admin_content", "")
def _h_markets_content():
from quart import g
return getattr(g, "markets_content", "")

View File

@@ -0,0 +1,89 @@
;; Events pages — mounted on various nested blueprints
;; Calendar admin (mounted on calendar.admin bp)
(defpage calendar-admin
:path "/"
:auth :admin
:layout :events-calendar-admin
:content (calendar-admin-content))
;; Day admin (mounted on day.admin bp)
(defpage day-admin
:path "/"
:auth :admin
:layout :events-day-admin
:content (day-admin-content))
;; Slots listing (mounted on slots bp)
(defpage slots-listing
:path "/"
:auth :public
:layout :events-slots
:content (slots-content))
;; Slot detail (mounted on slot bp)
(defpage slot-detail
:path "/"
:auth :admin
:layout :events-slot
:content (slot-content))
;; Entry detail (mounted on calendar_entry bp)
(defpage entry-detail
:path "/"
:auth :admin
:layout :events-entry
:content (entry-content)
:menu (entry-menu))
;; Entry admin (mounted on calendar_entry.admin bp)
(defpage entry-admin
:path "/"
:auth :admin
:layout :events-entry-admin
:content (entry-admin-content)
:menu (admin-menu))
;; Ticket types listing (mounted on ticket_types bp)
(defpage ticket-types-listing
:path "/"
:auth :public
:layout :events-ticket-types
:content (ticket-types-content)
:menu (admin-menu))
;; Ticket type detail (mounted on ticket_type bp)
(defpage ticket-type-detail
:path "/"
:auth :admin
:layout :events-ticket-type
:content (ticket-type-content)
:menu (admin-menu))
;; My tickets (mounted on tickets bp)
(defpage my-tickets
:path "/"
:auth :public
:layout :root
:content (tickets-content))
;; Ticket detail (mounted on tickets bp)
(defpage ticket-detail
:path "/<code>/"
:auth :public
:layout :root
:content (ticket-detail-content))
;; Ticket admin dashboard (mounted on ticket_admin bp)
(defpage ticket-admin
:path "/"
:auth :admin
:layout :root
:content (ticket-admin-content))
;; Markets (mounted on markets bp)
(defpage events-markets
:path "/"
:auth :public
:layout :events-markets
:content (markets-content))

View File

@@ -1,7 +1,7 @@
<!-- Desktop nav -->
{% import 'macros/links.html' as links %}
{% call links.link(
url_for('calendar.slots.get', calendar_slug=calendar.slug),
url_for('calendar.slots.defpage_slots_listing', calendar_slug=calendar.slug),
hx_select_search,
select_colours,
True,
@@ -14,5 +14,5 @@
{% endcall %}
{% if g.rights.admin %}
{% from 'macros/admin_nav.html' import admin_nav_item %}
{{ admin_nav_item(url_for('calendar.admin.admin', calendar_slug=calendar.slug)) }}
{{ admin_nav_item(url_for('calendar.admin.defpage_calendar_admin', calendar_slug=calendar.slug)) }}
{% endif %}

View File

@@ -23,7 +23,7 @@
<div class="text-xs font-medium">
{% call links.link(
url_for(
'calendar.slots.slot.get',
'calendar.slots.slot.defpage_slot_detail',
calendar_slug=calendar.slug,
slot_id=entry.slot.id
),

View File

@@ -1,7 +1,7 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='markets-row', oob=oob) %}
{% call links.link(url_for('markets.home'), hx_select_search) %}
{% call links.link(url_for('markets.defpage_events_markets'), hx_select_search) %}
<i class="fa fa-shopping-bag" aria-hidden="true"></i>
<div>
Markets

View File

@@ -77,7 +77,7 @@
<a
class="relative inline-flex items-center justify-center text-emerald-700"
href="{{ url_for('tickets.my_tickets') }}"
href="{{ url_for('tickets.defpage_my_tickets') }}"
>
<span class="relative inline-flex items-center justify-center">
<i class="fa-solid fa-shopping-cart text-2xl" aria-hidden="true"></i>
@@ -166,7 +166,7 @@
<a
class="relative inline-flex items-center justify-center text-emerald-700"
href="{{ url_for('tickets.my_tickets') }}"
href="{{ url_for('tickets.defpage_my_tickets') }}"
>
<span class="relative inline-flex items-center justify-center">
<i class="fa-solid fa-shopping-cart text-2xl" aria-hidden="true"></i>

View File

@@ -14,7 +14,7 @@
<div class="space-y-2 mb-4">
{% for ticket in created_tickets %}
<a
href="{{ url_for('tickets.ticket_detail', code=ticket.code) }}"
href="{{ url_for('tickets.defpage_ticket_detail', code=ticket.code) }}"
class="flex items-center justify-between p-2 rounded-lg bg-white border border-emerald-100 hover:border-emerald-300 transition text-sm"
>
<div class="flex items-center gap-2">
@@ -34,7 +34,7 @@
<div class="mt-3 flex gap-2">
<a
href="{{ url_for('tickets.my_tickets') }}"
href="{{ url_for('tickets.defpage_my_tickets') }}"
class="text-sm text-emerald-700 hover:text-emerald-900 underline"
>
View all my tickets

View File

@@ -1,7 +1,7 @@
<section id="ticket-detail" class="{{styles.list_container}} max-w-lg mx-auto">
{# Back link #}
<a href="{{ url_for('tickets.my_tickets') }}"
<a href="{{ url_for('tickets.defpage_my_tickets') }}"
class="inline-flex items-center gap-1 text-sm text-stone-500 hover:text-stone-700 mb-4">
<i class="fa fa-arrow-left" aria-hidden="true"></i>
Back to my tickets

View File

@@ -5,7 +5,7 @@
<div class="space-y-4">
{% for ticket in tickets %}
<a
href="{{ url_for('tickets.ticket_detail', code=ticket.code) }}"
href="{{ url_for('tickets.defpage_ticket_detail', code=ticket.code) }}"
class="block rounded-xl border border-stone-200 bg-white p-4 hover:shadow-md transition"
>
<div class="flex items-start justify-between gap-4">