Decouple blog: use shared.models for all cross-app imports
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 47s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 47s
- Replace all imports from cart.models, market.models, events.models with shared.models equivalents - Convert blog/models/ghost_content.py to re-export stub - Update shared + glue submodule pointers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ from sqlalchemy.orm.attributes import flag_modified # for non-Mutable JSON colu
|
|||||||
from models.ghost_content import (
|
from models.ghost_content import (
|
||||||
Post, Author, Tag, PostAuthor, PostTag
|
Post, Author, Tag, PostAuthor, PostTag
|
||||||
)
|
)
|
||||||
from cart.models.page_config import PageConfig
|
from shared.models.page_config import PageConfig
|
||||||
|
|
||||||
# User-centric membership models
|
# User-centric membership models
|
||||||
from shared.models import User
|
from shared.models import User
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from sqlalchemy.orm import selectinload, joinedload
|
from sqlalchemy.orm import selectinload, joinedload
|
||||||
|
|
||||||
from models.ghost_content import Post, Author, Tag, PostTag
|
from models.ghost_content import Post, Author, Tag, PostTag
|
||||||
from cart.models.page_config import PageConfig
|
from shared.models.page_config import PageConfig
|
||||||
from models.tag_group import TagGroup, TagGroupTag
|
from models.tag_group import TagGroup, TagGroupTag
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from ..ghost_db import DBClient # adjust import path
|
from ..ghost_db import DBClient # adjust import path
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from models.ghost_content import PostLike
|
from models.ghost_content import PostLike
|
||||||
from events.models.calendars import CalendarEntry, CalendarEntryPost
|
from shared.models.calendars import CalendarEntry, CalendarEntryPost
|
||||||
from quart import g
|
from quart import g
|
||||||
|
|
||||||
async def posts_data(
|
async def posts_data(
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def register():
|
|||||||
@require_admin
|
@require_admin
|
||||||
async def admin(slug: str):
|
async def admin(slug: str):
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from cart.models.page_config import PageConfig
|
from shared.models.page_config import PageConfig
|
||||||
from sqlalchemy import select as sa_select
|
from sqlalchemy import select as sa_select
|
||||||
|
|
||||||
# Load features for page admin
|
# Load features for page admin
|
||||||
@@ -62,7 +62,7 @@ def register():
|
|||||||
@require_admin
|
@require_admin
|
||||||
async def update_features(slug: str):
|
async def update_features(slug: str):
|
||||||
"""Update PageConfig.features for a page."""
|
"""Update PageConfig.features for a page."""
|
||||||
from cart.models.page_config import PageConfig
|
from shared.models.page_config import PageConfig
|
||||||
from models.ghost_content import Post
|
from models.ghost_content import Post
|
||||||
from sqlalchemy import select as sa_select
|
from sqlalchemy import select as sa_select
|
||||||
from quart import jsonify
|
from quart import jsonify
|
||||||
@@ -129,7 +129,7 @@ def register():
|
|||||||
@require_admin
|
@require_admin
|
||||||
async def update_sumup(slug: str):
|
async def update_sumup(slug: str):
|
||||||
"""Update PageConfig SumUp credentials for a page."""
|
"""Update PageConfig SumUp credentials for a page."""
|
||||||
from cart.models.page_config import PageConfig
|
from shared.models.page_config import PageConfig
|
||||||
from sqlalchemy import select as sa_select
|
from sqlalchemy import select as sa_select
|
||||||
from quart import jsonify
|
from quart import jsonify
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ def register():
|
|||||||
@require_admin
|
@require_admin
|
||||||
async def calendar_view(slug: str, calendar_id: int):
|
async def calendar_view(slug: str, calendar_id: int):
|
||||||
"""Show calendar month view for browsing entries"""
|
"""Show calendar month view for browsing entries"""
|
||||||
from events.models.calendars import Calendar
|
from shared.models.calendars import Calendar
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from quart import request
|
from quart import request
|
||||||
@@ -273,7 +273,7 @@ def register():
|
|||||||
@require_admin
|
@require_admin
|
||||||
async def entries(slug: str):
|
async def entries(slug: str):
|
||||||
from ..services.entry_associations import get_post_entry_ids
|
from ..services.entry_associations import get_post_entry_ids
|
||||||
from events.models.calendars import Calendar
|
from shared.models.calendars import Calendar
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
post_id = g.post_data["post"]["id"]
|
post_id = g.post_data["post"]["id"]
|
||||||
@@ -309,7 +309,7 @@ def register():
|
|||||||
@require_admin
|
@require_admin
|
||||||
async def toggle_entry(slug: str, entry_id: int):
|
async def toggle_entry(slug: str, entry_id: int):
|
||||||
from ..services.entry_associations import toggle_entry_association, get_post_entry_ids, get_associated_entries
|
from ..services.entry_associations import toggle_entry_association, get_post_entry_ids, get_associated_entries
|
||||||
from events.models.calendars import Calendar
|
from shared.models.calendars import Calendar
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from quart import jsonify
|
from quart import jsonify
|
||||||
|
|
||||||
@@ -603,7 +603,7 @@ def register():
|
|||||||
@require_admin
|
@require_admin
|
||||||
async def markets(slug: str):
|
async def markets(slug: str):
|
||||||
"""List markets for this page."""
|
"""List markets for this page."""
|
||||||
from market.models.market_place import MarketPlace
|
from shared.models.market_place import MarketPlace
|
||||||
from sqlalchemy import select as sa_select
|
from sqlalchemy import select as sa_select
|
||||||
|
|
||||||
post = (g.post_data or {}).get("post", {})
|
post = (g.post_data or {}).get("post", {})
|
||||||
@@ -631,7 +631,7 @@ def register():
|
|||||||
async def create_market(slug: str):
|
async def create_market(slug: str):
|
||||||
"""Create a new market for this page."""
|
"""Create a new market for this page."""
|
||||||
from ..services.markets import create_market as _create_market, MarketError
|
from ..services.markets import create_market as _create_market, MarketError
|
||||||
from market.models.market_place import MarketPlace
|
from shared.models.market_place import MarketPlace
|
||||||
from sqlalchemy import select as sa_select
|
from sqlalchemy import select as sa_select
|
||||||
from quart import jsonify
|
from quart import jsonify
|
||||||
|
|
||||||
@@ -669,7 +669,7 @@ def register():
|
|||||||
async def delete_market(slug: str, market_slug: str):
|
async def delete_market(slug: str, market_slug: str):
|
||||||
"""Soft-delete a market."""
|
"""Soft-delete a market."""
|
||||||
from ..services.markets import soft_delete_market
|
from ..services.markets import soft_delete_market
|
||||||
from market.models.market_place import MarketPlace
|
from shared.models.market_place import MarketPlace
|
||||||
from sqlalchemy import select as sa_select
|
from sqlalchemy import select as sa_select
|
||||||
from quart import jsonify
|
from quart import jsonify
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ from quart import (
|
|||||||
)
|
)
|
||||||
from .services.post_data import post_data
|
from .services.post_data import post_data
|
||||||
from .services.post_operations import toggle_post_like
|
from .services.post_operations import toggle_post_like
|
||||||
from events.models.calendars import Calendar
|
from shared.models.calendars import Calendar
|
||||||
from market.models.market_place import MarketPlace
|
from shared.models.market_place import MarketPlace
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from shared.browser.app.redis_cacher import cache_page, clear_cache
|
from shared.browser.app.redis_cacher import cache_page, clear_cache
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
from events.models.calendars import CalendarEntry, CalendarEntryPost, Calendar
|
from shared.models.calendars import CalendarEntry, CalendarEntryPost, Calendar
|
||||||
from models.ghost_content import Post
|
from models.ghost_content import Post
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import unicodedata
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from market.models.market_place import MarketPlace
|
from shared.models.market_place import MarketPlace
|
||||||
from models.ghost_content import Post
|
from models.ghost_content import Post
|
||||||
from cart.models.page_config import PageConfig
|
from shared.models.page_config import PageConfig
|
||||||
from shared.browser.app.utils import utcnow
|
from shared.browser.app.utils import utcnow
|
||||||
from glue.services.relationships import attach_child, detach_child
|
from glue.services.relationships import attach_child, detach_child
|
||||||
|
|
||||||
|
|||||||
2
glue
2
glue
Submodule glue updated: fc14d8323a...ebce44e9d9
@@ -1,216 +1,3 @@
|
|||||||
from datetime import datetime
|
from shared.models.ghost_content import ( # noqa: F401
|
||||||
from typing import List, Optional
|
Tag, Post, Author, PostAuthor, PostTag, PostLike,
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
||||||
from sqlalchemy import (
|
|
||||||
Integer,
|
|
||||||
String,
|
|
||||||
Text,
|
|
||||||
Boolean,
|
|
||||||
DateTime,
|
|
||||||
ForeignKey,
|
|
||||||
Column,
|
|
||||||
func,
|
|
||||||
)
|
)
|
||||||
from shared.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",
|
|
||||||
)
|
|
||||||
likes: Mapped[List["PostLike"]] = relationship(
|
|
||||||
"PostLike",
|
|
||||||
back_populates="post",
|
|
||||||
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")
|
|
||||||
|
|||||||
2
shared
2
shared
Submodule shared updated: da10fc4cf9...0c0f3c8416
Reference in New Issue
Block a user