Populate the client website template with catalog feature flags.

Extract always-on public/portal/UTM plus optional email_sms, directmail, blog, payments, social, and social_ai so new client sites can be bootstrapped from this seed.

Refs #1
Refs #2

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 07:55:26 -05:00
co-authored by Cursor
parent 45d0888d33
commit 787f0e48fb
297 changed files with 32534 additions and 3 deletions
+105
View File
@@ -0,0 +1,105 @@
"""Optional-app registry. Apps register from AppConfig.ready() — never from always-on imports."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Callable
from django.urls import reverse
@dataclass(frozen=True)
class NavItem:
section: str
label: str
url_name: str
group: str = "Overview"
order: int = 100
GROUP_ORDER = {
"Overview": 10,
"Outreach": 20,
"Content": 30,
"Social": 40,
"Billing": 50,
}
_portal_nav: list[NavItem] = []
_public_nav: list[NavItem] = []
_dashboard_collectors: list[Callable] = []
_dispatchers: list[Callable] = []
_features: set[str] = set()
def register_feature(name: str) -> None:
_features.add(name)
def enabled_features() -> frozenset[str]:
return frozenset(_features)
def register_portal_nav(
*,
section: str,
label: str,
url_name: str,
group: str = "Overview",
order: int = 100,
) -> None:
item = NavItem(
section=section, label=label, url_name=url_name, group=group, order=order
)
_portal_nav.append(item)
def register_public_nav(
*,
section: str,
label: str,
url_name: str,
order: int = 100,
) -> None:
_public_nav.append(
NavItem(section=section, label=label, url_name=url_name, order=order)
)
def register_dashboard_collector(fn: Callable) -> None:
"""fn(request) -> dict merged into dashboard home context."""
_dashboard_collectors.append(fn)
def register_dispatcher(fn: Callable) -> None:
"""fn() -> int count of items enqueued. Called by dispatch_due."""
_dispatchers.append(fn)
def portal_nav() -> list[NavItem]:
return sorted(
_portal_nav,
key=lambda i: (GROUP_ORDER.get(i.group, 99), i.order, i.label),
)
def public_nav() -> list[NavItem]:
return sorted(_public_nav, key=lambda i: (i.order, i.label))
def collect_dashboard_context(request) -> dict:
extra: dict = {}
for fn in _dashboard_collectors:
extra.update(fn(request) or {})
return extra
def run_dispatchers() -> int:
total = 0
for fn in _dispatchers:
total += int(fn() or 0)
return total
def reverse_nav(item: NavItem) -> str:
return reverse(item.url_name)