SQLA2.0 schema updates, column width saves

This commit is contained in:
Keith Edmunds 2022-07-04 21:32:23 +01:00
parent ab47bb0ab4
commit 374a312797
5 changed files with 243 additions and 131 deletions

View File

@ -43,6 +43,7 @@ sqlalchemy.url = SET
# sqlalchemy.url = mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_prod # 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_dev
# sqlalchemy.url = mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2 # 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]
# post_write_hooks defines scripts or Python functions that are run # post_write_hooks defines scripts or Python functions that are run

View File

@ -15,8 +15,8 @@ from sqlalchemy import (
Boolean, Boolean,
Column, Column,
DateTime, DateTime,
# Float, Float,
# ForeignKey, ForeignKey,
# func, # func,
Integer, Integer,
String, String,
@ -25,13 +25,16 @@ from sqlalchemy import (
) )
# from sqlalchemy.exc import IntegrityError # from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import ( from sqlalchemy.orm import (
# backref, backref,
declarative_base, declarative_base,
# relationship, relationship,
# RelationshipProperty 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 config import Config
# from helpers import ( # from helpers import (
@ -45,17 +48,17 @@ from sqlalchemy.orm import (
Base = declarative_base() Base = declarative_base()
# #
# #
# # Database classes # Database classes
# class NoteColours(Base): class NoteColours(Base):
# __tablename__ = 'notecolours' __tablename__ = 'notecolours'
#
# id: int = Column(Integer, primary_key=True, autoincrement=True) id: int = Column(Integer, primary_key=True, autoincrement=True)
# substring: str = Column(String(256), index=False) substring: str = Column(String(256), index=False)
# colour: str = Column(String(21), index=False) colour: str = Column(String(21), index=False)
# enabled: bool = Column(Boolean, default=True, index=True) enabled: bool = Column(Boolean, default=True, index=True)
# is_regex: bool = Column(Boolean, default=False, index=False) is_regex: bool = Column(Boolean, default=False, index=False)
# is_casesensitive: bool = Column(Boolean, default=False, index=False) is_casesensitive: bool = Column(Boolean, default=False, index=False)
# order: int = Column(Integer, index=True) order: int = Column(Integer, index=True)
# #
# def __init__( # def __init__(
# self, session: Session, substring: str, colour: str, # self, session: Session, substring: str, colour: str,
@ -119,17 +122,17 @@ Base = declarative_base()
# return rec.colour # return rec.colour
# #
# return None # return None
#
#
# class Notes(Base): class Notes(Base):
# __tablename__ = 'notes' __tablename__ = 'notes'
#
# id: int = Column(Integer, primary_key=True, autoincrement=True) id: int = Column(Integer, primary_key=True, autoincrement=True)
# playlist_id: int = Column(Integer, ForeignKey('playlists.id')) playlist_id: int = Column(Integer, ForeignKey('playlists.id'))
# playlist: RelationshipProperty = relationship( playlist: RelationshipProperty = relationship(
# "Playlists", back_populates="notes", lazy="joined") "Playlists", back_populates="notes", lazy="joined")
# row: int = Column(Integer, nullable=False) row: int = Column(Integer, nullable=False)
# note: str = Column(String(256), index=False) note: str = Column(String(256), index=False)
# #
# def __init__(self, session: Session, playlist_id: int, # def __init__(self, session: Session, playlist_id: int,
# row: int, text: str) -> None: # row: int, text: str) -> None:
@ -204,16 +207,16 @@ Base = declarative_base()
# if text: # if text:
# self.note = text # self.note = text
# session.flush() # session.flush()
#
#
# class Playdates(Base): class Playdates(Base):
# __tablename__ = 'playdates' __tablename__ = 'playdates'
#
# id: int = Column(Integer, primary_key=True, autoincrement=True) id: int = Column(Integer, primary_key=True, autoincrement=True)
# lastplayed: datetime = Column(DateTime, index=True, default=None) lastplayed: datetime = Column(DateTime, index=True, default=None)
# track_id: int = Column(Integer, ForeignKey('tracks.id')) track_id: int = Column(Integer, ForeignKey('tracks.id'))
# track: RelationshipProperty = relationship( track: RelationshipProperty = relationship(
# "Tracks", back_populates="playdates", lazy="joined") "Tracks", back_populates="playdates", lazy="joined")
# #
# def __init__(self, session: Session, track_id: int) -> None: # def __init__(self, session: Session, track_id: int) -> None:
# """Record that track was played""" # """Record that track was played"""
@ -294,7 +297,7 @@ class Playlists(Base):
# if row is None: # if row is None:
# row = self.next_free_row(session, self.id) # 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: # def close(self, session: Session) -> None:
# """Record playlist as no longer loaded""" # """Record playlist as no longer loaded"""
@ -353,7 +356,7 @@ class Playlists(Base):
# """Return next free row for this playlist""" # """Return next free row for this playlist"""
# #
# max_notes_row = Notes.max_used_row(session, playlist_id) # 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: # if max_notes_row is not None and max_tracks_row is not None:
# return max(max_notes_row, max_tracks_row) + 1 # return max(max_notes_row, max_tracks_row) + 1
@ -406,6 +409,25 @@ class Playlists(Base):
# cascade="all, delete-orphan" # 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 # # Ensure row numbers are unique within each playlist
# __table_args__ = (UniqueConstraint # __table_args__ = (UniqueConstraint
# ('row', 'playlist_id', name="uniquerow"), # ('row', 'playlist_id', name="uniquerow"),
@ -414,7 +436,7 @@ class Playlists(Base):
# def __init__( # def __init__(
# self, session: Session, playlist_id: int, track_id: int, # self, session: Session, playlist_id: int, track_id: int,
# row: int) -> None: # 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.playlist_id = playlist_id
# self.track_id = track_id # self.track_id = track_id
@ -430,7 +452,7 @@ class Playlists(Base):
# """ # """
# #
# last_row = session.query( # last_row = session.query(
# func.max(PlaylistTracks.row) # func.max(xPlaylistTracks.row)
# ).filter_by(playlist_id=playlist_id).first() # ).filter_by(playlist_id=playlist_id).first()
# # if there are no rows, the above returns (None, ) which is True # # if there are no rows, the above returns (None, ) which is True
# if last_row and last_row[0] is not None: # 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: # to_row: int, to_playlist_id: int) -> None:
# """Move row to another playlist""" # """Move row to another playlist"""
# #
# session.query(PlaylistTracks).filter( # session.query(xPlaylistTracks).filter(
# PlaylistTracks.playlist_id == from_playlist_id, # xPlaylistTracks.playlist_id == from_playlist_id,
# PlaylistTracks.row == from_row).update( # xPlaylistTracks.row == from_row).update(
# {'playlist_id': to_playlist_id, 'row': to_row}, False) # {'playlist_id': to_playlist_id, 'row': to_row}, False)
#
#
# class Settings(Base): class Settings(Base):
# __tablename__ = 'settings' """Manage settings"""
#
# id: int = Column(Integer, primary_key=True, autoincrement=True) __tablename__ = 'settings'
# name: str = Column(String(32), nullable=False, unique=True)
# f_datetime: datetime = Column(DateTime, default=None, nullable=True) id: int = Column(Integer, primary_key=True, autoincrement=True)
# f_int: int = Column(Integer, default=None, nullable=True) name: str = Column(String(64), nullable=False, unique=True)
# f_string: str = Column(String(128), default=None, nullable=True) f_datetime: datetime = Column(DateTime, default=None, nullable=True)
# f_int: int = Column(Integer, default=None, nullable=True)
# @classmethod f_string: str = Column(String(128), default=None, nullable=True)
# def get_int_settings(cls, session: Session, name: str) -> "Settings":
# """Get setting for an integer or return new setting record""" def __repr__(self) -> str:
# value = self.f_datetime or self.f_int or self.f_string
# int_setting: Settings return f"<Settings(id={self.id}, name={self.name}, {value=}>"
#
# try: @classmethod
# int_setting = session.query(cls).filter( def get_int_settings(cls, session: Session, name: str) -> "Settings":
# cls.name == name).one() """Get setting for an integer or return new setting record"""
# except NoResultFound:
# int_setting = Settings() int_setting: Settings
# int_setting.name = name
# int_setting.f_int = None try:
# session.add(int_setting) int_setting = session.execute(
# session.flush() select(cls)
# return int_setting .where(cls.name == name)
# ).scalar_one()
# def update(self, session: Session, data):
# for key, value in data.items(): except NoResultFound:
# assert hasattr(self, key) int_setting = Settings()
# setattr(self, key, value) int_setting.name = name
# session.flush() int_setting.f_int = None
# session.add(int_setting)
#
# class Tracks(Base): return int_setting
# __tablename__ = 'tracks'
# def update(self, session: Session, data):
# id: int = Column(Integer, primary_key=True, autoincrement=True) for key, value in data.items():
# title: str = Column(String(256), index=True) assert hasattr(self, key)
# artist: str = Column(String(256), index=True) setattr(self, key, value)
# duration: int = Column(Integer, index=True) session.flush()
# start_gap: int = Column(Integer, index=False)
# fade_at: int = Column(Integer, index=False)
# silence_at: int = Column(Integer, index=False) class Tracks(Base):
# path: str = Column(String(2048), index=False, nullable=False) __tablename__ = 'tracks'
# mtime: float = Column(Float, index=True)
# lastplayed: datetime = Column(DateTime, index=True, default=None) id: int = Column(Integer, primary_key=True, autoincrement=True)
# playlists: RelationshipProperty = relationship("PlaylistTracks", title: str = Column(String(256), index=True)
# back_populates="tracks", artist: str = Column(String(256), index=True)
# lazy="joined") duration: int = Column(Integer, index=True)
# playdates: RelationshipProperty = relationship("Playdates", start_gap: int = Column(Integer, index=False)
# back_populates="track" fade_at: int = Column(Integer, index=False)
# "", silence_at: int = Column(Integer, index=False)
# lazy="joined") 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__( # def __init__(
# self, # self,

View File

@ -31,17 +31,16 @@ from config import Config
# from datetime import datetime, timedelta # from datetime import datetime, timedelta
# from helpers import get_relative_date, open_in_audacity # from helpers import get_relative_date, open_in_audacity
# from log import log.debug, log.error # from log import log.debug, log.error
# from models import ( from models import (
# Notes, # Notes,
# Playdates, # Playdates,
# Playlists, # Playlists,
# PlaylistTracks, # PlaylistTracks,
# Settings, Settings,
# Tracks, # Tracks,
# NoteColours # NoteColours
# ) )
from dbconfig import Session from dbconfig import Session
#from collections import namedtuple
# start_time_re = re.compile(r"@\d\d:\d\d:\d\d") # 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, def __init__(self, musicmuster: QMainWindow, session: Session,
playlist_id: int, *args, **kwargs): playlist_id: int, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.musicmuster: QMainWindow = musicmuster self.musicmuster: QMainWindow = musicmuster
self.playlist_id: int = playlist_id self.playlist_id: int = playlist_id
# self.menu: Optional[QMenu] = None # self.menu: Optional[QMenu] = None
# self.current_track_start_time: Optional[datetime] = None # self.current_track_start_time: Optional[datetime] = None
# #
@ -111,19 +110,19 @@ class PlaylistTab(QTableWidget):
# self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) # self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.setRowCount(0) self.setRowCount(0)
self.setColumnCount(len(columns)) self.setColumnCount(len(columns))
# Add header row
# Header row
for idx in [a for a in range(len(columns))]: for idx in [a for a in range(len(columns))]:
item: QTableWidgetItem = QTableWidgetItem() item: QTableWidgetItem = QTableWidgetItem()
self.setHorizontalHeaderItem(idx, item) self.setHorizontalHeaderItem(idx, item)
self.horizontalHeader().setMinimumSectionSize(0) self.horizontalHeader().setMinimumSectionSize(0)
# self._set_column_widths(session) self._set_column_widths(session)
# Set column headings sorted by idx # Set column headings sorted by idx
self.setHorizontalHeaderLabels( self.setHorizontalHeaderLabels(
[a[0].heading for a in list(sorted(columns.values(), [a[0].heading for a in list(sorted(columns.values(),
key=lambda item: item[0][0]))] key=lambda item: item[0][0]))]
) )
#
# self.setDragEnabled(True) # self.setDragEnabled(True)
# self.setAcceptDrops(True) # self.setAcceptDrops(True)
# self.viewport().setAcceptDrops(True) # self.viewport().setAcceptDrops(True)
@ -146,15 +145,33 @@ class PlaylistTab(QTableWidget):
# self.row_filter: Optional[str] = None # self.row_filter: Optional[str] = None
# self.editing_cell: bool = False # self.editing_cell: bool = False
# self.selecting_in_progress = False # self.selecting_in_progress = False
# Connect signals
# self.cellChanged.connect(self._cell_changed) # self.cellChanged.connect(self._cell_changed)
# self.cellClicked.connect(self._edit_note_cell) # 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.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 # # Now load our tracks and notes
# self.populate(session, self.playlist_id) # 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: # def __repr__(self) -> str:
# return f"<PlaylistTab(id={self.playlist_id}" # return f"<PlaylistTab(id={self.playlist_id}"
# #
@ -1592,17 +1609,18 @@ class PlaylistTab(QTableWidget):
# #
# # Reset extended selection # # Reset extended selection
# self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) # self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
#
# def _set_column_widths(self, session: Session) -> None: def _set_column_widths(self, session: Session) -> None:
# """Column widths from settings""" """Column widths from settings"""
#
# for column in range(self.columnCount()): for column_name, data in columns.items():
# name: str = f"playlist_col_{str(column)}_width" idx = data[0].idx
# record: Settings = Settings.get_int_settings(session, name) attr_name = f"playlist_{column_name}_col_width"
# if record and record.f_int is not None: record: Settings = Settings.get_int_settings(session, attr_name)
# self.setColumnWidth(column, record.f_int) if record and record.f_int is not None:
# else: self.setColumnWidth(idx, record.f_int)
# self.setColumnWidth(column, Config.DEFAULT_COLUMN_WIDTH) else:
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
# #
# def _set_next(self, row: int, session: Session) -> None: # def _set_next(self, row: int, session: Session) -> None:
# """ # """

View File

@ -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')

View File

@ -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 ###