from sqlalchemy import Boolean, Column, Date, DateTime, ForeignKey, Integer, String, Table, UniqueConstraint from sqlalchemy.sql import func from app.extensions import db # ── Many-to-many: references ↔ musicians ───────────────────── reference_musicians = Table( "reference_musicians", db.metadata, Column("reference_id", Integer, ForeignKey("references.id", ondelete="CASCADE"), primary_key=True), Column("musician_id", Integer, ForeignKey("musicians.id", ondelete="CASCADE"), primary_key=True), ) class Musician(db.Model): __tablename__ = "musicians" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, nullable=False, unique=True) references = db.relationship("Reference", secondary=reference_musicians, back_populates="musicians") instrument_entries = db.relationship("TuneByInstrument", back_populates="learned_from_musician") def to_dict(self): return {"id": self.id, "name": self.name} class Source(db.Model): __tablename__ = "sources" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, nullable=False, unique=True) tunes = db.relationship("Tune", back_populates="source") def to_dict(self): return {"id": self.id, "name": self.name} class Instrument(db.Model): __tablename__ = "instruments" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, nullable=False, unique=True) entries = db.relationship("TuneByInstrument", back_populates="instrument") def to_dict(self): return {"id": self.id, "name": self.name} class Tuning(db.Model): __tablename__ = "tunings" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, nullable=False, unique=True) entries = db.relationship("TuneByInstrument", back_populates="tuning") def to_dict(self): return {"id": self.id, "name": self.name} class Tune(db.Model): __tablename__ = "tunes" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, nullable=True) key = Column(String, nullable=True) modal = Column(Boolean, nullable=True) source_id = Column(Integer, ForeignKey("sources.id", ondelete="SET NULL"), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=True) source = db.relationship("Source", back_populates="tunes") instruments = db.relationship("TuneByInstrument", back_populates="tune", cascade="all, delete-orphan") notes = db.relationship("TuneNote", back_populates="tune", cascade="all, delete-orphan") references = db.relationship("Reference", back_populates="tune", cascade="all, delete-orphan") def to_dict(self): return { "id": self.id, "name": self.name, "key": self.key, "modal": self.modal, "source_id": self.source_id, "created_at": self.created_at.isoformat() if self.created_at else None, "source": self.source.to_dict() if self.source else None, "instruments": [i.to_dict() for i in self.instruments], "notes": [n.to_dict() for n in self.notes], "references": [r.to_dict() for r in self.references], } class TuneByInstrument(db.Model): __tablename__ = "tune_by_instrument" __table_args__ = (UniqueConstraint("tune_id", "instrument_id", name="uq_tune_instrument"),) id = Column(Integer, primary_key=True, autoincrement=True) tune_id = Column(Integer, ForeignKey("tunes.id", ondelete="CASCADE"), nullable=False) instrument_id = Column(Integer, ForeignKey("instruments.id", ondelete="CASCADE"), nullable=False) learned_from_id = Column(Integer, ForeignKey("musicians.id", ondelete="SET NULL"), nullable=True) tuning_id = Column(Integer, ForeignKey("tunings.id", ondelete="SET NULL"), nullable=True) date_learned = Column(Date, nullable=True) callable = Column(Boolean, nullable=True) review = Column(Boolean, nullable=True) to_learn = Column(Boolean, nullable=True, default=False) difficulty = Column(String, nullable=True) tune = db.relationship("Tune", back_populates="instruments") instrument = db.relationship("Instrument", back_populates="entries") tuning = db.relationship("Tuning", back_populates="entries") learned_from_musician = db.relationship("Musician", back_populates="instrument_entries") notes = db.relationship("TuneByInstrumentNote", back_populates="entry", cascade="all, delete-orphan") def to_dict(self): return { "id": self.id, "tune_id": self.tune_id, "instrument_id": self.instrument_id, "instrument_name": self.instrument.name if self.instrument else None, "learned_from_id": self.learned_from_id, "learned_from": self.learned_from_musician.name if self.learned_from_musician else None, "date_learned": self.date_learned.isoformat() if self.date_learned else None, "tuning_id": self.tuning_id, "tuning": self.tuning.name if self.tuning else None, "callable": self.callable, "review": self.review, "to_learn": self.to_learn, "difficulty": self.difficulty, "notes": [n.to_dict() for n in self.notes], } class TuneNote(db.Model): __tablename__ = "tune_notes" id = Column(Integer, primary_key=True, autoincrement=True) tune_id = Column(Integer, ForeignKey("tunes.id", ondelete="CASCADE"), nullable=False) note = Column(String, nullable=True) tune = db.relationship("Tune", back_populates="notes") def to_dict(self): return {"id": self.id, "tune_id": self.tune_id, "note": self.note} class TuneByInstrumentNote(db.Model): __tablename__ = "tune_by_instrument_notes" id = Column(Integer, primary_key=True, autoincrement=True) tune_by_instrument_id = Column(Integer, ForeignKey("tune_by_instrument.id", ondelete="CASCADE"), nullable=False) note = Column(String, nullable=True) entry = db.relationship("TuneByInstrument", back_populates="notes") def to_dict(self): return {"id": self.id, "tune_by_instrument_id": self.tune_by_instrument_id, "note": self.note} class ReferenceSite(db.Model): __tablename__ = "reference_sites" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, nullable=False, unique=True) references = db.relationship("Reference", back_populates="site") def to_dict(self): return {"id": self.id, "name": self.name} class Reference(db.Model): __tablename__ = "references" id = Column(Integer, primary_key=True, autoincrement=True) tune_id = Column(Integer, ForeignKey("tunes.id", ondelete="CASCADE"), nullable=True) link = Column(String, nullable=True) site_id = Column(Integer, ForeignKey("reference_sites.id", ondelete="SET NULL"), nullable=True) tune = db.relationship("Tune", back_populates="references") site = db.relationship("ReferenceSite", back_populates="references") musicians = db.relationship("Musician", secondary=reference_musicians, back_populates="references") def to_dict(self): return { "id": self.id, "tune_id": self.tune_id, "link": self.link, "site_id": self.site_id, "site": self.site.to_dict() if self.site else None, "musicians": [m.to_dict() for m in self.musicians], }