From 68e524594d551e7f4d77df264b747cb4a04bb8b5 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Sun, 29 Dec 2024 18:34:44 +0000 Subject: [PATCH] Recover from git cockup: reimplement template management --- app/dbtables.py | 1 - app/models.py | 4 +- app/musicmuster.py | 105 +++++++++++++++++- app/ui/main_window_ui.py | 4 +- ..._remove_playlists_delete_and_implement_.py | 52 +++++++++ 5 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 migrations/versions/33c04e3c12c8_remove_playlists_delete_and_implement_.py diff --git a/app/dbtables.py b/app/dbtables.py index c2da638..e89a3b9 100644 --- a/app/dbtables.py +++ b/app/dbtables.py @@ -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", diff --git a/app/models.py b/app/models.py index 3fa5cbf..d7e8949 100644 --- a/app/models.py +++ b/app/models.py @@ -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 diff --git a/app/musicmuster.py b/app/musicmuster.py index 8cc75a1..a9bdc91 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -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. diff --git a/app/ui/main_window_ui.py b/app/ui/main_window_ui.py index ddeace6..a763a63 100644 --- a/app/ui/main_window_ui.py +++ b/app/ui/main_window_ui.py @@ -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 diff --git a/migrations/versions/33c04e3c12c8_remove_playlists_delete_and_implement_.py b/migrations/versions/33c04e3c12c8_remove_playlists_delete_and_implement_.py new file mode 100644 index 0000000..d84c19f --- /dev/null +++ b/migrations/versions/33c04e3c12c8_remove_playlists_delete_and_implement_.py @@ -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 ### +