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_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

View File

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

View File

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

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