refactor: consolidate 10+ model files into 2 modules

- models.py: All application models (users, decks, sessions, groups, imports, etc.)
- mtg_models.py: All MTG card data models (sets, cards, mirrors, links)
- Removed: mirror_models.py, user_data.py, user_deck.py, card_import_batch.py,
  user_card_import.py, user_card_import_record.py, relationships.py
- Added __backup__/ directory with original files for reference
- DecklistFile.card_links cross-model relationship resolved via late import
This commit is contained in:
2026-08-26 03:45:15 +00:00
parent e507625d86
commit 03f5433583
10 changed files with 1002 additions and 899 deletions
+116 -3
View File
@@ -1,14 +1,29 @@
"""
SQLAlchemy ORM models for the MTG database (mtgjson.com data).
SQLAlchemy ORM models for MTG card data and mirrors.
Models mirror the mtg_cards and mtg_sets tables in the MTG PostgreSQL database.
Consolidated from mtg_models.py and mirror_models.py into a single module.
This file contains all MTG card data models:
- MtgSet, MtgCard: Canonical card/set data from mtgjson.com (mtg_data DB)
- MtgCardMirror: Mirrored card data copied to mtgonline DB for fast deckbuilding queries
- DeckCardLink: Junction table linking user decks to mirrored cards
Cross-model references to application models (e.g., DeckCardLink.deck → DecklistFile)
use string-based relationship() calls to avoid circular imports.
"""
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Index
from sqlalchemy import (
Column, Integer, String, Text, DateTime, ForeignKey, Index,
UniqueConstraint,
)
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.database import Base
# ---------------------------------------------------------------------------
# MTG Set and Card (canonical data from mtgjson.com)
# ---------------------------------------------------------------------------
class MtgSet(Base):
"""MTG Set model."""
__tablename__ = "mtg_sets"
@@ -65,7 +80,105 @@ class MtgCard(Base):
return f"<MtgCard {self.name} ({self.set_id})>"
# ---------------------------------------------------------------------------
# Card Mirror and Deck Links (mtgonline DB)
# ---------------------------------------------------------------------------
class MtgCardMirror(Base):
"""
Mirrored card data for user decks.
Contains all relevant card fields copied from mtg_cards (mtg_data)
to avoid cross-DB joins during deckbuilding. Back-references source_id
to the canonical mtg_cards.id for sync tracking.
"""
__tablename__ = "mtg_cards_mirror"
id = Column(Integer, primary_key=True, index=True)
source_id = Column(
Integer,
nullable=True,
index=True,
comment="Logical reference to mtg_cards.id in mtg_data database "
"(cross-DB reference, no FK constraint)",
)
name = Column(String(255), nullable=False, index=True)
mana_cost = Column(String(255), nullable=True)
type_line = Column(String(255), nullable=True)
oracle_text = Column(Text, nullable=True)
power = Column(String(50), nullable=True)
toughness = Column(String(50), nullable=True)
rarity = Column(String(50), nullable=True)
layout = Column(String(50), nullable=True)
artist = Column(String(255), nullable=True)
flavor_text = Column(Text, nullable=True)
numbers = Column(String(100), nullable=True)
identifiers = Column(Text, nullable=True) # JSON string of all identifiers
images = Column(Text, nullable=True) # JSON string of image URLs
image = Column(Text, nullable=True) # Card image URL from MTGJSON
card_parts = Column(Text, nullable=True) # Comma-separated list of face names
keywords = Column(Text, nullable=True) # Comma-separated keywords
legalities = Column(Text, nullable=True) # JSON of format legality
set_code = Column(String(10), nullable=True, index=True)
set_name = Column(String(255), nullable=True)
# Sync tracking
synced_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
created_at = Column(DateTime, server_default=func.now())
# Relationships
deck_links = relationship(
"DeckCardLink",
back_populates="card",
cascade="all, delete-orphan",
)
def __repr__(self) -> str:
return f"<MtgCardMirror {self.name} (ID: {self.id}, source: {self.source_id})>"
class DeckCardLink(Base):
"""
Junction table linking user decks to mirrored cards.
Represents a specific quantity of a mirrored card in a specific zone
(main deck or sideboard) of a user's deck.
"""
__tablename__ = "deck_card_links"
id = Column(Integer, primary_key=True, index=True)
deck_id = Column(
Integer, ForeignKey("mtgonline_decklist_files.id", ondelete="CASCADE"),
nullable=False,
)
card_id = Column(
Integer, ForeignKey("mtg_cards_mirror.id"), nullable=False,
)
quantity = Column(Integer, nullable=False, default=1)
zone = Column(String(20), nullable=False, default="main") # 'main' or 'sideboard'
# Composite unique: a card can only appear once per zone in a deck
__table_args__ = (
UniqueConstraint("deck_id", "card_id", "zone", name="uq_deck_card_link"),
Index("idx_deck_card_deck", "deck_id"),
Index("idx_deck_card_card", "card_id"),
)
# Relationships
# String reference to DecklistFile in models.py to avoid circular imports
deck = relationship("DecklistFile", back_populates="card_links")
card = relationship("MtgCardMirror", back_populates="deck_links")
def __repr__(self) -> str:
return (
f"<DeckCardLink deck={self.deck_id} card={self.card_id} "
f"qty={self.quantity} zone={self.zone}>"
)
# ---------------------------------------------------------------------------
# Indexes for performance
# ---------------------------------------------------------------------------
Index("idx_mtg_cards_name_set", MtgCard.name, MtgCard.set_id)
Index("idx_mtg_cards_type", MtgCard.type_line)
Index("idx_mtg_cards_rarity", MtgCard.rarity)