SQLA2.0 schema updates, column width saves
This commit is contained in:
parent
ab47bb0ab4
commit
374a312797
@ -43,6 +43,7 @@ sqlalchemy.url = SET
|
||||
# sqlalchemy.url = mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_prod
|
||||
# sqlalchemy.url = mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_dev
|
||||
# sqlalchemy.url = mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2
|
||||
# sqlalchemy.url = mysql+mysqldb://musicmusterv3:musicmusterv3@localhost/musicmuster_dev_v3
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
|
||||
235
app/models.py
235
app/models.py
@ -15,8 +15,8 @@ from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
# Float,
|
||||
# ForeignKey,
|
||||
Float,
|
||||
ForeignKey,
|
||||
# func,
|
||||
Integer,
|
||||
String,
|
||||
@ -25,13 +25,16 @@ from sqlalchemy import (
|
||||
)
|
||||
# from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import (
|
||||
# backref,
|
||||
backref,
|
||||
declarative_base,
|
||||
# relationship,
|
||||
# RelationshipProperty
|
||||
relationship,
|
||||
RelationshipProperty
|
||||
)
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
from sqlalchemy.orm.exc import (
|
||||
# MultipleResultsFound,
|
||||
NoResultFound
|
||||
)
|
||||
# from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
# from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
|
||||
#
|
||||
# from config import Config
|
||||
# from helpers import (
|
||||
@ -45,17 +48,17 @@ from sqlalchemy.orm import (
|
||||
Base = declarative_base()
|
||||
#
|
||||
#
|
||||
# # Database classes
|
||||
# class NoteColours(Base):
|
||||
# __tablename__ = 'notecolours'
|
||||
#
|
||||
# id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
# substring: str = Column(String(256), index=False)
|
||||
# colour: str = Column(String(21), index=False)
|
||||
# enabled: bool = Column(Boolean, default=True, index=True)
|
||||
# is_regex: bool = Column(Boolean, default=False, index=False)
|
||||
# is_casesensitive: bool = Column(Boolean, default=False, index=False)
|
||||
# order: int = Column(Integer, index=True)
|
||||
# Database classes
|
||||
class NoteColours(Base):
|
||||
__tablename__ = 'notecolours'
|
||||
|
||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
substring: str = Column(String(256), index=False)
|
||||
colour: str = Column(String(21), index=False)
|
||||
enabled: bool = Column(Boolean, default=True, index=True)
|
||||
is_regex: bool = Column(Boolean, default=False, index=False)
|
||||
is_casesensitive: bool = Column(Boolean, default=False, index=False)
|
||||
order: int = Column(Integer, index=True)
|
||||
#
|
||||
# def __init__(
|
||||
# self, session: Session, substring: str, colour: str,
|
||||
@ -119,17 +122,17 @@ Base = declarative_base()
|
||||
# return rec.colour
|
||||
#
|
||||
# return None
|
||||
#
|
||||
#
|
||||
# class Notes(Base):
|
||||
# __tablename__ = 'notes'
|
||||
#
|
||||
# id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
# playlist_id: int = Column(Integer, ForeignKey('playlists.id'))
|
||||
# playlist: RelationshipProperty = relationship(
|
||||
# "Playlists", back_populates="notes", lazy="joined")
|
||||
# row: int = Column(Integer, nullable=False)
|
||||
# note: str = Column(String(256), index=False)
|
||||
|
||||
|
||||
class Notes(Base):
|
||||
__tablename__ = 'notes'
|
||||
|
||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
playlist_id: int = Column(Integer, ForeignKey('playlists.id'))
|
||||
playlist: RelationshipProperty = relationship(
|
||||
"Playlists", back_populates="notes", lazy="joined")
|
||||
row: int = Column(Integer, nullable=False)
|
||||
note: str = Column(String(256), index=False)
|
||||
#
|
||||
# def __init__(self, session: Session, playlist_id: int,
|
||||
# row: int, text: str) -> None:
|
||||
@ -204,16 +207,16 @@ Base = declarative_base()
|
||||
# if text:
|
||||
# self.note = text
|
||||
# session.flush()
|
||||
#
|
||||
#
|
||||
# class Playdates(Base):
|
||||
# __tablename__ = 'playdates'
|
||||
#
|
||||
# id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
# lastplayed: datetime = Column(DateTime, index=True, default=None)
|
||||
# track_id: int = Column(Integer, ForeignKey('tracks.id'))
|
||||
# track: RelationshipProperty = relationship(
|
||||
# "Tracks", back_populates="playdates", lazy="joined")
|
||||
|
||||
|
||||
class Playdates(Base):
|
||||
__tablename__ = 'playdates'
|
||||
|
||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
lastplayed: datetime = Column(DateTime, index=True, default=None)
|
||||
track_id: int = Column(Integer, ForeignKey('tracks.id'))
|
||||
track: RelationshipProperty = relationship(
|
||||
"Tracks", back_populates="playdates", lazy="joined")
|
||||
#
|
||||
# def __init__(self, session: Session, track_id: int) -> None:
|
||||
# """Record that track was played"""
|
||||
@ -294,7 +297,7 @@ class Playlists(Base):
|
||||
# if row is None:
|
||||
# row = self.next_free_row(session, self.id)
|
||||
#
|
||||
# PlaylistTracks(session, self.id, track_id, row)
|
||||
# xPlaylistTracks(session, self.id, track_id, row)
|
||||
#
|
||||
# def close(self, session: Session) -> None:
|
||||
# """Record playlist as no longer loaded"""
|
||||
@ -353,7 +356,7 @@ class Playlists(Base):
|
||||
# """Return next free row for this playlist"""
|
||||
#
|
||||
# max_notes_row = Notes.max_used_row(session, playlist_id)
|
||||
# max_tracks_row = PlaylistTracks.max_used_row(session, playlist_id)
|
||||
# max_tracks_row = xPlaylistTracks.max_used_row(session, playlist_id)
|
||||
#
|
||||
# if max_notes_row is not None and max_tracks_row is not None:
|
||||
# return max(max_notes_row, max_tracks_row) + 1
|
||||
@ -390,7 +393,7 @@ class Playlists(Base):
|
||||
#
|
||||
# class PlaylistTracks(Base):
|
||||
# __tablename__ = 'playlist_tracks'
|
||||
#
|
||||
#
|
||||
# id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
# playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
|
||||
# primary_key=True)
|
||||
@ -406,6 +409,25 @@ class Playlists(Base):
|
||||
# cascade="all, delete-orphan"
|
||||
# )
|
||||
# )
|
||||
class PlaylistRows(Base):
|
||||
__tablename__ = 'playlist_rows'
|
||||
|
||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
|
||||
primary_key=True)
|
||||
row: int = Column(Integer, nullable=False)
|
||||
text: str = Column(String(2048), index=False)
|
||||
track_id: int = Column(Integer, ForeignKey('tracks.id'), primary_key=True)
|
||||
tracks: RelationshipProperty = relationship("Tracks")
|
||||
playlist: RelationshipProperty = relationship(
|
||||
Playlists,
|
||||
backref=backref(
|
||||
"playlist_tracks",
|
||||
collection_class=attribute_mapped_collection("row"),
|
||||
lazy="joined",
|
||||
cascade="all, delete-orphan"
|
||||
)
|
||||
)
|
||||
# # Ensure row numbers are unique within each playlist
|
||||
# __table_args__ = (UniqueConstraint
|
||||
# ('row', 'playlist_id', name="uniquerow"),
|
||||
@ -414,7 +436,7 @@ class Playlists(Base):
|
||||
# def __init__(
|
||||
# self, session: Session, playlist_id: int, track_id: int,
|
||||
# row: int) -> None:
|
||||
# log.debug(f"PlaylistTracks.__init__({playlist_id=}, {track_id=}, {row=})")
|
||||
# log.debug(f"xPlaylistTracks.__init__({playlist_id=}, {track_id=}, {row=})")
|
||||
#
|
||||
# self.playlist_id = playlist_id
|
||||
# self.track_id = track_id
|
||||
@ -430,7 +452,7 @@ class Playlists(Base):
|
||||
# """
|
||||
#
|
||||
# last_row = session.query(
|
||||
# func.max(PlaylistTracks.row)
|
||||
# func.max(xPlaylistTracks.row)
|
||||
# ).filter_by(playlist_id=playlist_id).first()
|
||||
# # if there are no rows, the above returns (None, ) which is True
|
||||
# if last_row and last_row[0] is not None:
|
||||
@ -443,65 +465,74 @@ class Playlists(Base):
|
||||
# to_row: int, to_playlist_id: int) -> None:
|
||||
# """Move row to another playlist"""
|
||||
#
|
||||
# session.query(PlaylistTracks).filter(
|
||||
# PlaylistTracks.playlist_id == from_playlist_id,
|
||||
# PlaylistTracks.row == from_row).update(
|
||||
# session.query(xPlaylistTracks).filter(
|
||||
# xPlaylistTracks.playlist_id == from_playlist_id,
|
||||
# xPlaylistTracks.row == from_row).update(
|
||||
# {'playlist_id': to_playlist_id, 'row': to_row}, False)
|
||||
#
|
||||
#
|
||||
# class Settings(Base):
|
||||
# __tablename__ = 'settings'
|
||||
#
|
||||
# id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
# name: str = Column(String(32), nullable=False, unique=True)
|
||||
# f_datetime: datetime = Column(DateTime, default=None, nullable=True)
|
||||
# f_int: int = Column(Integer, default=None, nullable=True)
|
||||
# f_string: str = Column(String(128), default=None, nullable=True)
|
||||
#
|
||||
# @classmethod
|
||||
# def get_int_settings(cls, session: Session, name: str) -> "Settings":
|
||||
# """Get setting for an integer or return new setting record"""
|
||||
#
|
||||
# int_setting: Settings
|
||||
#
|
||||
# try:
|
||||
# int_setting = session.query(cls).filter(
|
||||
# cls.name == name).one()
|
||||
# except NoResultFound:
|
||||
# int_setting = Settings()
|
||||
# int_setting.name = name
|
||||
# int_setting.f_int = None
|
||||
# session.add(int_setting)
|
||||
# session.flush()
|
||||
# return int_setting
|
||||
#
|
||||
# def update(self, session: Session, data):
|
||||
# for key, value in data.items():
|
||||
# assert hasattr(self, key)
|
||||
# setattr(self, key, value)
|
||||
# session.flush()
|
||||
#
|
||||
#
|
||||
# class Tracks(Base):
|
||||
# __tablename__ = 'tracks'
|
||||
#
|
||||
# id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
# title: str = Column(String(256), index=True)
|
||||
# artist: str = Column(String(256), index=True)
|
||||
# duration: int = Column(Integer, index=True)
|
||||
# start_gap: int = Column(Integer, index=False)
|
||||
# fade_at: int = Column(Integer, index=False)
|
||||
# silence_at: int = Column(Integer, index=False)
|
||||
# path: str = Column(String(2048), index=False, nullable=False)
|
||||
# mtime: float = Column(Float, index=True)
|
||||
# lastplayed: datetime = Column(DateTime, index=True, default=None)
|
||||
# playlists: RelationshipProperty = relationship("PlaylistTracks",
|
||||
# back_populates="tracks",
|
||||
# lazy="joined")
|
||||
# playdates: RelationshipProperty = relationship("Playdates",
|
||||
# back_populates="track"
|
||||
# "",
|
||||
# lazy="joined")
|
||||
|
||||
|
||||
class Settings(Base):
|
||||
"""Manage settings"""
|
||||
|
||||
__tablename__ = 'settings'
|
||||
|
||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name: str = Column(String(64), nullable=False, unique=True)
|
||||
f_datetime: datetime = Column(DateTime, default=None, nullable=True)
|
||||
f_int: int = Column(Integer, default=None, nullable=True)
|
||||
f_string: str = Column(String(128), default=None, nullable=True)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
value = self.f_datetime or self.f_int or self.f_string
|
||||
return f"<Settings(id={self.id}, name={self.name}, {value=}>"
|
||||
|
||||
@classmethod
|
||||
def get_int_settings(cls, session: Session, name: str) -> "Settings":
|
||||
"""Get setting for an integer or return new setting record"""
|
||||
|
||||
int_setting: Settings
|
||||
|
||||
try:
|
||||
int_setting = session.execute(
|
||||
select(cls)
|
||||
.where(cls.name == name)
|
||||
).scalar_one()
|
||||
|
||||
except NoResultFound:
|
||||
int_setting = Settings()
|
||||
int_setting.name = name
|
||||
int_setting.f_int = None
|
||||
session.add(int_setting)
|
||||
|
||||
return int_setting
|
||||
|
||||
def update(self, session: Session, data):
|
||||
for key, value in data.items():
|
||||
assert hasattr(self, key)
|
||||
setattr(self, key, value)
|
||||
session.flush()
|
||||
|
||||
|
||||
class Tracks(Base):
|
||||
__tablename__ = 'tracks'
|
||||
|
||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title: str = Column(String(256), index=True)
|
||||
artist: str = Column(String(256), index=True)
|
||||
duration: int = Column(Integer, index=True)
|
||||
start_gap: int = Column(Integer, index=False)
|
||||
fade_at: int = Column(Integer, index=False)
|
||||
silence_at: int = Column(Integer, index=False)
|
||||
path: str = Column(String(2048), index=False, nullable=False)
|
||||
mtime: float = Column(Float, index=True)
|
||||
lastplayed: datetime = Column(DateTime, index=True, default=None)
|
||||
playlists: RelationshipProperty = relationship("PlaylistRows",
|
||||
back_populates="tracks",
|
||||
lazy="joined")
|
||||
playdates: RelationshipProperty = relationship("Playdates",
|
||||
back_populates="track"
|
||||
"",
|
||||
lazy="joined")
|
||||
#
|
||||
# def __init__(
|
||||
# self,
|
||||
|
||||
@ -31,17 +31,16 @@ from config import Config
|
||||
# from datetime import datetime, timedelta
|
||||
# from helpers import get_relative_date, open_in_audacity
|
||||
# from log import log.debug, log.error
|
||||
# from models import (
|
||||
# Notes,
|
||||
# Playdates,
|
||||
# Playlists,
|
||||
# PlaylistTracks,
|
||||
# Settings,
|
||||
# Tracks,
|
||||
# NoteColours
|
||||
# )
|
||||
from models import (
|
||||
# Notes,
|
||||
# Playdates,
|
||||
# Playlists,
|
||||
# PlaylistTracks,
|
||||
Settings,
|
||||
# Tracks,
|
||||
# NoteColours
|
||||
)
|
||||
from dbconfig import Session
|
||||
#from collections import namedtuple
|
||||
# start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
|
||||
#
|
||||
#
|
||||
@ -94,9 +93,9 @@ class PlaylistTab(QTableWidget):
|
||||
def __init__(self, musicmuster: QMainWindow, session: Session,
|
||||
playlist_id: int, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.musicmuster: QMainWindow = musicmuster
|
||||
self.playlist_id: int = playlist_id
|
||||
|
||||
# self.menu: Optional[QMenu] = None
|
||||
# self.current_track_start_time: Optional[datetime] = None
|
||||
#
|
||||
@ -111,19 +110,19 @@ class PlaylistTab(QTableWidget):
|
||||
# self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.setRowCount(0)
|
||||
self.setColumnCount(len(columns))
|
||||
# Add header row
|
||||
|
||||
# Header row
|
||||
for idx in [a for a in range(len(columns))]:
|
||||
item: QTableWidgetItem = QTableWidgetItem()
|
||||
self.setHorizontalHeaderItem(idx, item)
|
||||
|
||||
self.horizontalHeader().setMinimumSectionSize(0)
|
||||
# self._set_column_widths(session)
|
||||
self._set_column_widths(session)
|
||||
# Set column headings sorted by idx
|
||||
self.setHorizontalHeaderLabels(
|
||||
[a[0].heading for a in list(sorted(columns.values(),
|
||||
key=lambda item: item[0][0]))]
|
||||
)
|
||||
#
|
||||
|
||||
# self.setDragEnabled(True)
|
||||
# self.setAcceptDrops(True)
|
||||
# self.viewport().setAcceptDrops(True)
|
||||
@ -146,15 +145,33 @@ class PlaylistTab(QTableWidget):
|
||||
# self.row_filter: Optional[str] = None
|
||||
# self.editing_cell: bool = False
|
||||
# self.selecting_in_progress = False
|
||||
# Connect signals
|
||||
# self.cellChanged.connect(self._cell_changed)
|
||||
# self.cellClicked.connect(self._edit_note_cell)
|
||||
# self.doubleClicked.connect(self._edit_cell)
|
||||
# self.cellEditingStarted.connect(self._cell_edit_started)
|
||||
# self.cellEditingEnded.connect(self._cell_edit_ended)
|
||||
# self.cellEditingStarted.connect(self._cell_edit_started)
|
||||
# self.doubleClicked.connect(self._edit_cell)
|
||||
self.horizontalHeader().sectionResized.connect(self._column_resize)
|
||||
#
|
||||
# # Now load our tracks and notes
|
||||
# self.populate(session, self.playlist_id)
|
||||
#
|
||||
|
||||
def _column_resize(self, idx, old, new):
|
||||
"""
|
||||
Called when column widths are changed.
|
||||
|
||||
Save column sizes to database
|
||||
"""
|
||||
|
||||
with Session() as session:
|
||||
for column_name, data in columns.items():
|
||||
idx = data[0].idx
|
||||
width = self.columnWidth(idx)
|
||||
attribute_name = f"playlist_{column_name}_col_width"
|
||||
record = Settings.get_int_settings(session, attribute_name)
|
||||
if record.f_int != self.columnWidth(idx):
|
||||
record.update(session, {'f_int': width})
|
||||
|
||||
# def __repr__(self) -> str:
|
||||
# return f"<PlaylistTab(id={self.playlist_id}"
|
||||
#
|
||||
@ -1592,17 +1609,18 @@ class PlaylistTab(QTableWidget):
|
||||
#
|
||||
# # Reset extended selection
|
||||
# self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
#
|
||||
# def _set_column_widths(self, session: Session) -> None:
|
||||
# """Column widths from settings"""
|
||||
#
|
||||
# for column in range(self.columnCount()):
|
||||
# name: str = f"playlist_col_{str(column)}_width"
|
||||
# record: Settings = Settings.get_int_settings(session, name)
|
||||
# if record and record.f_int is not None:
|
||||
# self.setColumnWidth(column, record.f_int)
|
||||
# else:
|
||||
# self.setColumnWidth(column, Config.DEFAULT_COLUMN_WIDTH)
|
||||
|
||||
def _set_column_widths(self, session: Session) -> None:
|
||||
"""Column widths from settings"""
|
||||
|
||||
for column_name, data in columns.items():
|
||||
idx = data[0].idx
|
||||
attr_name = f"playlist_{column_name}_col_width"
|
||||
record: Settings = Settings.get_int_settings(session, attr_name)
|
||||
if record and record.f_int is not None:
|
||||
self.setColumnWidth(idx, record.f_int)
|
||||
else:
|
||||
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
|
||||
#
|
||||
# def _set_next(self, row: int, session: Session) -> None:
|
||||
# """
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
"""Rename playlist_tracks to playlist_rows
|
||||
|
||||
Revision ID: 3f55ac7d80ad
|
||||
Revises: 1c4048efee96
|
||||
Create Date: 2022-07-04 20:51:59.874004
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3f55ac7d80ad'
|
||||
down_revision = '1c4048efee96'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Rename so as not to lose content
|
||||
op.rename_table('playlist_tracks', 'playlist_rows')
|
||||
|
||||
|
||||
def downgrade():
|
||||
# Rename so as not to lose content
|
||||
op.rename_table('playlist_rows', 'playlist_tracks')
|
||||
@ -0,0 +1,36 @@
|
||||
"""Increase settings.name len and add playlist_rows.notes
|
||||
|
||||
Revision ID: 51f61433256f
|
||||
Revises: 3f55ac7d80ad
|
||||
Create Date: 2022-07-04 21:21:39.830406
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '51f61433256f'
|
||||
down_revision = '3f55ac7d80ad'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('playlist_rows', sa.Column('text', sa.String(length=2048), nullable=True))
|
||||
# op.drop_index('uniquerow', table_name='playlist_rows')
|
||||
op.alter_column('playlists', 'loaded',
|
||||
existing_type=mysql.TINYINT(display_width=1),
|
||||
nullable=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('playlists', 'loaded',
|
||||
existing_type=mysql.TINYINT(display_width=1),
|
||||
nullable=True)
|
||||
# op.create_index('uniquerow', 'playlist_rows', ['row', 'playlist_id'], unique=False)
|
||||
op.drop_column('playlist_rows', 'text')
|
||||
# ### end Alembic commands ###
|
||||
Loading…
Reference in New Issue
Block a user