Files
orion/scripts/seed/create_default_content_pages.py
Samir Boulahtit b8aa484653
Some checks failed
CI / dependency-scanning (push) Successful in 28s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
CI / ruff (push) Successful in 12s
CI / pytest (push) Failing after 47m21s
CI / validate (push) Successful in 25s
feat(i18n): complete post-launch i18n phases 5-8
- Phase 5: Translate homepage-modern.html (~90 new locale keys, all
  hardcoded strings replaced with _() calls for dashboard mock,
  features, pricing tiers, testimonial sections)
- Phase 6: Translate homepage-minimal.html (17 new locale keys for
  fallback content, features, and CTA sections)
- Phase 7: Add multi-language page.title/content support with
  title_translations and content_translations JSON columns, Alembic
  migration cms_002, translated title/content resolution in templates,
  and seed script updates with tt() helper
- Phase 8: Complete lb.json audit — fill 6 missing keys (messages,
  confirmations), also backfill same keys in fr.json and de.json

All 4 locale files now have 340 keys with full parity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 05:50:06 +01:00

1190 lines
49 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Create Default Content Pages for All Platforms (CMS)
This script creates:
1. Platform Homepages (slug="home", is_platform_page=True, sections JSON)
- wizard.lu: multi-product landing (hero + products + cta)
- omsflow.lu: OMS-focused (hero + features + pricing + cta)
- rewardflow.lu: loyalty-focused (hero + features + pricing + cta)
2. Platform Marketing Pages (is_platform_page=True)
- about, contact, faq, privacy, terms per platform
3. Store Default Pages (is_platform_page=False, store_id=NULL)
- Generic templates with {{store_name}} placeholders
- OMS: about, contact, faq, shipping, returns, privacy, terms
- Loyalty: about, contact, faq, privacy, terms
Prerequisites:
- Database migrations must be applied
- content_pages table must exist
- Platforms must exist (run init_production.py first)
Usage:
python scripts/seed/create_default_content_pages.py
"""
import sys
from datetime import UTC, datetime
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
import contextlib
from sqlalchemy import select
from sqlalchemy.orm import Session
# Register all models with SQLAlchemy so string-based relationships resolve
for _mod in [
"app.modules.billing.models",
"app.modules.inventory.models",
"app.modules.cart.models",
"app.modules.messaging.models",
"app.modules.loyalty.models",
"app.modules.catalog.models",
"app.modules.customers.models",
"app.modules.orders.models",
"app.modules.marketplace.models",
"app.modules.cms.models",
]:
with contextlib.suppress(ImportError):
__import__(_mod)
from app.core.database import SessionLocal
from app.modules.cms.models import ContentPage
from app.modules.tenancy.models import Platform
# ============================================================================
# HELPER: TranslatableText dict builder
# ============================================================================
def t(fr: str, en: str, de: str, lb: str | None = None) -> dict:
"""Build a TranslatableText-compatible dict."""
d = {"translations": {"fr": fr, "en": en, "de": de}}
if lb is not None:
d["translations"]["lb"] = lb
return d
def tt(en: str, fr: str, de: str, lb: str | None = None) -> dict:
"""Build a simple language-keyed dict for title_translations / content_translations."""
d = {"en": en, "fr": fr, "de": de}
if lb is not None:
d["lb"] = lb
return d
# ============================================================================
# PLATFORM HOMEPAGE SECTIONS (slug="home", is_platform_page=True)
# ============================================================================
def _wizard_homepage_sections() -> dict:
"""Wizard.lu (main) — multi-product landing page."""
return {
"hero": {
"enabled": True,
"title": t(
"Votre boîte à outils numérique pour votre entreprise",
"Your Business Digital Toolkit",
"Ihr digitales Business-Toolkit",
),
"subtitle": t(
"Gestion des commandes, programmes de fidélité et création de sites web — tout ce dont votre entreprise luxembourgeoise a besoin.",
"Order management, loyalty programs, and website building — everything your Luxembourg business needs.",
"Bestellverwaltung, Treueprogramme und Website-Erstellung — alles, was Ihr luxemburgisches Unternehmen braucht.",
),
"background_type": "gradient",
"buttons": [
{
"text": t(
"Découvrir nos solutions",
"Discover Our Solutions",
"Unsere Lösungen entdecken",
),
"url": "#products",
"style": "primary",
},
],
},
"products": {
"enabled": True,
"title": t(
"Nos solutions",
"Our Solutions",
"Unsere Lösungen",
),
"subtitle": t(
"Des outils conçus pour le commerce luxembourgeois",
"Tools built for Luxembourg commerce",
"Werkzeuge für den luxemburgischen Handel",
),
"products": [
{
"icon": "clipboard-list",
"title": t(
"Gestion des Commandes (OMS)",
"Order Management (OMS)",
"Bestellverwaltung (OMS)",
),
"description": t(
"Synchronisez vos commandes Letzshop, gérez les stocks et générez des factures conformes à la TVA.",
"Sync your Letzshop orders, manage inventory, and generate VAT-compliant invoices.",
"Synchronisieren Sie Ihre Letzshop-Bestellungen, verwalten Sie Lagerbestände und erstellen Sie MwSt-konforme Rechnungen.",
),
"url": "/platforms/oms/",
"link_text": t("En savoir plus", "Learn More", "Mehr erfahren"),
},
{
"icon": "heart",
"title": t(
"Programme de Fidélité",
"Loyalty Program",
"Treueprogramm",
),
"description": t(
"Créez des programmes de fidélité avec points, récompenses et niveaux pour fidéliser vos clients.",
"Create loyalty programs with points, rewards, and tiers to retain your customers.",
"Erstellen Sie Treueprogramme mit Punkten, Prämien und Stufen, um Ihre Kunden zu binden.",
),
"url": "/platforms/loyalty/",
"link_text": t("En savoir plus", "Learn More", "Mehr erfahren"),
},
{
"icon": "globe-alt",
"title": t(
"Création de Sites Web",
"Website Builder",
"Website-Erstellung",
),
"description": t(
"Créez votre présence en ligne avec notre outil de création de sites web intuitif.",
"Build your online presence with our intuitive website builder.",
"Erstellen Sie Ihre Online-Präsenz mit unserem intuitiven Website-Baukasten.",
),
"url": "",
"badge": t("Bientôt", "Coming Soon", "Demnächst"),
},
],
},
"cta": {
"enabled": True,
"title": t(
"Prêt à digitaliser votre entreprise ?",
"Ready to Digitalize Your Business?",
"Bereit, Ihr Unternehmen zu digitalisieren?",
),
"subtitle": t(
"Rejoignez les entreprises luxembourgeoises qui font confiance à nos solutions.",
"Join Luxembourg businesses that trust our solutions.",
"Schließen Sie sich luxemburgischen Unternehmen an, die unseren Lösungen vertrauen.",
),
"background_type": "gradient",
"buttons": [
{
"text": t(
"Commencer maintenant",
"Get Started Now",
"Jetzt starten",
),
"url": "/signup",
"style": "primary",
},
{
"text": t(
"Nous contacter",
"Contact Us",
"Kontaktieren Sie uns",
),
"url": "/contact",
"style": "secondary",
},
],
},
}
def _oms_homepage_sections() -> dict:
"""OMS (omsflow.lu) — order management focused homepage."""
return {
"hero": {
"enabled": True,
"badge_text": t(
"Essai gratuit — Aucune carte de crédit requise",
"Free Trial — No Credit Card Required",
"Kostenlose Testversion — Keine Kreditkarte erforderlich",
),
"title": t(
"OMS léger pour les vendeurs Letzshop",
"Lightweight OMS for Letzshop Sellers",
"Leichtes OMS für Letzshop-Verkäufer",
),
"subtitle": t(
"Gestion des commandes, stocks et facturation conçue pour le e-commerce luxembourgeois. Arrêtez de jongler avec les tableurs. Gérez votre entreprise.",
"Order management, inventory, and invoicing built for Luxembourg e-commerce. Stop juggling spreadsheets. Start running your business.",
"Bestellverwaltung, Lager und Rechnungsstellung für den luxemburgischen E-Commerce. Schluss mit Tabellenkalkulationen. Führen Sie Ihr Geschäft.",
),
"background_type": "gradient",
"buttons": [
{
"text": t(
"Essai gratuit",
"Start Free Trial",
"Kostenlos testen",
),
"url": "/signup",
"style": "primary",
},
{
"text": t(
"Trouvez votre boutique Letzshop",
"Find Your Letzshop Shop",
"Finden Sie Ihren Letzshop",
),
"url": "#find-shop",
"style": "secondary",
},
],
},
"features": {
"enabled": True,
"title": t(
"Tout ce dont un vendeur Letzshop a besoin",
"Everything a Letzshop Seller Needs",
"Alles, was ein Letzshop-Verkäufer braucht",
),
"subtitle": t(
"Les outils opérationnels que Letzshop ne fournit pas",
"The operational tools Letzshop doesn't provide",
"Die operativen Tools, die Letzshop nicht bietet",
),
"layout": "grid",
"features": [
{
"icon": "refresh",
"title": t(
"Synchronisation Letzshop",
"Letzshop Order Sync",
"Letzshop-Synchronisierung",
),
"description": t(
"Les commandes se synchronisent automatiquement. Confirmez et ajoutez le suivi depuis Orion.",
"Orders sync automatically. Confirm and add tracking directly from Orion.",
"Bestellungen werden automatisch synchronisiert. Bestätigen und Tracking direkt von Orion hinzufügen.",
),
},
{
"icon": "cube",
"title": t(
"Gestion des stocks",
"Inventory Management",
"Lagerverwaltung",
),
"description": t(
"Suivez vos stocks en temps réel avec emplacements d'entrepôt et bons de commande.",
"Track inventory in real-time with warehouse locations and purchase orders.",
"Verfolgen Sie Lagerbestände in Echtzeit mit Lagerstandorten und Bestellungen.",
),
},
{
"icon": "document-text",
"title": t(
"Facturation TVA UE",
"EU VAT Invoicing",
"EU-MwSt-Rechnungen",
),
"description": t(
"Générez des factures PDF conformes avec la TVA correcte pour tout pays UE.",
"Generate compliant PDF invoices with correct VAT for any EU country.",
"Erstellen Sie konforme PDF-Rechnungen mit korrekter MwSt für jedes EU-Land.",
),
},
{
"icon": "users",
"title": t(
"Données clients",
"Customer Data",
"Kundendaten",
),
"description": t(
"Possédez vos données clients. Exportez pour le marketing et la fidélisation.",
"Own your customer data. Export for marketing and loyalty building.",
"Besitzen Sie Ihre Kundendaten. Exportieren Sie für Marketing und Kundenbindung.",
),
},
],
},
"pricing": {
"enabled": True,
"title": t(
"Tarification simple et transparente",
"Simple, Transparent Pricing",
"Einfache, transparente Preise",
),
"subtitle": t(
"Choisissez le plan adapté à votre entreprise. Tous les plans incluent un essai gratuit.",
"Choose the plan that fits your business. All plans include a free trial.",
"Wählen Sie den Plan, der zu Ihrem Unternehmen passt. Alle Pläne beinhalten eine kostenlose Testversion.",
),
"use_subscription_tiers": True,
"monthly_label": t("Mensuel", "Monthly", "Monatlich"),
"annual_label": t("Annuel", "Annual", "Jährlich"),
"save_text": t("Économisez 2 mois !", "Save 2 months!", "Sparen Sie 2 Monate!"),
"popular_badge": t("LE PLUS POPULAIRE", "MOST POPULAR", "AM BELIEBTESTEN"),
"cta_text": t("Essai gratuit", "Start Free Trial", "Kostenlos testen"),
"per_month_label": t("/mois", "/month", "/Monat"),
"per_year_label": t("/an", "/year", "/Jahr"),
"coming_soon_text": t(
"Plans tarifaires bientôt disponibles",
"Pricing plans coming soon",
"Preispläne demnächst verfügbar",
),
},
"cta": {
"enabled": True,
"title": t(
"Prêt à optimiser vos commandes ?",
"Ready to Streamline Your Orders?",
"Bereit, Ihre Bestellungen zu optimieren?",
),
"subtitle": t(
"Rejoignez les vendeurs Letzshop qui font confiance à Orion pour leur gestion de commandes.",
"Join Letzshop stores who trust Orion for their order management.",
"Schließen Sie sich Letzshop-Händlern an, die Orion für ihre Bestellverwaltung vertrauen.",
),
"background_type": "gradient",
"buttons": [
{
"text": t(
"Essai gratuit",
"Start Free Trial",
"Kostenlos testen",
),
"url": "/signup",
"style": "primary",
},
],
},
}
def _loyalty_homepage_sections() -> dict:
"""Loyalty (rewardflow.lu) — loyalty program focused homepage."""
return {
"hero": {
"enabled": True,
"badge_text": t(
"Essai gratuit — Aucune carte de crédit requise",
"Free Trial — No Credit Card Required",
"Kostenlose Testversion — Keine Kreditkarte erforderlich",
),
"title": t(
"Fidélisation client simplifiée",
"Customer Loyalty Made Simple",
"Kundentreue leicht gemacht",
),
"subtitle": t(
"Créez des programmes de fidélité engageants avec points, récompenses et niveaux. Conçu pour les commerces luxembourgeois.",
"Create engaging loyalty programs with points, rewards, and tiers. Built for Luxembourg businesses.",
"Erstellen Sie ansprechende Treueprogramme mit Punkten, Prämien und Stufen. Für luxemburgische Unternehmen.",
),
"background_type": "gradient",
"buttons": [
{
"text": t(
"Essai gratuit",
"Start Free Trial",
"Kostenlos testen",
),
"url": "/signup",
"style": "primary",
},
{
"text": t(
"Voir les fonctionnalités",
"See Features",
"Funktionen ansehen",
),
"url": "#features",
"style": "secondary",
},
],
},
"features": {
"enabled": True,
"title": t(
"Tout pour fidéliser vos clients",
"Everything to Build Customer Loyalty",
"Alles für die Kundenbindung",
),
"subtitle": t(
"Des outils puissants pour créer et gérer vos programmes de fidélité",
"Powerful tools to create and manage your loyalty programs",
"Leistungsstarke Tools zum Erstellen und Verwalten Ihrer Treueprogramme",
),
"layout": "grid",
"features": [
{
"icon": "star",
"title": t(
"Système de points",
"Points System",
"Punktesystem",
),
"description": t(
"Attribuez des points pour chaque achat. Règles flexibles et multiplicateurs personnalisables.",
"Award points for every purchase. Flexible rules and customizable multipliers.",
"Vergeben Sie Punkte für jeden Einkauf. Flexible Regeln und anpassbare Multiplikatoren.",
),
},
{
"icon": "gift",
"title": t(
"Catalogue de récompenses",
"Rewards Catalog",
"Prämienkatalog",
),
"description": t(
"Créez un catalogue de récompenses attrayant. Produits, remises ou expériences exclusives.",
"Create an attractive rewards catalog. Products, discounts, or exclusive experiences.",
"Erstellen Sie einen attraktiven Prämienkatalog. Produkte, Rabatte oder exklusive Erlebnisse.",
),
},
{
"icon": "trending-up",
"title": t(
"Gestion des niveaux",
"Tier Management",
"Stufenverwaltung",
),
"description": t(
"Bronze, Argent, Or — motivez vos clients avec des niveaux de fidélité progressifs.",
"Bronze, Silver, Gold — motivate customers with progressive loyalty tiers.",
"Bronze, Silber, Gold — motivieren Sie Kunden mit progressiven Treuestufen.",
),
},
{
"icon": "chart-bar",
"title": t(
"Analytique avancée",
"Advanced Analytics",
"Erweiterte Analytik",
),
"description": t(
"Suivez l'engagement, les taux de rétention et le ROI de vos programmes.",
"Track engagement, retention rates, and program ROI.",
"Verfolgen Sie Engagement, Bindungsraten und Programm-ROI.",
),
},
],
},
"pricing": {
"enabled": True,
"title": t(
"Tarification simple et transparente",
"Simple, Transparent Pricing",
"Einfache, transparente Preise",
),
"subtitle": t(
"Choisissez le plan adapté à votre programme de fidélité.",
"Choose the plan that fits your loyalty program.",
"Wählen Sie den Plan, der zu Ihrem Treueprogramm passt.",
),
"use_subscription_tiers": True,
"monthly_label": t("Mensuel", "Monthly", "Monatlich"),
"annual_label": t("Annuel", "Annual", "Jährlich"),
"save_text": t("Économisez 2 mois !", "Save 2 months!", "Sparen Sie 2 Monate!"),
"popular_badge": t("LE PLUS POPULAIRE", "MOST POPULAR", "AM BELIEBTESTEN"),
"cta_text": t("Essai gratuit", "Start Free Trial", "Kostenlos testen"),
"per_month_label": t("/mois", "/month", "/Monat"),
"per_year_label": t("/an", "/year", "/Jahr"),
"coming_soon_text": t(
"Plans tarifaires bientôt disponibles",
"Pricing plans coming soon",
"Preispläne demnächst verfügbar",
),
},
"cta": {
"enabled": True,
"title": t(
"Prêt à fidéliser vos clients ?",
"Ready to Build Customer Loyalty?",
"Bereit, Kundentreue aufzubauen?",
),
"subtitle": t(
"Commencez votre programme de fidélité dès aujourd'hui. Essai gratuit, sans engagement.",
"Start your loyalty program today. Free trial, no commitment.",
"Starten Sie Ihr Treueprogramm noch heute. Kostenlose Testversion, ohne Verpflichtung.",
),
"background_type": "gradient",
"buttons": [
{
"text": t(
"Essai gratuit",
"Start Free Trial",
"Kostenlos testen",
),
"url": "/signup",
"style": "primary",
},
],
},
}
HOMEPAGE_SECTIONS = {
"main": _wizard_homepage_sections,
"oms": _oms_homepage_sections,
"loyalty": _loyalty_homepage_sections,
}
# ============================================================================
# PLATFORM MARKETING PAGES (is_platform_page=True)
# ============================================================================
def _get_platform_pages(platform_code: str) -> list[dict]:
"""Get platform marketing pages for a given platform code."""
if platform_code == "main":
return [
{
"slug": "about",
"title": "About Wizard",
"title_translations": tt("About Wizard", "À propos de Wizard", "Über Wizard", "Iwwer Wizard"),
"content": """<div class="prose-content">
<h2>About Wizard</h2>
<p>Wizard is the digital toolkit for Luxembourg businesses. We provide integrated solutions for order management, customer loyalty, and online presence.</p>
<h3>Our Mission</h3>
<p>To empower Luxembourg businesses with modern, easy-to-use digital tools that help them grow and thrive in the digital age.</p>
<h3>Our Solutions</h3>
<ul>
<li><strong>OMS (omsflow.lu):</strong> Order management, inventory, and invoicing for Letzshop sellers</li>
<li><strong>Loyalty (rewardflow.lu):</strong> Customer loyalty programs with points, rewards, and tiers</li>
<li><strong>Website Builder:</strong> Coming soon — build your online presence</li>
</ul>
<h3>Built in Luxembourg</h3>
<p>We understand the unique needs of Luxembourg commerce — from multilingual support (FR/DE/EN) to EU VAT compliance.</p>
</div>""",
"meta_description": "Wizard — the digital toolkit for Luxembourg businesses. Order management, loyalty programs, and more.",
"show_in_footer": True,
"show_in_header": True,
"display_order": 1,
},
{
"slug": "contact",
"title": "Contact Us",
"title_translations": tt("Contact Us", "Contactez-nous", "Kontakt", "Kontakt"),
"content": """<div class="prose-content">
<h2>Contact Wizard</h2>
<p>We'd love to hear from you. Get in touch with our team.</p>
<h3>General Inquiries</h3>
<ul>
<li><strong>Email:</strong> info@wizard.lu</li>
</ul>
<h3>Sales</h3>
<p>Interested in our solutions for your business?</p>
<ul>
<li><strong>Email:</strong> sales@wizard.lu</li>
</ul>
<h3>Support</h3>
<p>Already a customer? Our support team is here to help.</p>
<ul>
<li><strong>Email:</strong> support@wizard.lu</li>
</ul>
<h3>Office</h3>
<p>Luxembourg</p>
</div>""",
"meta_description": "Contact the Wizard team for inquiries about our business solutions.",
"show_in_footer": True,
"show_in_header": True,
"display_order": 2,
},
{
"slug": "faq",
"title": "FAQ",
"title_translations": tt("FAQ", "FAQ", "FAQ", "FAQ"),
"content": """<div class="prose-content">
<h2>Frequently Asked Questions</h2>
<h3>General</h3>
<h4>What is Wizard?</h4>
<p>Wizard is a suite of digital tools for Luxembourg businesses, including order management (OMS), customer loyalty programs, and website building.</p>
<h4>Who is Wizard for?</h4>
<p>Wizard is designed for Luxembourg businesses of all sizes — from individual Letzshop sellers to multi-store retailers.</p>
<h3>Pricing & Billing</h3>
<h4>Is there a free trial?</h4>
<p>Yes! All our solutions offer a free trial period. No credit card required to start.</p>
<h4>Can I switch plans?</h4>
<p>Yes, you can upgrade or downgrade your plan at any time.</p>
<h3>Technical</h3>
<h4>What languages are supported?</h4>
<p>Our platform supports French, German, and English — the three official languages of Luxembourg.</p>
<h4>Is my data secure?</h4>
<p>Yes. We use industry-standard encryption and follow GDPR regulations for data protection.</p>
</div>""",
"meta_description": "Frequently asked questions about Wizard solutions for Luxembourg businesses.",
"show_in_footer": True,
"show_in_header": False,
"display_order": 3,
},
]
if platform_code == "oms":
return [
{
"slug": "about",
"title": "About OMS",
"title_translations": tt("About OMS", "À propos d'OMS", "Über OMS", "Iwwer OMS"),
"content": """<div class="prose-content">
<h2>About OMS by Wizard</h2>
<p>OMS (omsflow.lu) is a lightweight order management system built specifically for Letzshop sellers in Luxembourg.</p>
<h3>Why OMS?</h3>
<p>Letzshop is a great marketplace, but it doesn't give sellers the back-office tools they need. OMS fills that gap with:</p>
<ul>
<li><strong>Automatic order sync</strong> from Letzshop</li>
<li><strong>Inventory management</strong> with warehouse locations</li>
<li><strong>EU VAT invoicing</strong> with correct rates per country</li>
<li><strong>Customer data ownership</strong> for marketing and retention</li>
</ul>
<h3>Built for Luxembourg</h3>
<p>From multilingual support to Luxembourg VAT compliance, OMS is designed for the local market.</p>
</div>""",
"meta_description": "OMS — lightweight order management for Letzshop sellers. Manage orders, inventory, and invoicing.",
"show_in_footer": True,
"show_in_header": True,
"display_order": 1,
},
{
"slug": "contact",
"title": "Contact OMS Support",
"title_translations": tt("Contact OMS Support", "Contacter le support OMS", "OMS Support kontaktieren", "OMS Support kontaktéieren"),
"content": """<div class="prose-content">
<h2>Contact OMS Support</h2>
<p>Need help with your order management? We're here for you.</p>
<h3>Support</h3>
<ul>
<li><strong>Email:</strong> support@omsflow.lu</li>
</ul>
<h3>Sales</h3>
<p>Interested in OMS for your Letzshop store?</p>
<ul>
<li><strong>Email:</strong> sales@omsflow.lu</li>
</ul>
</div>""",
"meta_description": "Contact the OMS support team for help with order management.",
"show_in_footer": True,
"show_in_header": True,
"display_order": 2,
},
{
"slug": "faq",
"title": "OMS FAQ",
"title_translations": tt("OMS FAQ", "FAQ OMS", "OMS FAQ", "OMS FAQ"),
"content": """<div class="prose-content">
<h2>Frequently Asked Questions</h2>
<h4>Do I need a Letzshop account?</h4>
<p>While OMS works best with Letzshop integration, you can also use it as a standalone order management tool.</p>
<h4>How does Letzshop sync work?</h4>
<p>Enter your Letzshop API credentials and orders sync automatically. Setup takes about 2 minutes.</p>
<h4>Is EU VAT invoicing included?</h4>
<p>Yes, EU VAT invoicing is available on Professional and Enterprise plans. Luxembourg VAT is available on all plans.</p>
<h4>Can I export my data?</h4>
<p>Yes, you can export customers, orders, and invoices at any time.</p>
<h4>Is there a free trial?</h4>
<p>Yes! Start with a free trial, no credit card required.</p>
</div>""",
"meta_description": "Frequently asked questions about OMS order management for Letzshop sellers.",
"show_in_footer": True,
"show_in_header": False,
"display_order": 3,
},
]
if platform_code == "loyalty":
return [
{
"slug": "about",
"title": "About Loyalty",
"title_translations": tt("About Loyalty", "À propos de Loyalty", "Über Loyalty", "Iwwer Loyalty"),
"content": """<div class="prose-content">
<h2>About Loyalty by Wizard</h2>
<p>Loyalty (rewardflow.lu) helps Luxembourg businesses create engaging customer loyalty programs.</p>
<h3>What We Offer</h3>
<ul>
<li><strong>Points system:</strong> Award points for purchases with flexible rules</li>
<li><strong>Rewards catalog:</strong> Products, discounts, or exclusive experiences</li>
<li><strong>Tier management:</strong> Bronze, Silver, Gold — progressive loyalty levels</li>
<li><strong>Analytics:</strong> Track engagement, retention, and program ROI</li>
</ul>
<h3>Built for Luxembourg</h3>
<p>Multilingual support (FR/DE/EN) and local business features out of the box.</p>
</div>""",
"meta_description": "Loyalty — customer loyalty programs for Luxembourg businesses. Points, rewards, and tiers.",
"show_in_footer": True,
"show_in_header": True,
"display_order": 1,
},
{
"slug": "contact",
"title": "Contact Loyalty Support",
"title_translations": tt("Contact Loyalty Support", "Contacter le support Loyalty", "Loyalty Support kontaktieren", "Loyalty Support kontaktéieren"),
"content": """<div class="prose-content">
<h2>Contact Loyalty Support</h2>
<p>Need help with your loyalty program? We're here for you.</p>
<h3>Support</h3>
<ul>
<li><strong>Email:</strong> support@rewardflow.lu</li>
</ul>
<h3>Sales</h3>
<p>Interested in a loyalty program for your business?</p>
<ul>
<li><strong>Email:</strong> sales@rewardflow.lu</li>
</ul>
</div>""",
"meta_description": "Contact the Loyalty support team for help with your loyalty program.",
"show_in_footer": True,
"show_in_header": True,
"display_order": 2,
},
{
"slug": "faq",
"title": "Loyalty FAQ",
"title_translations": tt("Loyalty FAQ", "FAQ Loyalty", "Loyalty FAQ", "Loyalty FAQ"),
"content": """<div class="prose-content">
<h2>Frequently Asked Questions</h2>
<h4>How do loyalty points work?</h4>
<p>You define the rules — for example, 1 point per euro spent. Points can be redeemed for rewards from your catalog.</p>
<h4>Can I customize the loyalty tiers?</h4>
<p>Yes! You can create custom tiers with different names, thresholds, and benefits.</p>
<h4>How do customers check their points?</h4>
<p>Customers can check their balance through a personalized loyalty page or at your point of sale.</p>
<h4>Is there a free trial?</h4>
<p>Yes! Start with a free trial, no credit card required.</p>
</div>""",
"meta_description": "Frequently asked questions about Loyalty customer loyalty programs.",
"show_in_footer": True,
"show_in_header": False,
"display_order": 3,
},
]
return []
# Shared legal pages (same content for all platforms)
SHARED_PLATFORM_PAGES = [
{
"slug": "privacy",
"title": "Privacy Policy",
"title_translations": tt("Privacy Policy", "Politique de confidentialité", "Datenschutzrichtlinie", "Dateschutzrichtlinn"),
"content": """<div class="prose-content">
<h2>Privacy Policy</h2>
<p><em>Last Updated: February 2026</em></p>
<h3>Information We Collect</h3>
<p>We collect information you provide directly:</p>
<ul>
<li>Name, email address, and contact information</li>
<li>Business information (for merchants)</li>
<li>Payment information (processed securely by our payment processor)</li>
</ul>
<h3>How We Use Your Information</h3>
<p>We use your information to provide and improve our services, process payments, communicate with you, and comply with legal obligations.</p>
<h3>Data Protection (GDPR)</h3>
<p>We comply with the EU General Data Protection Regulation. You have the right to access, correct, delete, or export your personal data at any time.</p>
<h3>Contact</h3>
<p>For privacy-related questions, contact privacy@wizard.lu</p>
</div>""",
"meta_description": "Privacy policy — how we collect, use, and protect your personal information.",
"show_in_footer": False,
"show_in_header": False,
"show_in_legal": True,
"display_order": 10,
},
{
"slug": "terms",
"title": "Terms of Service",
"title_translations": tt("Terms of Service", "Conditions d'utilisation", "Nutzungsbedingungen", "Notzungsbedingungen"),
"content": """<div class="prose-content">
<h2>Terms of Service</h2>
<p><em>Last Updated: February 2026</em></p>
<h3>1. Acceptance of Terms</h3>
<p>By accessing and using this platform, you accept and agree to be bound by these Terms of Service.</p>
<h3>2. Services</h3>
<p>We provide digital business tools including order management, loyalty programs, and website building services.</p>
<h3>3. Account</h3>
<p>You must provide accurate information and maintain the security of your account.</p>
<h3>4. Payments</h3>
<p>Subscription fees are billed monthly or annually. You may cancel at any time.</p>
<h3>5. Data & Privacy</h3>
<p>Your use of our services is also governed by our Privacy Policy.</p>
<h3>6. Governing Law</h3>
<p>These terms are governed by the laws of Luxembourg.</p>
<h3>Contact</h3>
<p>For questions about these terms, contact legal@wizard.lu</p>
</div>""",
"meta_description": "Terms of service governing the use of our platform.",
"show_in_footer": False,
"show_in_header": False,
"show_in_legal": True,
"display_order": 11,
},
]
# ============================================================================
# STORE DEFAULT PAGES (is_platform_page=False, store_id=NULL)
# Uses {{store_name}}, {{store_email}}, {{store_phone}} placeholders
# ============================================================================
STORE_DEFAULTS_COMMON = [
{
"slug": "about",
"title": "About Us",
"title_translations": tt("About Us", "À propos", "Über uns", "Iwwer eis"),
"content": """<div class="prose-content">
<h2>About {{store_name}}</h2>
<p>Welcome to {{store_name}}. We are committed to providing you with quality products and excellent service.</p>
<h3>Our Story</h3>
<p>{{store_name}} was founded with a simple mission: to deliver exceptional value to our customers.</p>
<h3>Contact Us</h3>
<p>Have questions? We'd love to hear from you.</p>
<ul>
<li><strong>Email:</strong> {{store_email}}</li>
<li><strong>Phone:</strong> {{store_phone}}</li>
</ul>
</div>""",
"meta_description": "Learn about {{store_name}} — our story, values, and commitment to quality.",
"show_in_footer": True,
"show_in_header": True,
"display_order": 1,
},
{
"slug": "contact",
"title": "Contact Us",
"title_translations": tt("Contact Us", "Contactez-nous", "Kontakt", "Kontakt"),
"content": """<div class="prose-content">
<h2>Contact {{store_name}}</h2>
<p>We're here to help! Reach out to us.</p>
<h3>Get in Touch</h3>
<ul>
<li><strong>Email:</strong> {{store_email}}</li>
<li><strong>Phone:</strong> {{store_phone}}</li>
</ul>
<p>We typically respond within 24 hours during business days.</p>
</div>""",
"meta_description": "Contact {{store_name}} for questions, support, or inquiries.",
"show_in_footer": True,
"show_in_header": True,
"display_order": 2,
},
{
"slug": "faq",
"title": "FAQ",
"title_translations": tt("FAQ", "FAQ", "FAQ", "FAQ"),
"content": """<div class="prose-content">
<h2>Frequently Asked Questions</h2>
<h4>How can I contact you?</h4>
<p>You can reach us at {{store_email}} or call {{store_phone}}.</p>
<h4>What are your business hours?</h4>
<p>We are available during regular business hours, Monday through Friday.</p>
<h4>Do you have a physical location?</h4>
<p>Please contact us for information about our location and visiting hours.</p>
</div>""",
"meta_description": "Frequently asked questions about {{store_name}}.",
"show_in_footer": True,
"show_in_header": False,
"display_order": 3,
},
{
"slug": "privacy",
"title": "Privacy Policy",
"title_translations": tt("Privacy Policy", "Politique de confidentialité", "Datenschutzrichtlinie", "Dateschutzrichtlinn"),
"content": """<div class="prose-content">
<h2>Privacy Policy</h2>
<p>{{store_name}} is committed to protecting your personal information.</p>
<h3>Information We Collect</h3>
<p>We collect information necessary to process your orders and provide customer service.</p>
<h3>Your Rights</h3>
<p>Under GDPR, you have the right to access, correct, or delete your personal data. Contact us at {{store_email}}.</p>
</div>""",
"meta_description": "Privacy policy for {{store_name}}.",
"show_in_footer": False,
"show_in_header": False,
"show_in_legal": True,
"display_order": 10,
},
{
"slug": "terms",
"title": "Terms of Service",
"title_translations": tt("Terms of Service", "Conditions d'utilisation", "Nutzungsbedingungen", "Notzungsbedingungen"),
"content": """<div class="prose-content">
<h2>Terms of Service</h2>
<p>By using the services provided by {{store_name}}, you agree to these terms.</p>
<h3>Orders</h3>
<p>All orders are subject to availability and confirmation.</p>
<h3>Contact</h3>
<p>For questions about these terms, contact us at {{store_email}}.</p>
</div>""",
"meta_description": "Terms of service for {{store_name}}.",
"show_in_footer": False,
"show_in_header": False,
"show_in_legal": True,
"display_order": 11,
},
]
# OMS-specific store defaults (shipping/returns)
STORE_DEFAULTS_OMS_EXTRA = [
{
"slug": "shipping",
"title": "Shipping Policy",
"title_translations": tt("Shipping Policy", "Politique de livraison", "Versandrichtlinie", "Versandrichtlinn"),
"content": """<div class="prose-content">
<h2>Shipping Policy</h2>
<h3>Shipping Methods</h3>
<p>{{store_name}} offers multiple shipping options:</p>
<ul>
<li><strong>Standard Shipping:</strong> 3-7 business days</li>
<li><strong>Express Shipping:</strong> 1-3 business days</li>
</ul>
<h3>Shipping Costs</h3>
<p>Shipping costs are calculated based on weight and destination at checkout.</p>
<h3>Tracking</h3>
<p>You will receive a tracking number by email once your order ships.</p>
<h3>Questions?</h3>
<p>Contact us at {{store_email}} for shipping inquiries.</p>
</div>""",
"meta_description": "Shipping policy for {{store_name}} — methods, costs, and delivery times.",
"show_in_footer": True,
"show_in_header": False,
"display_order": 4,
},
{
"slug": "returns",
"title": "Return & Refund Policy",
"title_translations": tt("Return & Refund Policy", "Politique de retour et remboursement", "Rückgabe- und Erstattungsrichtlinie", "Retour- a Rembourséierungsrichtlinn"),
"content": """<div class="prose-content">
<h2>Return & Refund Policy</h2>
<h3>Returns</h3>
<p>{{store_name}} accepts returns within 14 days of delivery, in accordance with Luxembourg consumer protection law.</p>
<h3>How to Return</h3>
<ol>
<li>Contact us at {{store_email}} to initiate a return</li>
<li>Pack the item in its original condition</li>
<li>Ship using the provided return instructions</li>
</ol>
<h3>Refunds</h3>
<p>Refunds are processed within 14 days of receiving the returned item, back to the original payment method.</p>
<h3>Damaged Items</h3>
<p>If you receive a damaged item, contact us immediately at {{store_email}} with photos.</p>
</div>""",
"meta_description": "Return and refund policy for {{store_name}}.",
"show_in_footer": True,
"show_in_header": False,
"display_order": 5,
},
]
# ============================================================================
# SCRIPT FUNCTIONS
# ============================================================================
def _page_exists(db: Session, platform_id: int, slug: str, *, is_platform_page: bool) -> bool:
"""Check if a page already exists for the given platform, slug, and type."""
existing = db.execute(
select(ContentPage).where(
ContentPage.platform_id == platform_id,
ContentPage.store_id.is_(None),
ContentPage.slug == slug,
ContentPage.is_platform_page == is_platform_page,
)
).scalar_one_or_none()
return existing is not None
def _create_page(
db: Session,
platform_id: int,
page_data: dict,
*,
is_platform_page: bool = False,
sections: dict | None = None,
) -> bool:
"""Create a single content page. Returns True if created, False if skipped."""
slug = page_data["slug"]
title = page_data["title"]
if _page_exists(db, platform_id, slug, is_platform_page=is_platform_page):
print(f" Skipped: {title} (/{slug}) - already exists")
return False
page = ContentPage(
platform_id=platform_id,
store_id=None,
slug=slug,
title=title,
title_translations=page_data.get("title_translations"),
content=page_data.get("content", ""),
content_translations=page_data.get("content_translations"),
content_format="html",
template=page_data.get("template", "default"),
sections=sections,
meta_description=page_data.get("meta_description", ""),
meta_keywords=page_data.get("meta_keywords", ""),
is_platform_page=is_platform_page,
is_published=True,
published_at=datetime.now(UTC),
show_in_footer=page_data.get("show_in_footer", False),
show_in_header=page_data.get("show_in_header", False),
show_in_legal=page_data.get("show_in_legal", False),
display_order=page_data.get("display_order", 0),
created_at=datetime.now(UTC),
updated_at=datetime.now(UTC),
)
db.add(page)
tier_label = "platform" if is_platform_page else "store-default"
print(f" Created [{tier_label}]: {title} (/{slug})")
return True
def create_default_pages(db: Session) -> None:
"""
Create all default content pages for all platforms.
This function is idempotent — it skips pages that already exist.
"""
print("\n" + "=" * 70)
print("Creating Default Content Pages (CMS)")
print("=" * 70 + "\n")
# Load all platforms
platforms = db.execute(select(Platform)).scalars().all()
if not platforms:
print(" No platforms found. Run init_production.py first.")
return
total_created = 0
total_skipped = 0
for platform in platforms:
print(f"\n Platform: {platform.name} (code={platform.code})")
created_count = 0
skipped_count = 0
# ------------------------------------------------------------------
# 1. Platform Homepage (slug="home", is_platform_page=True, sections)
# ------------------------------------------------------------------
sections_fn = HOMEPAGE_SECTIONS.get(platform.code)
if sections_fn:
sections_data = sections_fn()
homepage_data = {
"slug": "home",
"title": f"{platform.name} - Home",
"content": "",
"meta_description": f"{platform.name} — digital tools for Luxembourg businesses.",
"show_in_footer": False,
"show_in_header": False,
"display_order": 0,
}
if _create_page(
db, platform.id, homepage_data,
is_platform_page=True, sections=sections_data,
):
created_count += 1
else:
skipped_count += 1
else:
print(f" Warning: No homepage sections defined for platform '{platform.code}'")
# ------------------------------------------------------------------
# 2. Platform Marketing Pages (is_platform_page=True)
# ------------------------------------------------------------------
platform_pages = _get_platform_pages(platform.code)
for page_data in platform_pages:
if _create_page(db, platform.id, page_data, is_platform_page=True):
created_count += 1
else:
skipped_count += 1
# Shared legal pages (privacy, terms) for all platforms
for page_data in SHARED_PLATFORM_PAGES:
if _create_page(db, platform.id, page_data, is_platform_page=True):
created_count += 1
else:
skipped_count += 1
# ------------------------------------------------------------------
# 3. Store Default Pages (is_platform_page=False, store_id=NULL)
# Only for platforms that host stores (not wizard.lu main)
# ------------------------------------------------------------------
if platform.code != "main":
for page_data in STORE_DEFAULTS_COMMON:
if _create_page(db, platform.id, page_data, is_platform_page=False):
created_count += 1
else:
skipped_count += 1
# OMS-specific: shipping + returns
if platform.code == "oms":
for page_data in STORE_DEFAULTS_OMS_EXTRA:
if _create_page(db, platform.id, page_data, is_platform_page=False):
created_count += 1
else:
skipped_count += 1
print(f" --- {created_count} created, {skipped_count} skipped")
total_created += created_count
total_skipped += skipped_count
db.commit()
print("\n" + "=" * 70)
print("Summary:")
print(f" Platforms: {len(platforms)}")
print(f" Created: {total_created} pages")
print(f" Skipped: {total_skipped} pages (already exist)")
print(f" Total: {total_created + total_skipped} pages")
print("=" * 70 + "\n")
if total_created > 0:
print("Default content pages created successfully!\n")
else:
print("All default pages already exist. No changes made.\n")
# ============================================================================
# MAIN EXECUTION
# ============================================================================
def main():
"""Main execution function."""
print("\nStarting Default Content Pages Creation Script...\n")
db = SessionLocal()
try:
create_default_pages(db)
print("Script completed successfully!\n")
except Exception as e:
print(f"\nError: {e}\n")
db.rollback()
raise
finally:
db.close()
if __name__ == "__main__":
main()