Files
orion/models/database/content_page.py
Samir Boulahtit c219f5b5f8 feat: add CMS database model and migrations
Implement Content Management System database layer:

Database Model:
- ContentPage model with two-tier architecture
- Platform defaults (vendor_id=NULL)
- Vendor-specific overrides (vendor_id=123)
- SEO fields (meta_description, meta_keywords)
- Publishing workflow (is_published, published_at)
- Navigation flags (show_in_footer, show_in_header)
- Display ordering and timestamps

Migrations:
- Create content_pages table with all columns
- Add indexes for performance (vendor_id, slug, published status)
- Add unique constraint on (vendor_id, slug)
- Add foreign key relationships with cascade delete

Model Registration:
- Add ContentPage to Vendor relationship
- Import model in alembic/env.py for migration detection

This provides the foundation for managing static content pages
(About, FAQ, Contact, etc.) with platform defaults and vendor overrides.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 15:54:29 +01:00

132 lines
5.0 KiB
Python

# models/database/content_page.py
"""
Content Page Model
Manages static content pages (About, FAQ, Contact, Shipping, Returns, etc.)
with platform-level defaults and vendor-specific overrides.
Features:
- Platform-level default content
- Vendor-specific overrides
- Rich text content (HTML/Markdown)
- SEO metadata
- Published/Draft status
- Version history support
"""
from datetime import datetime, timezone
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, Index
from sqlalchemy.orm import relationship
from app.core.database import Base
class ContentPage(Base):
"""
Content pages for shops (About, FAQ, Contact, etc.)
Two-tier system:
1. Platform-level defaults (vendor_id=NULL)
2. Vendor-specific overrides (vendor_id=123)
Lookup logic:
1. Check for vendor-specific page (vendor_id + slug)
2. If not found, use platform default (slug only)
3. If neither exists, show 404 or default template
"""
__tablename__ = "content_pages"
id = Column(Integer, primary_key=True, index=True)
# Vendor association (NULL = platform default)
vendor_id = Column(Integer, ForeignKey("vendors.id", ondelete="CASCADE"), nullable=True, index=True)
# Page identification
slug = Column(String(100), nullable=False, index=True) # about, faq, contact, shipping, returns, etc.
title = Column(String(200), nullable=False)
# Content
content = Column(Text, nullable=False) # HTML or Markdown
content_format = Column(String(20), default="html") # html, markdown
# SEO
meta_description = Column(String(300), nullable=True)
meta_keywords = Column(String(300), nullable=True)
# Publishing
is_published = Column(Boolean, default=False, nullable=False)
published_at = Column(DateTime(timezone=True), nullable=True)
# Ordering (for menus, footers)
display_order = Column(Integer, default=0)
show_in_footer = Column(Boolean, default=True)
show_in_header = Column(Boolean, default=False)
# Timestamps
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
# Author tracking (admin or vendor user who created/updated)
created_by = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
# Relationships
vendor = relationship("Vendor", back_populates="content_pages")
creator = relationship("User", foreign_keys=[created_by])
updater = relationship("User", foreign_keys=[updated_by])
# Constraints
__table_args__ = (
# Unique combination: vendor can only have one page per slug
# Platform defaults (vendor_id=NULL) can only have one page per slug
UniqueConstraint('vendor_id', 'slug', name='uq_vendor_slug'),
# Indexes for performance
Index('idx_vendor_published', 'vendor_id', 'is_published'),
Index('idx_slug_published', 'slug', 'is_published'),
)
def __repr__(self):
vendor_name = self.vendor.name if self.vendor else "PLATFORM"
return f"<ContentPage(id={self.id}, vendor={vendor_name}, slug={self.slug}, title={self.title})>"
@property
def is_platform_default(self):
"""Check if this is a platform-level default page."""
return self.vendor_id is None
@property
def is_vendor_override(self):
"""Check if this is a vendor-specific override."""
return self.vendor_id is not None
def to_dict(self):
"""Convert to dictionary for API responses."""
return {
"id": self.id,
"vendor_id": self.vendor_id,
"vendor_name": self.vendor.name if self.vendor else None,
"slug": self.slug,
"title": self.title,
"content": self.content,
"content_format": self.content_format,
"meta_description": self.meta_description,
"meta_keywords": self.meta_keywords,
"is_published": self.is_published,
"published_at": self.published_at.isoformat() if self.published_at else None,
"display_order": self.display_order,
"show_in_footer": self.show_in_footer,
"show_in_header": self.show_in_header,
"is_platform_default": self.is_platform_default,
"is_vendor_override": self.is_vendor_override,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
"created_by": self.created_by,
"updated_by": self.updated_by,
}
# Add relationship to Vendor model
# This should be added to models/database/vendor.py:
# content_pages = relationship("ContentPage", back_populates="vendor", cascade="all, delete-orphan")