feat: implement section-based homepage management system

Add structured JSON sections to ContentPage for multi-language homepage editing:

Database:
- Add `sections` JSON column to content_pages table
- Migration z8i9j0k1l2m3 adds the column

Schema:
- New models/schema/homepage_sections.py with Pydantic schemas
- TranslatableText for language-keyed translations
- HeroSection, FeaturesSection, PricingSection, CTASection

Templates:
- New section partials in app/templates/platform/sections/
- Updated homepage-default.html to render sections dynamically
- Fallback to placeholder content when sections not configured

Service:
- update_homepage_sections() - validate and save all sections
- update_single_section() - update individual section
- get_default_sections() - empty structure for new homepages

API:
- GET /{page_id}/sections - get sections with platform languages
- PUT /{page_id}/sections - update all sections
- PUT /{page_id}/sections/{section_name} - update single section

Admin UI:
- Section editor appears when editing homepage (slug='home')
- Language tabs from platform.supported_languages
- Accordion sections for Hero, Features, Pricing, CTA
- Button/feature card repeaters with add/remove

Also fixes broken line 181 in z4e5f6a7b8c9 migration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-23 14:31:23 +01:00
parent 3d3b8cae22
commit dca52d004e
14 changed files with 1377 additions and 176 deletions

View File

@@ -872,6 +872,130 @@ class ContentPageService:
if not success:
raise ContentPageNotFoundException(identifier=page_id)
# =========================================================================
# Homepage Sections Management
# =========================================================================
@staticmethod
def update_homepage_sections(
db: Session,
page_id: int,
sections: dict,
updated_by: int | None = None,
) -> ContentPage:
"""
Update homepage sections with validation.
Args:
db: Database session
page_id: Content page ID
sections: Homepage sections dict (validated against HomepageSections schema)
updated_by: User ID making the update
Returns:
Updated ContentPage
Raises:
ContentPageNotFoundException: If page not found
ValidationError: If sections schema invalid
"""
from models.schema.homepage_sections import HomepageSections
page = ContentPageService.get_page_by_id_or_raise(db, page_id)
# Validate sections against schema
validated = HomepageSections(**sections)
# Update page
page.sections = validated.model_dump()
page.updated_by = updated_by
db.flush()
db.refresh(page)
logger.info(f"[CMS] Updated homepage sections for page_id={page_id}")
return page
@staticmethod
def update_single_section(
db: Session,
page_id: int,
section_name: str,
section_data: dict,
updated_by: int | None = None,
) -> ContentPage:
"""
Update a single section within homepage sections.
Args:
db: Database session
page_id: Content page ID
section_name: Section to update (hero, features, pricing, cta)
section_data: Section configuration dict
updated_by: User ID making the update
Returns:
Updated ContentPage
Raises:
ContentPageNotFoundException: If page not found
ValueError: If section name is invalid
"""
from models.schema.homepage_sections import (
HeroSection,
FeaturesSection,
PricingSection,
CTASection,
)
SECTION_SCHEMAS = {
"hero": HeroSection,
"features": FeaturesSection,
"pricing": PricingSection,
"cta": CTASection,
}
if section_name not in SECTION_SCHEMAS:
raise ValueError(f"Invalid section name: {section_name}. Must be one of: {list(SECTION_SCHEMAS.keys())}")
page = ContentPageService.get_page_by_id_or_raise(db, page_id)
# Validate section data against its schema
schema = SECTION_SCHEMAS[section_name]
validated_section = schema(**section_data)
# Initialize sections if needed
current_sections = page.sections or {}
current_sections[section_name] = validated_section.model_dump()
page.sections = current_sections
page.updated_by = updated_by
db.flush()
db.refresh(page)
logger.info(f"[CMS] Updated section '{section_name}' for page_id={page_id}")
return page
@staticmethod
def get_default_sections(languages: list[str] | None = None) -> dict:
"""
Get empty sections structure for new homepage.
Args:
languages: List of language codes from platform.supported_languages.
Defaults to ['fr', 'de', 'en'] if not provided.
Returns:
Empty sections dict with language placeholders
"""
from models.schema.homepage_sections import HomepageSections
if languages is None:
languages = ["fr", "de", "en"]
return HomepageSections.get_empty_structure(languages).model_dump()
# Singleton instance
content_page_service = ContentPageService()