From 040020e7edd0ced5361e4ace002a9eb29811360a Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Sun, 23 Feb 2025 17:26:43 +0000 Subject: [PATCH] Refactor playlist management functions --- app/models.py | 28 +--- app/musicmuster.py | 348 +++++++++++++++++++++++++-------------------- 2 files changed, 203 insertions(+), 173 deletions(-) diff --git a/app/models.py b/app/models.py index e620b0b..e6a96ac 100644 --- a/app/models.py +++ b/app/models.py @@ -179,12 +179,18 @@ class Playdates(dbtables.PlaydatesTable): class Playlists(dbtables.PlaylistsTable): - def __init__(self, session: Session, name: str): + def __init__(self, session: Session, name: str, template_id: int) -> None: + """Create playlist with passed name""" + self.name = name self.last_used = dt.datetime.now() session.add(self) session.commit() + # If a template is specified, copy from it + if template_id: + PlaylistRows.copy_playlist(session, template_id, self.id) + @staticmethod def clear_tabs(session: Session, playlist_ids: list[int]) -> None: """ @@ -201,26 +207,6 @@ class Playlists(dbtables.PlaylistsTable): self.open = False session.commit() - @classmethod - def create_playlist_from_template( - cls, session: Session, template_id: int, playlist_name: str - ) -> Optional["Playlists"]: - """Create a new playlist from template""" - - # Sanity check - if not template_id: - return None - - playlist = cls(session, playlist_name) - - # Sanity / mypy checks - if not playlist or not playlist.id: - return None - - PlaylistRows.copy_playlist(session, template_id, playlist.id) - - return playlist - def delete(self, session: Session) -> None: """ Delete playlist diff --git a/app/musicmuster.py b/app/musicmuster.py index 7dc9514..95f094d 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -694,15 +694,19 @@ class Window(QMainWindow): and template_id. """ - submenu_items: list[dict[str, str | tuple[Session, int]]] = [] - with db.Session() as session: - templates = Playlists.get_all_templates(session) + submenu_items: list[dict[str, str | tuple[Session, int]]] = [ + {"text": "Show all", + "handler": "create_playlist_from_template", + "args": (session, 0) + } + ] + templates = Playlists.get_favourite_templates(session) for template in templates: submenu_items.append( { "text": template.name, - "handler": "create_and_show_playlist", + "handler": "create_playlist_from_template", "args": ( session, template.id, @@ -719,6 +723,178 @@ class Window(QMainWindow): {"text": "Action Y", "handler": "action_y_handler"}, ] + # # # # # # # # # # Playlist management functions # # # # # # # # # # + + def _create_playlist( + self, session: Session, name: str, template_id: int + ) -> Playlists: + """ + Create a playlist in the database, populate it from the template + if template_id > 0, and return the Playlists object. + """ + + log.debug(f" _create_playlist({name=}, {template_id=})") + + return Playlists(session, name, template_id) + + def _open_playlist(self, playlist: Playlists, is_template: bool = False) -> int: + """ + With passed playlist: + - create models + - create tab + - switch to tab + - mark playist as open + return: tab index + """ + + log.debug(f" _open_playlist({playlist=}, {is_template=})") + + # Create base model and proxy model + base_model = PlaylistModel(playlist.id, is_template) + proxy_model = PlaylistProxyModel() + proxy_model.setSourceModel(base_model) + + # Create tab + playlist_tab = PlaylistTab(musicmuster=self, model=proxy_model) + idx = self.playlist_section.tabPlaylist.addTab(playlist_tab, playlist.name) + + # Mark playlist as open + playlist.mark_open() + + # Switch to new tab + self.playlist_section.tabPlaylist.setCurrentIndex(idx) + self.update_playlist_icons() + + return idx + + def create_playlist_from_template(self, session: Session, template_id: int) -> None: + """ + Prompt for new playlist name and create from passed template_id + """ + + if template_id == 0: + # Show all templates + selected_template_id = self.solicit_template_to_use(session) + if selected_template_id is None: + return + else: + template_id = selected_template_id + + playlist_name = self.solicit_playlist_name(session) + if not playlist_name: + return + + playlist = self._create_playlist(session, playlist_name, template_id) + self._open_playlist(playlist) + session.commit() + + def delete_playlist(self) -> None: + """ + Delete current playlist + """ + + with db.Session() as session: + playlist_id = self.current.playlist_id + playlist = session.get(Playlists, playlist_id) + if playlist: + if helpers.ask_yes_no( + "Delete playlist", + f"Delete playlist '{playlist.name}': " "Are you sure?", + ): + if self.close_playlist_tab(): + playlist.delete(session) + session.commit() + else: + log.error("Failed to retrieve playlist") + + def open_existing_playlist(self) -> None: + """Open existing playlist""" + + with db.Session() as session: + playlists = Playlists.get_closed(session) + dlg = SelectPlaylistDialog(self, playlists=playlists, session=session) + dlg.exec() + playlist = dlg.playlist + if playlist: + self._open_playlist(playlist) + session.commit() + + def save_as_template(self) -> None: + """Save current playlist as template""" + + with db.Session() as session: + template_names = [a.name for a in Playlists.get_all_templates(session)] + + while True: + # Get name for new template + dlg = QInputDialog(self) + dlg.setInputMode(QInputDialog.InputMode.TextInput) + dlg.setLabelText("Template name:") + dlg.resize(500, 100) + ok = dlg.exec() + if not ok: + return + + template_name = dlg.textValue() + if template_name not in template_names: + break + helpers.show_warning( + self, "Duplicate template", "Template name already in use" + ) + Playlists.save_as_template(session, self.current.playlist_id, template_name) + session.commit() + helpers.show_OK("Template", "Template saved", self) + + def solicit_playlist_name( + self, session: Session, default: str = "", prompt: str = "Playlist name:" + ) -> Optional[str]: + """Get name of new playlist from user""" + + dlg = QInputDialog(self) + dlg.setInputMode(QInputDialog.InputMode.TextInput) + dlg.setLabelText(prompt) + while True: + if default: + dlg.setTextValue(default) + dlg.resize(500, 100) + ok = dlg.exec() + if ok: + proposed_name = dlg.textValue() + if Playlists.name_is_available(session, proposed_name): + return proposed_name + else: + helpers.show_warning( + self, + "Name in use", + f"There's already a playlist called '{proposed_name}'", + ) + continue + else: + return None + + def solicit_template_to_use( + self, session: Session, template_prompt: Optional[str] = None + ) -> Optional[int]: + """ + Have user select a template. Return the template.id, or None if they cancel. + template_id of zero means don't use a template. + """ + + template_name_id_list: list[tuple[str, int]] = [] + template_name_id_list.append((Config.NO_TEMPLATE_NAME, 0)) + + with db.Session() as session: + for template in Playlists.get_all_templates(session): + template_name_id_list.append((template.name, template.id)) + + dlg = TemplateSelectorDialog(template_name_id_list, template_prompt) + if not dlg.exec() or dlg.selected_id is None: + return None # User cancelled + + return dlg.selected_id + + # # # # # # # # # # Miscellaneous functions # # # # # # # # # # + def select_duplicate_rows(self) -> None: """Call playlist to select duplicate rows""" @@ -898,55 +1074,6 @@ class Window(QMainWindow): self.signals.search_songfacts_signal.connect(self.open_songfacts_browser) self.signals.search_wikipedia_signal.connect(self.open_wikipedia_browser) - def create_and_show_playlist( - self, session: Session, template_id: int - ) -> Optional[Playlists]: - """Create new playlist""" - - # Get a name for this new playlist - playlist_name = self.solicit_playlist_name(session) - if not playlist_name: - return None - - # If template.id == 0, user doesn't want a template - playlist: Optional[Playlists] - if template_id == 0: - playlist = Playlists(session, playlist_name) - else: - playlist = Playlists.create_playlist_from_template( - session, template_id, playlist_name - ) - - if playlist: - self.open_and_show_playlist(session, playlist) - else: - log.error(f"Failed to create playlist, {playlist_name=}") - - return None - - def create_playlist_tab( - self, playlist: Playlists, is_template: bool = False - ) -> int: - """ - Take the passed playlist, create a playlist tab and - add tab to display. Return index number of tab. - """ - - log.debug(f"create_playlist_tab({playlist=})") - - # Create model and proxy model - base_model = PlaylistModel(playlist.id, is_template) - proxy_model = PlaylistProxyModel() - proxy_model.setSourceModel(base_model) - - # Create tab - playlist_tab = PlaylistTab(musicmuster=self, model=proxy_model) - idx = self.playlist_section.tabPlaylist.addTab(playlist_tab, playlist.name) - - log.debug(f"create_playlist_tab() returned: {idx=}") - - return idx - def current_row_or_end(self) -> int: """ If a row or rows are selected, return the row number of the first @@ -965,25 +1092,6 @@ class Window(QMainWindow): ipdb.set_trace() - def delete_playlist(self) -> None: - """ - Delete current playlist - """ - - with db.Session() as session: - playlist_id = self.current.playlist_id - playlist = session.get(Playlists, playlist_id) - if playlist: - if helpers.ask_yes_no( - "Delete playlist", - f"Delete playlist '{playlist.name}': " "Are you sure?", - ): - if self.close_playlist_tab(): - playlist.delete(session) - session.commit() - else: - log.error("Failed to retrieve playlist") - def download_played_tracks(self) -> None: """Download a CSV of played tracks""" @@ -1191,7 +1299,7 @@ class Window(QMainWindow): if playlist: log.debug(f"load_last_playlists() loaded {playlist=}") # Create tab - playlist_ids.append(self.create_playlist_tab(playlist)) + playlist_ids.append(self._open_playlist(playlist)) # Set active tab record = Settings.get_setting(session, "active_tab") @@ -1270,8 +1378,7 @@ class Window(QMainWindow): ) # Simply load the template as a playlist. Any changes # made will persist - idx = self.create_playlist_tab(template, is_template=True) - self.playlist_section.tabPlaylist.setCurrentIndex(idx) + self._open_playlist(template, is_template=True) def favourite(template_id: int, favourite: bool) -> None: """Mark template as (not) favourite""" @@ -1285,14 +1392,24 @@ class Window(QMainWindow): # Get base template template_id = self.solicit_template_to_use( - session, template_prmompt="New template based upon:" + session, template_prompt="New template based upon:" ) if template_id is None: return - new_template = self.create_and_show_playlist(session, template_id) - if new_template: - self.open_and_show_playlist(session, new_template, is_template=True) + # Get new template name + name = self.solicit_playlist_name( + session, default="", prompt="New template name:" + ) + if not name: + return + + # Create playlist for template and mark is as a template + template = self._create_playlist(session, name, template_id) + template.is_template = True + + # Open it for editing + self._open_playlist(template, is_template=True) def rename(template_id: int) -> Optional[str]: """rename template""" @@ -1419,53 +1536,6 @@ class Window(QMainWindow): self.move_playlist_rows(unplayed_rows) self.disable_selection_timing = False - def new_playlist(self) -> Optional[Playlists]: - """ - Create new playlist, optionally from template - """ - - with db.Session() as session: - template_id = self.solicit_template_to_use(session) - if not template_id: - return None # User cancelled - - playlist = self.create_and_show_playlist(session, template_id) - - if playlist: - playlist.mark_open() - # Need to ensure that the new playlist is committed to - # the database before it is opened by the model. - session.commit() - idx = self.create_playlist_tab(playlist) - self.playlist_section.tabPlaylist.setCurrentIndex(idx) - return playlist - else: - ApplicationError("new_playlist: Playlist failed to create") - - return None - - def open_existing_playlist(self) -> None: - """Open existing playlist""" - - with db.Session() as session: - playlists = Playlists.get_closed(session) - dlg = SelectPlaylistDialog(self, playlists=playlists, session=session) - dlg.exec() - playlist = dlg.playlist - if playlist: - self.open_and_show_playlist(session, playlist) - - def open_and_show_playlist( - self, session: Session, playlist: Playlists, is_template: bool = False - ) -> None: - """Open passed playlist""" - - idx = self.create_playlist_tab(playlist, is_template) - playlist.mark_open() - session.commit() - - self.playlist_section.tabPlaylist.setCurrentIndex(idx) - def open_songfacts_browser(self, title: str) -> None: """Search Songfacts for title""" @@ -1789,32 +1859,6 @@ class Window(QMainWindow): ) track_sequence.current.start_time -= dt.timedelta(milliseconds=elapsed_ms) - def save_as_template(self) -> None: - """Save current playlist as template""" - - with db.Session() as session: - template_names = [a.name for a in Playlists.get_all_templates(session)] - - while True: - # Get name for new template - dlg = QInputDialog(self) - dlg.setInputMode(QInputDialog.InputMode.TextInput) - dlg.setLabelText("Template name:") - dlg.resize(500, 100) - ok = dlg.exec() - if not ok: - return - - template_name = dlg.textValue() - if template_name not in template_names: - break - helpers.show_warning( - self, "Duplicate template", "Template name already in use" - ) - Playlists.save_as_template(session, self.current.playlist_id, template_name) - session.commit() - helpers.show_OK("Template", "Template saved", self) - def search_playlist(self) -> None: """Show text box to search playlist"""