Files
orion/docs/features/cms-implementation-guide.md
Samir Boulahtit 7a9dda282d refactor(scripts): reorganize scripts/ into seed/ and validate/ subfolders
Move 9 init/seed scripts into scripts/seed/ and 7 validation scripts
(+ validators/ subfolder) into scripts/validate/ to reduce clutter in
the root scripts/ directory. Update all references across Makefile,
CI/CD configs, pre-commit hooks, docs (~40 files), and Python imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 21:35:53 +01:00

415 lines
11 KiB
Markdown

# 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
```bash
# 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:
```python
# 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`):
```python
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`):
```python
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):
```python
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:
```python
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`:
```jinja2
{# 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 %}
```
### 6. Update Footer to Load Navigation Dynamically
Edit `app/templates/shop/base.html` to load navigation from database.
First, update the context helper to include footer pages:
```python
# 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:
```jinja2
{# 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`:
```python
#!/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:
```bash
python scripts/seed/create_default_content_pages.py
```
## Testing
### 1. Test Platform Defaults
```bash
# 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/wizamart/about
```
### 2. Test Store Override
```bash
# Create store override
curl -X POST http://localhost:8000/api/v1/store/wizamart/content-pages/ \
-H "Authorization: Bearer <store_token>" \
-H "Content-Type: application/json" \
-d '{
"slug": "about",
"title": "About Wizamart",
"content": "<h1>About Wizamart</h1><p>Custom store content</p>",
"is_published": true
}'
# View in shop (should show store content)
curl http://localhost:8000/store/wizamart/about
```
### 3. Test Fallback
```bash
# Delete store override
curl -X DELETE http://localhost:8000/api/v1/store/wizamart/content-pages/{id} \
-H "Authorization: Bearer <store_token>"
# View in shop (should fall back to platform default)
curl http://localhost:8000/store/wizamart/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!