feat(cms): CMS-driven homepages, products section, placeholder resolution
Some checks failed
CI / ruff (push) Successful in 11s
CI / pytest (push) Failing after 51m41s
CI / validate (push) Successful in 26s
CI / dependency-scanning (push) Successful in 32s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped

- Add ProductCard/ProductsSection schema and _products.html section macro
- Rewrite seed script with 3-platform homepage sections (wizard, OMS, loyalty),
  platform marketing pages, and store defaults with {{store_name}} placeholders
- Add resolve_placeholders() to ContentPageService for store default pages
- Fix SQLAlchemy filter bugs: replace Python `is None` with `.is_(None)` across
  all ContentPageService query methods (was silently breaking all platform page lookups)
- Remove hardcoded orion fallback and delete homepage-orion.html
- Add placeholder hint box with click-to-copy in admin content page editor
- Export ProductCard/ProductsSection from cms schemas __init__

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 12:12:20 +01:00
parent ef9ea29643
commit adbecd360b
11 changed files with 1132 additions and 916 deletions

View File

@@ -31,6 +31,8 @@ from app.modules.cms.schemas.homepage_sections import (
HomepageSections,
HomepageSectionsResponse,
PricingSection,
ProductCard,
ProductsSection,
# API schemas
SectionUpdateRequest,
# Translatable text
@@ -92,6 +94,8 @@ __all__ = [
"HeroSection",
"FeatureCard",
"FeaturesSection",
"ProductCard",
"ProductsSection",
"PricingSection",
"CTASection",
"HomepageSections",

View File

@@ -77,6 +77,25 @@ class FeatureCard(BaseModel):
description: TranslatableText = Field(default_factory=TranslatableText)
class ProductCard(BaseModel):
"""Single product/offering card in products section."""
icon: str = ""
title: TranslatableText = Field(default_factory=TranslatableText)
description: TranslatableText = Field(default_factory=TranslatableText)
url: str = ""
badge: TranslatableText | None = None
class ProductsSection(BaseModel):
"""Product/offering showcase section (e.g. wizard.lu multi-product landing)."""
enabled: bool = True
title: TranslatableText = Field(default_factory=TranslatableText)
subtitle: TranslatableText | None = None
products: list[ProductCard] = Field(default_factory=list)
class FeaturesSection(BaseModel):
"""Features section configuration."""
@@ -114,6 +133,7 @@ class HomepageSections(BaseModel):
"""Complete homepage sections structure."""
hero: HeroSection | None = None
products: ProductsSection | None = None
features: FeaturesSection | None = None
pricing: PricingSection | None = None
cta: CTASection | None = None
@@ -139,6 +159,10 @@ class HomepageSections(BaseModel):
subtitle=make_translatable(languages),
buttons=[],
),
products=ProductsSection(
title=make_translatable(languages),
products=[],
),
features=FeaturesSection(
title=make_translatable(languages),
features=[],
@@ -162,7 +186,7 @@ class HomepageSections(BaseModel):
class SectionUpdateRequest(BaseModel):
"""Request to update a single section."""
section_name: str = Field(..., description="hero, features, pricing, or cta")
section_name: str = Field(..., description="hero, products, features, pricing, or cta")
section_data: dict = Field(..., description="Section configuration")