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:
parent
72930605db
commit
02c0c9c861
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 ###
|
||||
|
||||
Loading…
Reference in New Issue
Block a user