Implement template management

Allow template edits and deletions. Deletions are now true deletes,
not just flagged in database as deletes, and this applies to all
playlists. Includes schema changes to cascade deletes.
This commit is contained in:
Keith Edmunds 2024-12-29 18:06:31 +00:00
parent 72930605db
commit 02c0c9c861
5 changed files with 160 additions and 6 deletions

View File

@ -73,7 +73,6 @@ class PlaylistsTable(Model):
tab: Mapped[Optional[int]] = mapped_column(default=None)
open: Mapped[bool] = mapped_column(default=False)
is_template: Mapped[bool] = mapped_column(default=False)
deleted: Mapped[bool] = mapped_column(default=False)
rows: Mapped[List["PlaylistRowsTable"]] = relationship(
"PlaylistRowsTable",
back_populates="playlist",

View File

@ -225,10 +225,10 @@ class Playlists(dbtables.PlaylistsTable):
def delete(self, session: Session) -> None:
"""
Mark as deleted
Delete playlist
"""
self.deleted = True
session.execute(delete(Playlists).where(Playlists.id == self.id))
session.commit()
@classmethod

View File

@ -51,6 +51,7 @@ import stackprinter # type: ignore
# App imports
from classes import (
ApplicationError,
MusicMusterSignals,
TrackInfo,
)
@ -96,6 +97,64 @@ class Current:
)
class EditDeleteDialog(QDialog):
def __init__(self, templates: list[tuple[str, int]]) -> None:
super().__init__()
self.templates = templates
self.selection: tuple[str, int] = ("", -1)
self.init_ui()
def init_ui(self) -> None:
# Create label
label = QLabel("Select template:")
# Create combo box
self.combo_box = QComboBox()
for text, id_ in self.templates:
self.combo_box.addItem(text, id_)
# Create buttons
edit_button = QPushButton("Edit")
delete_button = QPushButton("Delete")
cancel_button = QPushButton("Cancel")
# Connect buttons
edit_button.clicked.connect(self.edit_clicked)
delete_button.clicked.connect(self.delete_clicked)
cancel_button.clicked.connect(self.cancel_clicked)
# Layout setup
top_layout = QHBoxLayout()
top_layout.addWidget(label)
top_layout.addWidget(self.combo_box)
bottom_layout = QHBoxLayout()
bottom_layout.addStretch()
bottom_layout.addWidget(edit_button)
bottom_layout.addWidget(delete_button)
bottom_layout.addWidget(cancel_button)
main_layout = QVBoxLayout()
main_layout.addLayout(top_layout)
main_layout.addLayout(bottom_layout)
self.setLayout(main_layout)
self.setWindowTitle("Edit or Delete Template")
def edit_clicked(self) -> None:
self.selection = ("Edit", self.combo_box.currentData())
self.accept()
def delete_clicked(self) -> None:
self.selection = ("Delete", self.combo_box.currentData())
self.accept()
def cancel_clicked(self) -> None:
self.selection = ("Cancelled", -1)
self.reject()
class PreviewManager:
"""
Manage track preview player
@ -803,8 +862,12 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.resize(500, 100)
ok = dlg.exec()
if ok:
if self.current.selected_rows:
new_row_number = self.current.selected_rows[0]
else:
new_row_number = self.current.base_model.rowCount()
self.current.base_model.insert_row(
proposed_row_number=self.current.selected_rows[0],
proposed_row_number=new_row_number,
note=dlg.textValue(),
)
@ -870,6 +933,46 @@ class Window(QMainWindow, Ui_MainWindow):
self.signals.search_wikipedia_signal.emit(track_info.title)
def manage_templates(self) -> None:
"""
Delete / edit templates
"""
# Build a list of (template-name, playlist-id) tuples
template_list: list[tuple[str, int]] = []
with db.Session() as session:
for template in Playlists.get_all_templates(session):
template_list.append((template.name, template.id))
# Get user's selection
dlg = EditDeleteDialog(template_list)
if not dlg.exec():
return # User cancelled
action, template_id = dlg.selection
playlist = session.get(Playlists, template_id)
if not playlist:
log.error(f"Error opening {template_id=}")
if action == "Edit":
# Simply load the template as a playlist. Any changes
# made will persist
idx = self.create_playlist_tab(playlist)
self.tabPlaylist.setCurrentIndex(idx)
elif action == "Delete":
if helpers.ask_yes_no(
"Delete template",
f"Delete template '{playlist.name}': " "Are you sure?",
):
if self.close_playlist_tab():
playlist.delete(session)
session.commit()
else:
raise ApplicationError(f"Unrecognised action from EditDeleteDialog: {action=}")
def mark_rows_for_moving(self) -> None:
"""
Cut rows ready for pasting.

View File

@ -677,5 +677,5 @@ class Ui_MainWindow(object):
self.actionSearch_title_in_Songfacts.setShortcut(_translate("MainWindow", "Ctrl+S"))
self.actionSelect_duplicate_rows.setText(_translate("MainWindow", "Select duplicate rows..."))
self.actionReplace_files.setText(_translate("MainWindow", "Import files..."))
from infotabs import InfoTabs
from pyqtgraph import PlotWidget
from infotabs import InfoTabs # type: ignore
from pyqtgraph import PlotWidget # type: ignore

View File

@ -0,0 +1,52 @@
"""Remove playlists.delete and implement Cascade deletes
Revision ID: 33c04e3c12c8
Revises: 164bd5ef3074
Create Date: 2024-12-29 17:56:00.627198
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '33c04e3c12c8'
down_revision = '164bd5ef3074'
branch_labels = None
depends_on = None
def upgrade(engine_name: str) -> None:
globals()["upgrade_%s" % engine_name]()
def downgrade(engine_name: str) -> None:
globals()["downgrade_%s" % engine_name]()
def upgrade_() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('playlist_rows', schema=None) as batch_op:
batch_op.drop_constraint('playlist_rows_ibfk_3', type_='foreignkey')
batch_op.create_foreign_key('playlist_rows_ibfk_3', 'playlists', ['playlist_id'], ['id'], ondelete='CASCADE')
with op.batch_alter_table('playlists', schema=None) as batch_op:
batch_op.drop_column('deleted')
# ### end Alembic commands ###
def downgrade_() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('playlists', schema=None) as batch_op:
batch_op.add_column(sa.Column('deleted', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False))
with op.batch_alter_table('playlist_rows', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'playlists', ['playlist_id'], ['id'])
# ### end Alembic commands ###