refactor: clean up legacy models and migrate remaining schemas
Delete empty stub files from models/database/: - audit.py, backup.py, configuration.py, monitoring.py - notification.py, payment.py, search.py, task.py Delete re-export files: - models/database/subscription.py → app.modules.billing.models - models/database/architecture_scan.py → app.modules.dev_tools.models - models/database/test_run.py → app.modules.dev_tools.models - models/schema/subscription.py → app.modules.billing.schemas - models/schema/marketplace.py (empty) - models/schema/monitoring.py (empty) Migrate schemas to canonical module locations: - billing.py → app/modules/billing/schemas/ - vendor_product.py → app/modules/catalog/schemas/ - homepage_sections.py → app/modules/cms/schemas/ Keep as CORE (framework-level, used everywhere): - models/schema/: admin, auth, base, company, email, image, media, team, vendor* - models/database/: admin*, base, company, email, feature, media, platform*, user, vendor* Update 30+ files to use canonical import locations. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ from app.modules.cms.schemas.content_page import (
|
||||
ContentPageCreate,
|
||||
ContentPageUpdate,
|
||||
ContentPageResponse,
|
||||
HomepageSectionsResponse,
|
||||
HomepageSectionsResponse as ContentPageHomepageSectionsResponse,
|
||||
SectionUpdateResponse,
|
||||
# Vendor schemas
|
||||
VendorContentPageCreate,
|
||||
@@ -18,19 +18,46 @@ from app.modules.cms.schemas.content_page import (
|
||||
PublicContentPageResponse,
|
||||
ContentPageListItem,
|
||||
)
|
||||
from app.modules.cms.schemas.homepage_sections import (
|
||||
# Translatable text
|
||||
TranslatableText,
|
||||
# Section components
|
||||
HeroButton,
|
||||
HeroSection,
|
||||
FeatureCard,
|
||||
FeaturesSection,
|
||||
PricingSection,
|
||||
CTASection,
|
||||
# Main structure
|
||||
HomepageSections,
|
||||
# API schemas
|
||||
SectionUpdateRequest,
|
||||
HomepageSectionsResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Admin
|
||||
# Content Page - Admin
|
||||
"ContentPageCreate",
|
||||
"ContentPageUpdate",
|
||||
"ContentPageResponse",
|
||||
"HomepageSectionsResponse",
|
||||
"ContentPageHomepageSectionsResponse",
|
||||
"SectionUpdateResponse",
|
||||
# Vendor
|
||||
# Content Page - Vendor
|
||||
"VendorContentPageCreate",
|
||||
"VendorContentPageUpdate",
|
||||
"CMSUsageResponse",
|
||||
# Public
|
||||
# Content Page - Public
|
||||
"PublicContentPageResponse",
|
||||
"ContentPageListItem",
|
||||
# Homepage Sections
|
||||
"TranslatableText",
|
||||
"HeroButton",
|
||||
"HeroSection",
|
||||
"FeatureCard",
|
||||
"FeaturesSection",
|
||||
"PricingSection",
|
||||
"CTASection",
|
||||
"HomepageSections",
|
||||
"SectionUpdateRequest",
|
||||
"HomepageSectionsResponse",
|
||||
]
|
||||
|
||||
174
app/modules/cms/schemas/homepage_sections.py
Normal file
174
app/modules/cms/schemas/homepage_sections.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Homepage Section Schemas with Dynamic Multi-Language Support.
|
||||
|
||||
Language codes are NOT hardcoded - they come from platform.supported_languages.
|
||||
The TranslatableText class stores translations as a dict where keys are language codes.
|
||||
|
||||
Example JSON structure:
|
||||
{
|
||||
"hero": {
|
||||
"enabled": true,
|
||||
"title": {"translations": {"fr": "Bienvenue", "en": "Welcome"}},
|
||||
"subtitle": {"translations": {...}},
|
||||
"buttons": [...]
|
||||
},
|
||||
"features": {...},
|
||||
"pricing": {...},
|
||||
"cta": {...}
|
||||
}
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class TranslatableText(BaseModel):
|
||||
"""
|
||||
Text field with translations stored as language-keyed dict.
|
||||
|
||||
Languages come from platform.supported_languages (not hardcoded).
|
||||
Use .get(lang, default_lang) to retrieve translation with fallback.
|
||||
"""
|
||||
|
||||
translations: dict[str, str] = Field(
|
||||
default_factory=dict, description="Language code -> translated text mapping"
|
||||
)
|
||||
|
||||
def get(self, lang: str, default_lang: str = "fr") -> str:
|
||||
"""Get translation with fallback to default language."""
|
||||
return self.translations.get(lang) or self.translations.get(default_lang) or ""
|
||||
|
||||
def set(self, lang: str, text: str) -> None:
|
||||
"""Set translation for a language."""
|
||||
self.translations[lang] = text
|
||||
|
||||
def has_translation(self, lang: str) -> bool:
|
||||
"""Check if translation exists for language."""
|
||||
return bool(self.translations.get(lang))
|
||||
|
||||
|
||||
class HeroButton(BaseModel):
|
||||
"""Button in hero or CTA section."""
|
||||
|
||||
text: TranslatableText = Field(default_factory=TranslatableText)
|
||||
url: str = ""
|
||||
style: str = Field(default="primary", description="primary, secondary, outline")
|
||||
|
||||
|
||||
class HeroSection(BaseModel):
|
||||
"""Hero section configuration."""
|
||||
|
||||
enabled: bool = True
|
||||
badge_text: Optional[TranslatableText] = None
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
subtitle: TranslatableText = Field(default_factory=TranslatableText)
|
||||
background_type: str = Field(
|
||||
default="gradient", description="gradient, image, solid"
|
||||
)
|
||||
background_image: Optional[str] = None
|
||||
buttons: list[HeroButton] = Field(default_factory=list)
|
||||
|
||||
|
||||
class FeatureCard(BaseModel):
|
||||
"""Single feature in features section."""
|
||||
|
||||
icon: str = ""
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
description: TranslatableText = Field(default_factory=TranslatableText)
|
||||
|
||||
|
||||
class FeaturesSection(BaseModel):
|
||||
"""Features section configuration."""
|
||||
|
||||
enabled: bool = True
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
features: list[FeatureCard] = Field(default_factory=list)
|
||||
layout: str = Field(default="grid", description="grid, list, cards")
|
||||
|
||||
|
||||
class PricingSection(BaseModel):
|
||||
"""Pricing section configuration."""
|
||||
|
||||
enabled: bool = True
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
use_subscription_tiers: bool = Field(
|
||||
default=True, description="Pull pricing from subscription_tiers table dynamically"
|
||||
)
|
||||
|
||||
|
||||
class CTASection(BaseModel):
|
||||
"""Call-to-action section configuration."""
|
||||
|
||||
enabled: bool = True
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
buttons: list[HeroButton] = Field(default_factory=list)
|
||||
background_type: str = Field(
|
||||
default="gradient", description="gradient, image, solid"
|
||||
)
|
||||
|
||||
|
||||
class HomepageSections(BaseModel):
|
||||
"""Complete homepage sections structure."""
|
||||
|
||||
hero: Optional[HeroSection] = None
|
||||
features: Optional[FeaturesSection] = None
|
||||
pricing: Optional[PricingSection] = None
|
||||
cta: Optional[CTASection] = None
|
||||
|
||||
@classmethod
|
||||
def get_empty_structure(cls, languages: list[str]) -> "HomepageSections":
|
||||
"""
|
||||
Create empty section structure with language placeholders.
|
||||
|
||||
Args:
|
||||
languages: List of language codes from platform.supported_languages
|
||||
|
||||
Returns:
|
||||
HomepageSections with empty translations for all languages
|
||||
"""
|
||||
|
||||
def make_translatable(langs: list[str]) -> TranslatableText:
|
||||
return TranslatableText(translations={lang: "" for lang in langs})
|
||||
|
||||
return cls(
|
||||
hero=HeroSection(
|
||||
title=make_translatable(languages),
|
||||
subtitle=make_translatable(languages),
|
||||
buttons=[],
|
||||
),
|
||||
features=FeaturesSection(
|
||||
title=make_translatable(languages),
|
||||
features=[],
|
||||
),
|
||||
pricing=PricingSection(
|
||||
title=make_translatable(languages),
|
||||
use_subscription_tiers=True,
|
||||
),
|
||||
cta=CTASection(
|
||||
title=make_translatable(languages),
|
||||
buttons=[],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Request/Response Schemas
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class SectionUpdateRequest(BaseModel):
|
||||
"""Request to update a single section."""
|
||||
|
||||
section_name: str = Field(..., description="hero, features, pricing, or cta")
|
||||
section_data: dict = Field(..., description="Section configuration")
|
||||
|
||||
|
||||
class HomepageSectionsResponse(BaseModel):
|
||||
"""Response containing all homepage sections with platform language info."""
|
||||
|
||||
sections: Optional[HomepageSections] = None
|
||||
supported_languages: list[str] = Field(default_factory=lambda: ["fr", "de", "en"])
|
||||
default_language: str = "fr"
|
||||
@@ -899,7 +899,7 @@ class ContentPageService:
|
||||
ContentPageNotFoundException: If page not found
|
||||
ValidationError: If sections schema invalid
|
||||
"""
|
||||
from models.schema.homepage_sections import HomepageSections
|
||||
from app.modules.cms.schemas import HomepageSections
|
||||
|
||||
page = ContentPageService.get_page_by_id_or_raise(db, page_id)
|
||||
|
||||
@@ -941,7 +941,7 @@ class ContentPageService:
|
||||
ContentPageNotFoundException: If page not found
|
||||
ValueError: If section name is invalid
|
||||
"""
|
||||
from models.schema.homepage_sections import (
|
||||
from app.modules.cms.schemas import (
|
||||
HeroSection,
|
||||
FeaturesSection,
|
||||
PricingSection,
|
||||
@@ -989,7 +989,7 @@ class ContentPageService:
|
||||
Returns:
|
||||
Empty sections dict with language placeholders
|
||||
"""
|
||||
from models.schema.homepage_sections import HomepageSections
|
||||
from app.modules.cms.schemas import HomepageSections
|
||||
|
||||
if languages is None:
|
||||
languages = ["fr", "de", "en"]
|
||||
|
||||
Reference in New Issue
Block a user