Files
orion/docs/features/cms-implementation-guide.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

11 KiB

CMS Implementation Guide

Quick Start

This guide shows you how to implement the Content Management System for static pages.

What Was Implemented

Database Model: models/database/content_page.py Service Layer: app/services/content_page_service.py Admin API: app/api/v1/admin/content_pages.py Store API: app/api/v1/store/content_pages.py Shop API: app/api/v1/shop/content_pages.py Documentation: Full CMS documentation in docs/features/content-management-system.md

Next Steps to Activate

1. Create Database Migration

# Create Alembic migration
alembic revision --autogenerate -m "Add content_pages table"

# Review the generated migration in alembic/versions/

# Run migration
alembic upgrade head

2. Add Relationship to Store Model

Edit models/database/store.py and add this relationship:

# Add this import
from sqlalchemy.orm import relationship

# Add this relationship to Store class
content_pages = relationship("ContentPage", back_populates="store", cascade="all, delete-orphan")

3. Register API Routers

Edit the appropriate router files to include the new endpoints:

Admin Router (app/api/v1/admin/__init__.py):

from app.api.v1.admin import content_pages

api_router.include_router(
    content_pages.router,
    prefix="/content-pages",
    tags=["admin-content-pages"]
)

Store Router (app/api/v1/store/__init__.py):

from app.api.v1.store import content_pages

api_router.include_router(
    content_pages.router,
    prefix="/{store_code}/content-pages",
    tags=["store-content-pages"]
)

