Introduce page_configs table for per-page feature flags (calendar, market) and future SumUp credentials. Add page_config relationship to Post model. Remove duplicate CartItem definition from cart_item.py (canonical stays in market.py). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
248 lines
9.7 KiB
Python
248 lines
9.7 KiB
Python
from datetime import datetime
|
|
from typing import List, Optional
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from sqlalchemy import (
|
|
Integer,
|
|
String,
|
|
Text,
|
|
Boolean,
|
|
DateTime,
|
|
ForeignKey,
|
|
Column,
|
|
func,
|
|
)
|
|
from db.base import Base # whatever your Base is
|
|
# from .author import Author # make sure imports resolve
|
|
# from ..app.blog.calendars.model import Calendar
|
|
|
|
class Tag(Base):
|
|
__tablename__ = "tags"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
ghost_id: Mapped[str] = mapped_column(String(64), index=True, unique=True, nullable=False)
|
|
|
|
slug: Mapped[str] = mapped_column(String(191), index=True, nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
description: Mapped[Optional[str]] = mapped_column(Text())
|
|
visibility: Mapped[str] = mapped_column(String(32), default="public", nullable=False)
|
|
feature_image: Mapped[Optional[str]] = mapped_column(Text())
|
|
|
|
meta_title: Mapped[Optional[str]] = mapped_column(String(300))
|
|
meta_description: Mapped[Optional[str]] = mapped_column(Text())
|
|
|
|
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
|
|
# NEW: posts relationship is now direct Post objects via PostTag
|
|
posts: Mapped[List["Post"]] = relationship(
|
|
"Post",
|
|
secondary="post_tags",
|
|
primaryjoin="Tag.id==post_tags.c.tag_id",
|
|
secondaryjoin="Post.id==post_tags.c.post_id",
|
|
back_populates="tags",
|
|
order_by="PostTag.sort_order",
|
|
)
|
|
|
|
|
|
class Post(Base):
|
|
__tablename__ = "posts"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
ghost_id: Mapped[str] = mapped_column(String(64), index=True, unique=True, nullable=False)
|
|
uuid: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
|
slug: Mapped[str] = mapped_column(String(191), index=True, nullable=False)
|
|
|
|
title: Mapped[str] = mapped_column(String(500), nullable=False)
|
|
|
|
html: Mapped[Optional[str]] = mapped_column(Text())
|
|
plaintext: Mapped[Optional[str]] = mapped_column(Text())
|
|
mobiledoc: Mapped[Optional[str]] = mapped_column(Text())
|
|
lexical: Mapped[Optional[str]] = mapped_column(Text())
|
|
|
|
feature_image: Mapped[Optional[str]] = mapped_column(Text())
|
|
feature_image_alt: Mapped[Optional[str]] = mapped_column(Text())
|
|
feature_image_caption: Mapped[Optional[str]] = mapped_column(Text())
|
|
|
|
excerpt: Mapped[Optional[str]] = mapped_column(Text())
|
|
custom_excerpt: Mapped[Optional[str]] = mapped_column(Text())
|
|
|
|
visibility: Mapped[str] = mapped_column(String(32), default="public", nullable=False)
|
|
status: Mapped[str] = mapped_column(String(32), default="draft", nullable=False)
|
|
featured: Mapped[bool] = mapped_column(Boolean(), default=False, nullable=False)
|
|
is_page: Mapped[bool] = mapped_column(Boolean(), default=False, nullable=False)
|
|
email_only: Mapped[bool] = mapped_column(Boolean(), default=False, nullable=False)
|
|
|
|
canonical_url: Mapped[Optional[str]] = mapped_column(Text())
|
|
meta_title: Mapped[Optional[str]] = mapped_column(String(500))
|
|
meta_description: Mapped[Optional[str]] = mapped_column(Text())
|
|
og_image: Mapped[Optional[str]] = mapped_column(Text())
|
|
og_title: Mapped[Optional[str]] = mapped_column(String(500))
|
|
og_description: Mapped[Optional[str]] = mapped_column(Text())
|
|
twitter_image: Mapped[Optional[str]] = mapped_column(Text())
|
|
twitter_title: Mapped[Optional[str]] = mapped_column(String(500))
|
|
twitter_description: Mapped[Optional[str]] = mapped_column(Text())
|
|
custom_template: Mapped[Optional[str]] = mapped_column(String(191))
|
|
|
|
reading_time: Mapped[Optional[int]] = mapped_column(Integer())
|
|
comment_id: Mapped[Optional[str]] = mapped_column(String(191))
|
|
|
|
published_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
|
|
user_id: Mapped[Optional[int]] = mapped_column(
|
|
Integer, ForeignKey("users.id", ondelete="SET NULL"), index=True
|
|
)
|
|
publish_requested: Mapped[bool] = mapped_column(Boolean(), default=False, server_default="false", nullable=False)
|
|
|
|
primary_author_id: Mapped[Optional[int]] = mapped_column(
|
|
Integer, ForeignKey("authors.id", ondelete="SET NULL")
|
|
)
|
|
primary_tag_id: Mapped[Optional[int]] = mapped_column(
|
|
Integer, ForeignKey("tags.id", ondelete="SET NULL")
|
|
)
|
|
|
|
primary_author: Mapped[Optional["Author"]] = relationship(
|
|
"Author", foreign_keys=[primary_author_id]
|
|
)
|
|
primary_tag: Mapped[Optional[Tag]] = relationship(
|
|
"Tag", foreign_keys=[primary_tag_id]
|
|
)
|
|
user: Mapped[Optional["User"]] = relationship(
|
|
"User", foreign_keys=[user_id]
|
|
)
|
|
|
|
# AUTHORS RELATIONSHIP (many-to-many via post_authors)
|
|
authors: Mapped[List["Author"]] = relationship(
|
|
"Author",
|
|
secondary="post_authors",
|
|
primaryjoin="Post.id==post_authors.c.post_id",
|
|
secondaryjoin="Author.id==post_authors.c.author_id",
|
|
back_populates="posts",
|
|
order_by="PostAuthor.sort_order",
|
|
)
|
|
|
|
# TAGS RELATIONSHIP (many-to-many via post_tags)
|
|
tags: Mapped[List[Tag]] = relationship(
|
|
"Tag",
|
|
secondary="post_tags",
|
|
primaryjoin="Post.id==post_tags.c.post_id",
|
|
secondaryjoin="Tag.id==post_tags.c.tag_id",
|
|
back_populates="posts",
|
|
order_by="PostTag.sort_order",
|
|
)
|
|
calendars:Mapped[List["Calendar"]] = relationship(
|
|
"Calendar",
|
|
back_populates="post",
|
|
cascade="all, delete-orphan",
|
|
passive_deletes=True,
|
|
order_by="Calendar.name",
|
|
)
|
|
|
|
likes: Mapped[List["PostLike"]] = relationship(
|
|
"PostLike",
|
|
back_populates="post",
|
|
cascade="all, delete-orphan",
|
|
passive_deletes=True,
|
|
)
|
|
|
|
calendar_entries: Mapped[List["CalendarEntryPost"]] = relationship(
|
|
"CalendarEntryPost",
|
|
back_populates="post",
|
|
cascade="all, delete-orphan",
|
|
passive_deletes=True,
|
|
)
|
|
|
|
menu_items: Mapped[List["MenuItem"]] = relationship(
|
|
"MenuItem",
|
|
back_populates="post",
|
|
cascade="all, delete-orphan",
|
|
passive_deletes=True,
|
|
order_by="MenuItem.sort_order",
|
|
)
|
|
|
|
page_config: Mapped[Optional["PageConfig"]] = relationship(
|
|
"PageConfig",
|
|
back_populates="post",
|
|
uselist=False,
|
|
cascade="all, delete-orphan",
|
|
passive_deletes=True,
|
|
)
|
|
|
|
class Author(Base):
|
|
__tablename__ = "authors"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
ghost_id: Mapped[str] = mapped_column(String(64), index=True, unique=True, nullable=False)
|
|
|
|
slug: Mapped[str] = mapped_column(String(191), index=True, nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
email: Mapped[Optional[str]] = mapped_column(String(255))
|
|
|
|
profile_image: Mapped[Optional[str]] = mapped_column(Text())
|
|
cover_image: Mapped[Optional[str]] = mapped_column(Text())
|
|
bio: Mapped[Optional[str]] = mapped_column(Text())
|
|
website: Mapped[Optional[str]] = mapped_column(Text())
|
|
location: Mapped[Optional[str]] = mapped_column(Text())
|
|
facebook: Mapped[Optional[str]] = mapped_column(Text())
|
|
twitter: Mapped[Optional[str]] = mapped_column(Text())
|
|
|
|
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
|
|
# backref to posts via post_authors
|
|
posts: Mapped[List[Post]] = relationship(
|
|
"Post",
|
|
secondary="post_authors",
|
|
primaryjoin="Author.id==post_authors.c.author_id",
|
|
secondaryjoin="Post.id==post_authors.c.post_id",
|
|
back_populates="authors",
|
|
order_by="PostAuthor.sort_order",
|
|
)
|
|
|
|
class PostAuthor(Base):
|
|
__tablename__ = "post_authors"
|
|
|
|
post_id: Mapped[int] = mapped_column(
|
|
ForeignKey("posts.id", ondelete="CASCADE"),
|
|
primary_key=True,
|
|
)
|
|
author_id: Mapped[int] = mapped_column(
|
|
ForeignKey("authors.id", ondelete="CASCADE"),
|
|
primary_key=True,
|
|
)
|
|
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
|
|
|
|
class PostTag(Base):
|
|
__tablename__ = "post_tags"
|
|
|
|
post_id: Mapped[int] = mapped_column(
|
|
ForeignKey("posts.id", ondelete="CASCADE"),
|
|
primary_key=True,
|
|
)
|
|
tag_id: Mapped[int] = mapped_column(
|
|
ForeignKey("tags.id", ondelete="CASCADE"),
|
|
primary_key=True,
|
|
)
|
|
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
|
|
|
|
class PostLike(Base):
|
|
__tablename__ = "post_likes"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
|
post_id: Mapped[int] = mapped_column(ForeignKey("posts.id", ondelete="CASCADE"), nullable=False)
|
|
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
|
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
|
|
post: Mapped["Post"] = relationship("Post", back_populates="likes", foreign_keys=[post_id])
|
|
user = relationship("User", back_populates="liked_posts")
|