Decouple cart: use shared.models for all cross-app imports
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 50s

- Replace all imports from blog.models, market.models, events.models
  and bare models.* with shared.models equivalents
- Convert cart/models/order.py and page_config.py to re-export stubs
- 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:58:10 +00:00
parent d6d82664d6
commit 8527ddb84b
14 changed files with 32 additions and 183 deletions

4
app.py
View File

@@ -81,8 +81,8 @@ async def cart_context() -> dict:
def create_app() -> "Quart": def create_app() -> "Quart":
from blog.models.ghost_content import Post from shared.models.ghost_content import Post
from models.page_config import PageConfig from shared.models.page_config import PageConfig
app = create_base_app( app = create_base_app(
"cart", "cart",

View File

@@ -10,10 +10,10 @@ from quart import Blueprint, g, request, jsonify
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from market.models.market import CartItem from shared.models.market import CartItem
from market.models.market_place import MarketPlace from shared.models.market_place import MarketPlace
from events.models.calendars import CalendarEntry, Calendar from shared.models.calendars import CalendarEntry, Calendar
from blog.models.ghost_content import Post from shared.models.ghost_content import Post
from shared.browser.app.csrf import csrf_exempt from shared.browser.app.csrf import csrf_exempt
from shared.infrastructure.cart_identity import current_cart_identity from shared.infrastructure.cart_identity import current_cart_identity

View File

@@ -5,9 +5,9 @@ from __future__ import annotations
from quart import Blueprint, g, request, render_template, redirect, url_for, make_response from quart import Blueprint, g, request, render_template, redirect, url_for, make_response
from sqlalchemy import select from sqlalchemy import select
from models.order import Order from shared.models.order import Order
from blog.models.ghost_content import Post from shared.models.ghost_content import Post
from market.models.market_place import MarketPlace from shared.models.market_place import MarketPlace
from glue.services.order_lifecycle import get_entries_for_order from glue.services.order_lifecycle import get_entries_for_order
from .services import ( from .services import (
current_cart_identity, current_cart_identity,

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from events.models.calendars import CalendarEntry from shared.models.calendars import CalendarEntry
from .identity import current_cart_identity from .identity import current_cart_identity

View File

@@ -7,11 +7,11 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from market.models.market import Product, CartItem from shared.models.market import Product, CartItem
from models.order import Order, OrderItem from shared.models.order import Order, OrderItem
from events.models.calendars import CalendarEntry, Calendar from shared.models.calendars import CalendarEntry, Calendar
from models.page_config import PageConfig from shared.models.page_config import PageConfig
from market.models.market_place import MarketPlace from shared.models.market_place import MarketPlace
from shared.config import config from shared.config import config
from shared.events import emit_event from shared.events import emit_event
from glue.services.order_lifecycle import claim_entries_for_order from glue.services.order_lifecycle import claim_entries_for_order

View File

@@ -1,8 +1,8 @@
from sqlalchemy import update, func, select from sqlalchemy import update, func, select
from market.models.market import CartItem from shared.models.market import CartItem
from market.models.market_place import MarketPlace from shared.models.market_place import MarketPlace
from models.order import Order from shared.models.order import Order
async def clear_cart_for_order(session, order: Order, *, page_post_id: int | None = None) -> None: async def clear_cart_for_order(session, order: Order, *, page_post_id: int | None = None) -> None:

View File

@@ -1,7 +1,7 @@
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from market.models.market import CartItem from shared.models.market import CartItem
from .identity import current_cart_identity from .identity import current_cart_identity
async def get_cart(session): async def get_cart(session):

View File

@@ -12,11 +12,11 @@ from collections import defaultdict
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from market.models.market import CartItem from shared.models.market import CartItem
from market.models.market_place import MarketPlace from shared.models.market_place import MarketPlace
from events.models.calendars import CalendarEntry, Calendar from shared.models.calendars import CalendarEntry, Calendar
from blog.models.ghost_content import Post from shared.models.ghost_content import Post
from models.page_config import PageConfig from shared.models.page_config import PageConfig
from .identity import current_cart_identity from .identity import current_cart_identity

View File

@@ -5,8 +5,8 @@ from sqlalchemy import select, func, or_, cast, String, exists
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from market.models.market import Product from shared.models.market import Product
from models.order import Order, OrderItem from shared.models.order import Order, OrderItem
from shared.browser.app.payments.sumup import create_checkout as sumup_create_checkout from shared.browser.app.payments.sumup import create_checkout as sumup_create_checkout
from shared.config import config from shared.config import config

View File

@@ -5,8 +5,8 @@ from sqlalchemy import select, func, or_, cast, String, exists
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from market.models.market import Product from shared.models.market import Product
from models.order import Order, OrderItem from shared.models.order import Order, OrderItem
from shared.browser.app.payments.sumup import create_checkout as sumup_create_checkout from shared.browser.app.payments.sumup import create_checkout as sumup_create_checkout
from shared.config import config from shared.config import config

2
glue

Submodule glue updated: fc14d8323a...ebce44e9d9

View File

@@ -1,114 +1 @@
from __future__ import annotations from shared.models.order import Order, OrderItem # noqa: F401
from datetime import datetime
from typing import Optional, List
from sqlalchemy import Integer, String, DateTime, ForeignKey, Numeric, func, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from shared.db.base import Base
class Order(Base):
__tablename__ = "orders"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), nullable=True)
session_id: Mapped[Optional[str]] = mapped_column(String(64), index=True, nullable=True)
page_config_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("page_configs.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
status: Mapped[str] = mapped_column(
String(32),
nullable=False,
default="pending",
server_default="pending",
)
currency: Mapped[str] = mapped_column(String(16), nullable=False, default="GBP")
total_amount: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False)
# free-form description for the order
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True, index=True)
# SumUp reference string (what we send as checkout_reference)
sumup_reference: Mapped[Optional[str]] = mapped_column(
String(255),
nullable=True,
index=True,
)
# SumUp integration fields
sumup_checkout_id: Mapped[Optional[str]] = mapped_column(
String(128),
nullable=True,
index=True,
)
sumup_status: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
sumup_hosted_url: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
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(),
onupdate=func.now(),
)
items: Mapped[List["OrderItem"]] = relationship(
"OrderItem",
back_populates="order",
cascade="all, delete-orphan",
lazy="selectin",
)
page_config: Mapped[Optional["PageConfig"]] = relationship(
"PageConfig",
foreign_keys=[page_config_id],
lazy="selectin",
)
class OrderItem(Base):
__tablename__ = "order_items"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
order_id: Mapped[int] = mapped_column(
ForeignKey("orders.id", ondelete="CASCADE"),
nullable=False,
)
product_id: Mapped[int] = mapped_column(
ForeignKey("products.id"),
nullable=False,
)
product_title: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
quantity: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
unit_price: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False)
currency: Mapped[str] = mapped_column(String(16), nullable=False, default="GBP")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=func.now(),
)
order: Mapped["Order"] = relationship(
"Order",
back_populates="items",
)
# NEW: link each order item to its product
product: Mapped["Product"] = relationship(
"Product",
back_populates="order_items",
lazy="selectin",
)

View File

@@ -1,39 +1 @@
from __future__ import annotations from shared.models.page_config import PageConfig # noqa: F401
from datetime import datetime
from typing import Optional
from sqlalchemy import Integer, String, Text, DateTime, func, JSON, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from shared.db.base import Base
class PageConfig(Base):
__tablename__ = "page_configs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
container_type: Mapped[str] = mapped_column(
String(32), nullable=False, server_default=text("'page'"),
)
container_id: Mapped[int] = mapped_column(Integer, nullable=False)
features: Mapped[dict] = mapped_column(
JSON, nullable=False, server_default="{}"
)
# Per-page SumUp credentials (NULL until configured)
sumup_merchant_code: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
sumup_api_key: Mapped[Optional[str]] = mapped_column(Text(), nullable=True)
sumup_checkout_prefix: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
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), nullable=True
)

2
shared

Submodule shared updated: da10fc4cf9...0c0f3c8416