Shop Router (app/api/v1/shop/__init__.py or create if doesn't exist):

from app.api.v1.shop import content_pages

api_router.include_router(
    content_pages.router,
    prefix="/content-pages",
    tags=["shop-content-pages"]
)

4. Update Shop Routes to Use CMS

Edit app/routes/shop_pages.py to add a generic content page handler:

from app.services.content_page_service import content_page_service

@router.get("/{slug}", response_class=HTMLResponse, include_in_schema=False)
async def generic_content_page(
    slug: str,
    request: Request,
    db: Session = Depends(get_db)
):
    """
    Generic content page handler.
    Handles: /about, /faq, /contact, /shipping, /returns, /privacy, /terms, etc.
    """
    store = getattr(request.state, 'store', None)
    store_id = store.id if store else None

    page = content_page_service.get_page_for_store(
        db,
        slug=slug,
        store_id=store_id,
        include_unpublished=False
    )

    if not page:
        raise HTTPException(status_code=404, detail=f"Page not found: {slug}")

    return templates.TemplateResponse(
        "shop/content-page.html",
        get_shop_context(request, page=page)
    )

5. Create Generic Content Page Template

Create app/templates/shop/content-page.html:

{# app/templates/shop/content-page.html #}
{% extends "shop/base.html" %}

{% block title %}{{ page.title }}{% endblock %}

{% block meta_description %}
  {{ page.meta_description or page.title }}
{% endblock %}

{% block content %}
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">

  {# Breadcrumbs #}
  <nav class="mb-6 text-sm">
    <a href="{{ base_url }}" class="text-primary hover:underline">Home</a>
    <span class="mx-2 text-gray-400">/</span>
    <span class="text-gray-600 dark:text-gray-300">{{ page.title }}</span>
  </nav>

  {# Page Title #}
  <h1 class="text-4xl font-bold text-gray-900 dark:text-gray-100 mb-8">
    {{ page.title }}
  </h1>

  {# Content #}
  <div class="prose prose-lg dark:prose-invert max-w-none">
    {{ page.content | safe }}
  </div>

  {# Last updated #}
  {% if page.updated_at %}
  <div class="mt-12 pt-6 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-500 dark:text-gray-400">
    Last updated: {{ page.updated_at.strftime('%B %d, %Y') }}
  </div>
  {% endif %}

</div>
{% endblock %}

Edit app/templates/shop/base.html to load navigation from database.

First, update the context helper to include footer pages:

# app/routes/shop_pages.py

def get_shop_context(request: Request, **extra_context) -> dict:
    # ... existing code ...

    # Load footer navigation pages
    db = next(get_db())
    try:
        footer_pages = content_page_service.list_pages_for_store(
            db,
            store_id=store.id if store else None,
            include_unpublished=False,
            footer_only=True
        )
    finally:
        db.close()

    context = {
        "request": request,
        "store": store,
        "theme": theme,
        "clean_path": clean_path,
        "access_method": access_method,
        "base_url": base_url,
        "footer_pages": footer_pages,  # Add this
        **extra_context
    }

    return context

Then update the footer template:

{# app/templates/shop/base.html - Footer section #}

<div>
    <h4 class="font-semibold mb-4">Quick Links</h4>
    <ul class="space-y-2">
        {% for page in footer_pages %}
        <li>
            <a href="{{ base_url }}{{ page.slug }}"
               class="text-gray-600 hover:text-primary dark:text-gray-400">
                {{ page.title }}
            </a>
        </li>
        {% endfor %}
    </ul>
</div>

7. Create Default Platform Pages (Script)

Create scripts/seed/create_default_content_pages.py:

#!/usr/bin/env python3
"""Create default platform content pages."""

from sqlalchemy.orm import Session
from app.core.database import SessionLocal
from app.services.content_page_service import content_page_service

def create_defaults():
    db: Session = SessionLocal()

    try:
        # About Us
        content_page_service.create_page(
            db,
            slug="about",
            title="About Us",
            content="""
                <h2>Welcome to Our Marketplace</h2>
                <p>We connect quality stores with customers worldwide.</p>
                <p>Our mission is to provide a seamless shopping experience...</p>
            """,
            is_published=True,
            show_in_footer=True,
            display_order=1
        )

        # Shipping Information
        content_page_service.create_page(
            db,
            slug="shipping",
            title="Shipping Information",
            content="""
                <h2>Shipping Policy</h2>
                <p>We offer fast and reliable shipping...</p>
            """,
            is_published=True,
            show_in_footer=True,
            display_order=2
        )

        # Returns
        content_page_service.create_page(
            db,
            slug="returns",
            title="Returns & Refunds",
            content="""
                <h2>Return Policy</h2>
                <p>30-day return policy on all items...</p>
            """,
            is_published=True,
            show_in_footer=True,
            display_order=3
        )

        # Privacy Policy
        content_page_service.create_page(
            db,
            slug="privacy",
            title="Privacy Policy",
            content="""
                <h2>Privacy Policy</h2>
                <p>Your privacy is important to us...</p>
            """,
            is_published=True,
            show_in_footer=True,
            display_order=4
        )

        # Terms of Service
        content_page_service.create_page(
            db,
            slug="terms",
            title="Terms of Service",
            content="""
                <h2>Terms of Service</h2>
                <p>By using our platform, you agree to...</p>
            """,
            is_published=True,
            show_in_footer=True,
            display_order=5
        )

        # Contact
        content_page_service.create_page(
            db,
            slug="contact",
            title="Contact Us",
            content="""
                <h2>Get in Touch</h2>
                <p>Have questions? We'd love to hear from you!</p>
                <p>Email: support@example.com</p>
            """,
            is_published=True,
            show_in_footer=True,
            display_order=6
        )

        # FAQ
        content_page_service.create_page(
            db,
            slug="faq",
            title="Frequently Asked Questions",
            content="""
                <h2>FAQ</h2>
                <h3>How do I place an order?</h3>
                <p>Simply browse our products...</p>
            """,
            is_published=True,
            show_in_footer=True,
            display_order=7
        )

        print("✅ Created default content pages successfully")

    except Exception as e:
        print(f"❌ Error: {e}")
        db.rollback()
    finally:
        db.close()


if __name__ == "__main__":
    create_defaults()

Run it:

python scripts/seed/create_default_content_pages.py

Testing

1. Test Platform Defaults

# Create platform default
curl -X POST http://localhost:8000/api/v1/admin/content-pages/platform \
  -H "Authorization: Bearer <admin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "about",
    "title": "About Our Marketplace",
    "content": "<h1>About</h1><p>Platform default content</p>",
    "is_published": true,
    "show_in_footer": true
  }'

# View in shop
curl http://localhost:8000/store/orion/about

2. Test Store Override

# Create store override
curl -X POST http://localhost:8000/api/v1/store/orion/content-pages/ \
  -H "Authorization: Bearer <store_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "about",
    "title": "About Orion",
    "content": "<h1>About Orion</h1><p>Custom store content</p>",
    "is_published": true
  }'

# View in shop (should show store content)
curl http://localhost:8000/store/orion/about

3. Test Fallback

# Delete store override
curl -X DELETE http://localhost:8000/api/v1/store/orion/content-pages/{id} \
  -H "Authorization: Bearer <store_token>"

# View in shop (should fall back to platform default)
curl http://localhost:8000/store/orion/about

Summary

You now have a complete CMS system that allows:

  1. Platform admins to create default content for all stores
  2. Stores to override specific pages with custom content
  3. Automatic fallback to platform defaults when store hasn't customized
  4. Dynamic navigation loading from database
  5. SEO optimization with meta tags
  6. Draft/Published workflow for content management

All pages are accessible via their slug: /about, /faq, /contact, etc. with proper store context and routing support!