Decouple blog: use shared.models for all cross-app imports
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:
giles
2026-02-18 20:57:56 +00:00
parent 1ea2950310
commit c537770172
10 changed files with 21 additions and 234 deletions

View File

@@ -13,7 +13,7 @@ from sqlalchemy.orm.attributes import flag_modified # for non-Mutable JSON colu
from models.ghost_content import (
Post, Author, Tag, PostAuthor, PostTag
)
from cart.models.page_config import PageConfig
from shared.models.page_config import PageConfig
# User-centric membership models
from shared.models import User

View File

@@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload, joinedload
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

View File

@@ -1,7 +1,7 @@
from ..ghost_db import DBClient # adjust import path
from sqlalchemy import select
from models.ghost_content import PostLike
from events.models.calendars import CalendarEntry, CalendarEntryPost
from shared.models.calendars import CalendarEntry, CalendarEntryPost
from quart import g
async def posts_data(

View File

@@ -22,7 +22,7 @@ def register():
@require_admin
async def admin(slug: str):
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
# Load features for page admin
@@ -62,7 +62,7 @@ def register():
@require_admin
async def update_features(slug: str):
"""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 sqlalchemy import select as sa_select
from quart import jsonify
@@ -129,7 +129,7 @@ def register():
@require_admin
async def update_sumup(slug: str):
"""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 quart import jsonify
@@ -191,7 +191,7 @@ def register():
@require_admin
async def calendar_view(slug: str, calendar_id: int):
"""Show calendar month view for browsing entries"""
from events.models.calendars import Calendar
from shared.models.calendars import Calendar
from sqlalchemy import select
from datetime import datetime, timezone
from quart import request
@@ -273,7 +273,7 @@ def register():
@require_admin
async def entries(slug: str):
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
post_id = g.post_data["post"]["id"]
@@ -309,7 +309,7 @@ def register():
@require_admin
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 events.models.calendars import Calendar
from shared.models.calendars import Calendar
from sqlalchemy import select
from quart import jsonify
@@ -603,7 +603,7 @@ def register():
@require_admin
async def markets(slug: str):
"""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
post = (g.post_data or {}).get("post", {})
@@ -631,7 +631,7 @@ def register():
async def create_market(slug: str):
"""Create a new market for this page."""
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 quart import jsonify
@@ -669,7 +669,7 @@ def register():
async def delete_market(slug: str, market_slug: str):
"""Soft-delete a 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 quart import jsonify

View File

@@ -11,8 +11,8 @@ from quart import (
)
from .services.post_data import post_data
from .services.post_operations import toggle_post_like
from events.models.calendars import Calendar
from market.models.market_place import MarketPlace
from shared.models.calendars import Calendar
from shared.models.market_place import MarketPlace
from sqlalchemy import select
from shared.browser.app.redis_cacher import cache_page, clear_cache

View File

@@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
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

View File

@@ -6,9 +6,9 @@ import unicodedata
from sqlalchemy import select
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 cart.models.page_config import PageConfig
from shared.models.page_config import PageConfig
from shared.browser.app.utils import utcnow
from glue.services.relationships import attach_child, detach_child

2
glue

Submodule glue updated: fc14d8323a...ebce44e9d9

View File

@@ -1,216 +1,3 @@
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 shared.models.ghost_content import ( # noqa: F401
Tag, Post, Author, PostAuthor, PostTag, PostLike,
)
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

Submodule shared updated: da10fc4cf9...0c0f3c8416