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:
@@ -284,3 +284,116 @@ def delete_page(
|
||||
"""Delete a content page."""
|
||||
content_page_service.delete_page_or_raise(db, page_id)
|
||||
db.commit()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# HOMEPAGE SECTIONS MANAGEMENT
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class HomepageSectionsResponse(BaseModel):
|
||||
"""Response containing homepage sections with platform language info."""
|
||||
|
||||
sections: dict | None = None
|
||||
supported_languages: list[str] = Field(default_factory=lambda: ["fr", "de", "en"])
|
||||
default_language: str = "fr"
|
||||
|
||||
|
||||
class SectionUpdateResponse(BaseModel):
|
||||
"""Response after updating sections."""
|
||||
|
||||
message: str
|
||||
sections: dict | None = None
|
||||
|
||||
|
||||
@router.get("/{page_id}/sections", response_model=HomepageSectionsResponse)
|
||||
def get_page_sections(
|
||||
page_id: int,
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Get homepage sections for a content page.
|
||||
|
||||
Returns sections along with platform language settings for the editor.
|
||||
"""
|
||||
page = content_page_service.get_page_by_id_or_raise(db, page_id)
|
||||
|
||||
# Get platform languages
|
||||
platform = page.platform
|
||||
supported_languages = (
|
||||
platform.supported_languages if platform else ["fr", "de", "en"]
|
||||
)
|
||||
default_language = platform.default_language if platform else "fr"
|
||||
|
||||
return {
|
||||
"sections": page.sections,
|
||||
"supported_languages": supported_languages,
|
||||
"default_language": default_language,
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{page_id}/sections", response_model=SectionUpdateResponse)
|
||||
def update_page_sections(
|
||||
page_id: int,
|
||||
sections: dict,
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Update all homepage sections at once.
|
||||
|
||||
Expected structure:
|
||||
{
|
||||
"hero": { ... },
|
||||
"features": { ... },
|
||||
"pricing": { ... },
|
||||
"cta": { ... }
|
||||
}
|
||||
"""
|
||||
page = content_page_service.update_homepage_sections(
|
||||
db,
|
||||
page_id=page_id,
|
||||
sections=sections,
|
||||
updated_by=current_user.id,
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": "Sections updated successfully",
|
||||
"sections": page.sections,
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{page_id}/sections/{section_name}", response_model=SectionUpdateResponse)
|
||||
def update_single_section(
|
||||
page_id: int,
|
||||
section_name: str,
|
||||
section_data: dict,
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Update a single section (hero, features, pricing, or cta).
|
||||
|
||||
section_name must be one of: hero, features, pricing, cta
|
||||
"""
|
||||
if section_name not in ["hero", "features", "pricing", "cta"]:
|
||||
raise ValidationException(
|
||||
message=f"Invalid section name: {section_name}. Must be one of: hero, features, pricing, cta",
|
||||
field="section_name",
|
||||
)
|
||||
|
||||
page = content_page_service.update_single_section(
|
||||
db,
|
||||
page_id=page_id,
|
||||
section_name=section_name,
|
||||
section_data=section_data,
|
||||
updated_by=current_user.id,
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"Section '{section_name}' updated successfully",
|
||||
"sections": page.sections,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user