fix: refactor Roster from 26 FK columns to RosterSlot junction table (#29)

- Remove card_1..card_26 FK columns from Roster ORM model
- Add RosterSlot model with (roster, slot, card) and a unique index on (roster, slot)
- Activate get_cards() helper on Roster using the new junction table
- Register RosterSlot in create_tables for SQLite dev environments
- Add migrations/migrate_roster_junction_table.py to backfill existing data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-03-04 09:33:13 -06:00
parent 8227b57875
commit 44b6222ad5
2 changed files with 84 additions and 35 deletions

View File

@ -498,51 +498,34 @@ class Roster(BaseModel):
team = ForeignKeyField(Team)
name = CharField()
roster_num = IntegerField()
card_1 = ForeignKeyField(Card)
card_2 = ForeignKeyField(Card)
card_3 = ForeignKeyField(Card)
card_4 = ForeignKeyField(Card)
card_5 = ForeignKeyField(Card)
card_6 = ForeignKeyField(Card)
card_7 = ForeignKeyField(Card)
card_8 = ForeignKeyField(Card)
card_9 = ForeignKeyField(Card)
card_10 = ForeignKeyField(Card)
card_11 = ForeignKeyField(Card)
card_12 = ForeignKeyField(Card)
card_13 = ForeignKeyField(Card)
card_14 = ForeignKeyField(Card)
card_15 = ForeignKeyField(Card)
card_16 = ForeignKeyField(Card)
card_17 = ForeignKeyField(Card)
card_18 = ForeignKeyField(Card)
card_19 = ForeignKeyField(Card)
card_20 = ForeignKeyField(Card)
card_21 = ForeignKeyField(Card)
card_22 = ForeignKeyField(Card)
card_23 = ForeignKeyField(Card)
card_24 = ForeignKeyField(Card)
card_25 = ForeignKeyField(Card)
card_26 = ForeignKeyField(Card)
def __str__(self):
return f"{self.team} Roster"
# def get_cards(self, team):
# all_cards = Card.select().where(Card.roster == self)
# this_roster = []
# return [this_roster.card1, this_roster.card2, this_roster.card3, this_roster.card4, this_roster.card5,
# this_roster.card6, this_roster.card7, this_roster.card8, this_roster.card9, this_roster.card10,
# this_roster.card11, this_roster.card12, this_roster.card13, this_roster.card14, this_roster.card15,
# this_roster.card16, this_roster.card17, this_roster.card18, this_roster.card19, this_roster.card20,
# this_roster.card21, this_roster.card22, this_roster.card23, this_roster.card24, this_roster.card25,
# this_roster.card26]
def get_cards(self):
return (
Card.select()
.join(RosterSlot)
.where(RosterSlot.roster == self)
.order_by(RosterSlot.slot)
)
class Meta:
database = db
table_name = "roster"
class RosterSlot(BaseModel):
roster = ForeignKeyField(Roster, backref="slots")
slot = IntegerField()
card = ForeignKeyField(Card, backref="roster_slots")
class Meta:
database = db
table_name = "rosterslot"
indexes = ((("roster", "slot"), True),)
class Result(BaseModel):
away_team = ForeignKeyField(Team)
home_team = ForeignKeyField(Team)
@ -744,6 +727,7 @@ if not SKIP_TABLE_CREATION:
db.create_tables(
[
Roster,
RosterSlot,
BattingStat,
PitchingStat,
Result,

View File

@ -0,0 +1,65 @@
"""
Migration: Replace 26 FK columns on Roster with RosterSlot junction table.
Creates the `rosterslot` table and migrates existing lineup data from the
card_1..card_26 columns. Safe to re-run (skips rosters already migrated).
Usage:
python migrations/migrate_roster_junction_table.py
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from app.db_engine import db, Roster, RosterSlot
SLOTS = 26
def migrate():
db.connect(reuse_if_open=True)
# Create the table if it doesn't exist yet
db.create_tables([RosterSlot], safe=True)
# Read raw rows from the old schema via plain SQL so we don't depend on
# the ORM model knowing about the legacy card_N columns.
cursor = db.execute_sql("SELECT * FROM roster")
columns = [desc[0] for desc in cursor.description]
migrated = 0
skipped = 0
with db.atomic():
for row in cursor.fetchall():
row_dict = dict(zip(columns, row))
roster_id = row_dict["id"]
already_migrated = (
RosterSlot.select().where(RosterSlot.roster == roster_id).exists()
)
if already_migrated:
skipped += 1
continue
slots_to_insert = []
for slot_num in range(1, SLOTS + 1):
col = f"card_{slot_num}_id"
card_id = row_dict.get(col)
if card_id is not None:
slots_to_insert.append(
{"roster": roster_id, "slot": slot_num, "card": card_id}
)
if slots_to_insert:
RosterSlot.insert_many(slots_to_insert).execute()
migrated += 1
print(f"Migration complete: {migrated} rosters migrated, {skipped} already done.")
db.close()
if __name__ == "__main__":
migrate()