From aec994bafd62a5e8b359d30e5cb2d008e81a49a4 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Sun, 13 Apr 2025 09:12:33 +0100 Subject: [PATCH] Rename ds functions; fix add track to header --- app/classes.py | 6 +- app/dialogs.py | 18 +- app/ds.py | 688 +++++++++++++++++----------------- app/file_importer.py | 2 +- app/musicmuster.py | 6 +- app/playlistmodel.py | 22 +- app/playlistrow.py | 10 +- app/playlists.py | 10 +- app/querylistmodel.py | 2 +- app/utilities.py | 4 +- tests/X_test_file_importer.py | 2 +- tests/test_ds.py | 20 +- tests/test_file_importer.py | 2 +- tests/test_playlistmodel.py | 134 ++++--- 14 files changed, 461 insertions(+), 465 deletions(-) diff --git a/app/classes.py b/app/classes.py index d41e12f..a567ff2 100644 --- a/app/classes.py +++ b/app/classes.py @@ -207,7 +207,7 @@ class InsertTrack: @dataclass -class PlayTrack: +class TrackAndPlaylist: playlist_id: int track_id: int @@ -229,7 +229,7 @@ class MusicMusterSignals(QObject): search_songfacts_signal = pyqtSignal(str) search_wikipedia_signal = pyqtSignal(str) show_warning_signal = pyqtSignal(str, str) - signal_add_track_to_header = pyqtSignal(InsertTrack) + signal_add_track_to_header = pyqtSignal(int) signal_begin_insert_rows = pyqtSignal(InsertRows) signal_end_insert_rows = pyqtSignal(int) signal_insert_track = pyqtSignal(InsertTrack) @@ -239,7 +239,7 @@ class MusicMusterSignals(QObject): # specify that here as it requires us to import PlaylistRow from # playlistrow.py, which itself imports MusicMusterSignals signal_set_next_track = pyqtSignal(object) - signal_track_started = pyqtSignal(PlayTrack) + signal_track_started = pyqtSignal(TrackAndPlaylist) span_cells_signal = pyqtSignal(int, int, int, int, int) status_message_signal = pyqtSignal(str, int) track_ended_signal = pyqtSignal() diff --git a/app/dialogs.py b/app/dialogs.py index 845a0ba..030c5af 100644 --- a/app/dialogs.py +++ b/app/dialogs.py @@ -18,7 +18,12 @@ from PyQt6.QtWidgets import ( # Third party imports # App imports -from classes import ApplicationError, InsertTrack, MusicMusterSignals +from classes import ( + ApplicationError, + InsertTrack, + MusicMusterSignals, + TrackAndPlaylist, +) from helpers import ( get_relative_date, ms_to_mmss, @@ -94,8 +99,8 @@ class TrackInsertDialog(QDialog): self.setLayout(layout) self.resize(800, 600) - width = ds.get_setting("dbdialog_width") or 800 - height = ds.get_setting("dbdialog_height") or 800 + width = ds.setting_get("dbdialog_width") or 800 + height = ds.setting_get("dbdialog_height") or 800 self.resize(width, height) self.signals = MusicMusterSignals() @@ -141,7 +146,12 @@ class TrackInsertDialog(QDialog): self.title_edit.setFocus() if self.add_to_header: - self.signals.signal_add_track_to_header.emit(insert_track_data) + # The model will have the right-clicked row marked as a + # selected_row so we only need to pass the playlist_id and + # track_id. + self.signals.signal_add_track_to_header.emit( + TrackAndPlaylist(playlist_id=self.playlist_id, track_id=track_id) + ) self.accept() else: self.signals.signal_insert_track.emit(insert_track_data) diff --git a/app/ds.py b/app/ds.py index 78e135a..2cf41b6 100644 --- a/app/ds.py +++ b/app/ds.py @@ -78,7 +78,7 @@ def _remove_substring_case_insensitive(parent_string: str, substring: str) -> st # Notecolour functions -def _all_notecolours(session: Session) -> list[NoteColoursDTO]: +def _notecolours_all(session: Session) -> list[NoteColoursDTO]: """ Return all notecolour records """ @@ -118,13 +118,13 @@ def _all_notecolours(session: Session) -> list[NoteColoursDTO]: return results -def _get_colour_record(text: str) -> tuple[NoteColoursDTO | None, str]: +def _notecolors_get_notecolours_dto(text: str) -> tuple[NoteColoursDTO | None, str]: """ Parse text and return first matching colour record or None """ with db.Session() as session: - for rec in _all_notecolours(session): + for rec in _notecolours_all(session): if rec.is_regex: flags = re.UNICODE if not rec.is_casesensitive: @@ -151,13 +151,13 @@ def _get_colour_record(text: str) -> tuple[NoteColoursDTO | None, str]: return (None, text) -def get_colour(text: str, foreground: bool = False) -> str: +def notecolours_get_colour(text: str, foreground: bool = False) -> str: """ Parse text and return background (foreground if foreground==True) colour string if matched, else None """ - (rec, _) = _get_colour_record(text) + (rec, _) = _notecolors_get_notecolours_dto(text) if rec is None: return "" elif foreground: @@ -167,12 +167,12 @@ def get_colour(text: str, foreground: bool = False) -> str: # @log_call -def remove_colour_substring(text: str) -> str: +def notecolours_remove_colour_substring(text: str) -> str: """ Remove text that identifies the colour to be used if strip_substring is True """ - (rec, stripped_text) = _get_colour_record(text) + (rec, stripped_text) = _notecolors_get_notecolours_dto(text) return stripped_text @@ -239,17 +239,33 @@ def _tracks_where(query: BinaryExpression | ColumnElement[bool],) -> list[TrackD return results -def track_by_path(path: str) -> TrackDTO | None: +# @log_call +def track_add_to_header(playlistrow_id: int, track_id: int) -> None: """ - Return track with passed path or None + Add a track to this (header) row """ - track_list = _tracks_where(Tracks.path.ilike(path)) - if not track_list: - return None - if len(track_list) > 1: - raise ApplicationError(f"Duplicate {path=}") - return track_list[0] + with db.Session() as session: + session.execute( + update(PlaylistRows) + .where(PlaylistRows.id == playlistrow_id) + .values(track_id=track_id) + ) + session.commit() + + +def tracks_all() -> list[TrackDTO]: + """Return a list of all tracks""" + + return _tracks_where(Tracks.id > 0) + + +def tracks_by_artist(filter_str: str) -> list[TrackDTO]: + """ + Return tracks where artist is like filter + """ + + return _tracks_where(Tracks.artist.ilike(f"%{filter_str}%")) def track_by_id(track_id: int) -> TrackDTO | None: @@ -265,12 +281,17 @@ def track_by_id(track_id: int) -> TrackDTO | None: return track_list[0] -def tracks_by_artist(filter_str: str) -> list[TrackDTO]: +def track_by_path(path: str) -> TrackDTO | None: """ - Return tracks where artist is like filter + Return track with passed path or None """ - return _tracks_where(Tracks.artist.ilike(f"%{filter_str}%")) + track_list = _tracks_where(Tracks.path.ilike(path)) + if not track_list: + return None + if len(track_list) > 1: + raise ApplicationError(f"Duplicate {path=}") + return track_list[0] def tracks_by_title(filter_str: str) -> list[TrackDTO]: @@ -281,13 +302,48 @@ def tracks_by_title(filter_str: str) -> list[TrackDTO]: return _tracks_where(Tracks.title.ilike(f"%{filter_str}%")) -def get_all_tracks() -> list[TrackDTO]: - """Return a list of all tracks""" +# @log_call +def track_create(metadata: dict[str, str | int | float]) -> TrackDTO: + """ + Create a track db entry from a track path and return the DTO + """ - return _tracks_where(Tracks.id > 0) + with db.Session() as session: + try: + track = Tracks( + session=session, + path=str(metadata["path"]), + title=str(metadata["title"]), + artist=str(metadata["artist"]), + duration=int(metadata["duration"]), + start_gap=int(metadata["start_gap"]), + fade_at=int(metadata["fade_at"]), + silence_at=int(metadata["silence_at"]), + bitrate=int(metadata["bitrate"]), + ) + + track_id = track.id + session.commit() + except Exception: + raise ApplicationError("Can't create Track") + + new_track = track_by_id(track_id) + if not new_track: + raise ApplicationError("Unable to create new track") + + return new_track -def get_filtered_tracks(filter: Filter) -> list[TrackDTO]: +def track_delete(track_id: int) -> None: + """Delete track""" + + with db.Session() as session: + track = session.get(Tracks, track_id) + session.delete(track) + session.commit() + + +def tracks_filtered(filter: Filter) -> list[TrackDTO]: """ Return tracks matching filter """ @@ -378,55 +434,23 @@ def get_filtered_tracks(filter: Filter) -> list[TrackDTO]: return results -# @log_call -def add_track_to_header(playlistrow_id: int, track_id: int) -> None: +def track_set_intro(track_id: int, intro: int) -> None: """ - Add a track to this (header) row + Set track intro time """ with db.Session() as session: session.execute( - update(PlaylistRows) - .where(PlaylistRows.id == playlistrow_id) - .values(track_id=track_id) + update(Tracks) + .where(Tracks.id == track_id) + .values(intro=intro) ) - session.commit() + + session.commit() # @log_call -def create_track(path: str, metadata: dict[str, str | int | float]) -> TrackDTO: - """ - Create a track db entry from a track path and return the DTO - """ - - with db.Session() as session: - try: - track = Tracks( - session=session, - path=str(metadata["path"]), - title=str(metadata["title"]), - artist=str(metadata["artist"]), - duration=int(metadata["duration"]), - start_gap=int(metadata["start_gap"]), - fade_at=int(metadata["fade_at"]), - silence_at=int(metadata["silence_at"]), - bitrate=int(metadata["bitrate"]), - ) - - track_id = track.id - session.commit() - except Exception: - raise ApplicationError("Can't create Track") - - new_track = track_by_id(track_id) - if not new_track: - raise ApplicationError("Unable to create new track") - - return new_track - - -# @log_call -def update_track( +def track_update( path: str, track_id: int, metadata: dict[str, str | int | float] ) -> TrackDTO: """ @@ -455,31 +479,58 @@ def update_track( return updated_track -def set_track_intro(track_id: int, intro: int) -> None: - """ - Set track intro time - """ - - with db.Session() as session: - session.execute( - update(Tracks) - .where(Tracks.id == track_id) - .values(intro=intro) - ) - - session.commit() - - -def delete_track(track_id: int) -> None: - """Delete track""" - - with db.Session() as session: - track = session.get(Tracks, track_id) - session.delete(track) - session.commit() - - # Playlist functions +def _playlist_check_playlist( + session: Session, playlist_id: int, fix: bool = False +) -> None: + """ + Ensure the row numbers are contiguous. Fix and log if fix==True, + else raise ApplicationError. + """ + + playlist_rows = ( + session.execute( + select(PlaylistRows) + .where(PlaylistRows.playlist_id == playlist_id) + .order_by(PlaylistRows.row_number) + ) + .scalars() + .all() + ) + for idx, plr in enumerate(playlist_rows): + if plr.row_number == idx: + continue + + msg = ( + "_check_playlist_integrity: incorrect row number " + f"({plr.id=}, {plr.row_number=}, {idx=})" + ) + if fix: + log.debug(msg) + plr.row_number = idx + else: + raise ApplicationError(msg) + + +# @log_call +def _playlist_shift_rows( + session: Session, playlist_id: int, starting_row: int, shift_by: int +) -> None: + """ + Shift rows from starting_row by shift_by. If shift_by is +ve, shift rows + down; if -ve, shift them up. + """ + + session.execute( + update(PlaylistRows) + .where( + (PlaylistRows.playlist_id == playlist_id), + (PlaylistRows.row_number >= starting_row), + ) + .values(row_number=PlaylistRows.row_number + shift_by) + ) + + # @log_call def _playlists_where( query: BinaryExpression | ColumnElement[bool], @@ -513,6 +564,12 @@ def _playlists_where( return results +def playlists_all(): + """Return all playlists""" + + return _playlists_where(Playlists.id > 0) + + # @log_call def playlist_by_id(playlist_id: int) -> PlaylistDTO | None: """ @@ -527,6 +584,26 @@ def playlist_by_id(playlist_id: int) -> PlaylistDTO | None: return playlist_list[0] +def playlist_copy(src_id: int, dst_id: int) -> None: + """Copy playlist entries""" + + with db.Session() as session: + src_rows = session.scalars( + select(PlaylistRows).where(PlaylistRows.playlist_id == src_id) + ).all() + + for plr in src_rows: + PlaylistRows( + session=session, + playlist_id=dst_id, + row_number=plr.row_number, + note=plr.note, + track_id=plr.track_id, + ) + + session.commit() + + def playlists_closed() -> list[PlaylistDTO]: """ Return a list of closed playlists @@ -535,44 +612,32 @@ def playlists_closed() -> list[PlaylistDTO]: return _playlists_where(Playlists.open.is_(False)) -def playlists_open() -> list[PlaylistDTO]: +# @log_call +def playlist_create(name: str, template_id: int, as_template: bool = False) -> PlaylistDTO: """ - Return a list of open playlists + Create playlist and return DTO. """ - return _playlists_where(Playlists.open.is_(True)) + with db.Session() as session: + try: + playlist = Playlists(session, name, template_id) + playlist.is_template = as_template + playlist_id = playlist.id + session.commit() + except Exception: + raise ApplicationError("Can't create Playlist") + + if template_id != 0: + playlist_copy(template_id, playlist_id) + + new_playlist = playlist_by_id(playlist_id) + if not new_playlist: + raise ApplicationError("Can't retrieve new Playlist") + + return new_playlist -def playlists_template_by_id(playlist_id: int) -> PlaylistDTO | None: - """ - Return a list of closed playlists - """ - - playlist_list = _playlists_where( - Playlists.playlist_id == playlist_id, Playlists.is_template.is_(True) - ) - if not playlist_list: - return None - if len(playlist_list) > 1: - raise ApplicationError(f"Duplicate {playlist_id=}") - return playlist_list[0] - - -def playlists_templates() -> list[PlaylistDTO]: - """ - Return a list of playlist templates - """ - - return _playlists_where(Playlists.is_template.is_(True)) - - -def get_all_playlists(): - """Return all playlists""" - - return _playlists_where(Playlists.id > 0) - - -def delete_playlist(playlist_id: int) -> None: +def playlist_delete(playlist_id: int) -> None: """Delete playlist""" with db.Session() as session: @@ -581,61 +646,44 @@ def delete_playlist(playlist_id: int) -> None: session.commit() -def save_as_template(playlist_id: int, template_name: str) -> None: +# @log_call +def playlist_insert_row( + playlist_id: int, row_number: int, track_id: int | None, note: str +) -> PlaylistRowDTO: """ - Save playlist as templated - """ - - new_template = create_playlist(template_name, 0, as_template=True) - - copy_playlist(playlist_id, new_template.id) - - -def playlist_rename(playlist_id: int, new_name: str) -> None: - """ - Rename playlist + Insert a new row into playlist and return new row DTO """ with db.Session() as session: - session.execute( - update(Playlists) - .where(Playlists.id == playlist_id) - .values(name=new_name) + # Sanity check + _playlist_check_playlist(session, playlist_id, fix=False) + + # Make space for new row + _playlist_shift_rows( + session=session, + playlist_id=playlist_id, + starting_row=row_number, + shift_by=1, ) - session.commit() - - -def _check_playlist_integrity( - session: Session, playlist_id: int, fix: bool = False -) -> None: - """ - Ensure the row numbers are contiguous. Fix and log if fix==True, - else raise ApplicationError. - """ - - playlist_rows = ( - session.execute( - select(PlaylistRows) - .where(PlaylistRows.playlist_id == playlist_id) - .order_by(PlaylistRows.row_number) + playlist_row = PlaylistRows( + session=session, + playlist_id=playlist_id, + row_number=row_number, + note=note, + track_id=track_id, ) - .scalars() - .all() - ) - for idx, plr in enumerate(playlist_rows): - if plr.row_number == idx: - continue + session.commit() + playlist_row_id = playlist_row.id - msg = ( - "_check_playlist_integrity: incorrect row number " - f"({plr.id=}, {plr.row_number=}, {idx=})" - ) - if fix: - log.debug(msg) - plr.row_number = idx - else: - raise ApplicationError(msg) + # Sanity check + _playlist_check_playlist(session, playlist_id, fix=False) + + new_playlist_row = playlistrow_by_id(playlistrow_id=playlist_row_id) + if not new_playlist_row: + raise ApplicationError("Can't retrieve new playlist row") + + return new_playlist_row # @log_call @@ -653,26 +701,7 @@ def playlist_mark_status(playlist_id: int, open: bool) -> None: # @log_call -def _shift_rows( - session: Session, playlist_id: int, starting_row: int, shift_by: int -) -> None: - """ - Shift rows from starting_row by shift_by. If shift_by is +ve, shift rows - down; if -ve, shift them up. - """ - - session.execute( - update(PlaylistRows) - .where( - (PlaylistRows.playlist_id == playlist_id), - (PlaylistRows.row_number >= starting_row), - ) - .values(row_number=PlaylistRows.row_number + shift_by) - ) - - -# @log_call -def move_rows( +def playlist_move_rows( from_rows: list[int], from_playlist_id: int, to_row: int, @@ -698,18 +727,18 @@ def move_rows( with db.Session() as session: # Sanity check row numbers - _check_playlist_integrity(session, from_playlist_id, fix=False) + _playlist_check_playlist(session, from_playlist_id, fix=False) if from_playlist_id != to_playlist_id: - _check_playlist_integrity(session, to_playlist_id, fix=False) + _playlist_check_playlist(session, to_playlist_id, fix=False) # Check there are no playlist rows with playlist_id == PENDING_MOVE - pending_move_rows = get_playlist_rows(Config.PLAYLIST_PENDING_MOVE) + pending_move_rows = playlistrows_by_playlist(Config.PLAYLIST_PENDING_MOVE) if pending_move_rows: raise ApplicationError(f"move_rows_to_playlist: {pending_move_rows=}") # We need playlist length if we're moving within a playlist. Get # that now before we remove rows. - from_playlist_length = len(get_playlist_rows(from_playlist_id)) + from_playlist_length = len(playlistrows_by_playlist(from_playlist_id)) # Put rows to be moved into PENDING_MOVE playlist session.execute( update(PlaylistRows) @@ -721,7 +750,7 @@ def move_rows( ) # Resequence remaining row numbers - _check_playlist_integrity(session, from_playlist_id, fix=True) + _playlist_check_playlist(session, from_playlist_id, fix=True) session.commit() # Make space for moved rows. If moving within one playlist, @@ -738,13 +767,13 @@ def move_rows( to_row - overflow - len([a for a in from_rows if a > to_row]) ) - _shift_rows(session, to_playlist_id, space_row, len(from_rows)) + _playlist_shift_rows(session, to_playlist_id, space_row, len(from_rows)) # Move the PENDING_MOVE rows back and fixup row numbers update_list: list[dict[str, int]] = [] next_row = space_row # PLAYLIST_PENDING_MOVE may have gaps so don't check it - for row_to_move in get_playlist_rows( + for row_to_move in playlistrows_by_playlist( Config.PLAYLIST_PENDING_MOVE, check_playlist_itegrity=False ): update_list.append( @@ -758,71 +787,32 @@ def move_rows( session.commit() # Sanity check row numbers - _check_playlist_integrity(session, from_playlist_id, fix=False) + _playlist_check_playlist(session, from_playlist_id, fix=False) if from_playlist_id != to_playlist_id: - _check_playlist_integrity(session, to_playlist_id, fix=False) + _playlist_check_playlist(session, to_playlist_id, fix=False) -# @log_call -def update_row_numbers( - playlist_id: int, id_to_row_number: list[dict[int, int]] -) -> None: +def playlists_open() -> list[PlaylistDTO]: """ - Update playlistrows rownumbers for passed playlistrow_ids - playlist_id is only needed for sanity checking + Return a list of open playlists + """ + + return _playlists_where(Playlists.open.is_(True)) + + +def playlist_rename(playlist_id: int, new_name: str) -> None: + """ + Rename playlist """ with db.Session() as session: - session.execute(update(PlaylistRows), id_to_row_number) - session.commit() + session.execute( + update(Playlists) + .where(Playlists.id == playlist_id) + .values(name=new_name) + ) - # Sanity check - _check_playlist_integrity(session, playlist_id, fix=False) - - -# @log_call -def create_playlist(name: str, template_id: int, as_template: bool = False) -> PlaylistDTO: - """ - Create playlist and return DTO. - """ - - with db.Session() as session: - try: - playlist = Playlists(session, name, template_id) - playlist.is_template = as_template - playlist_id = playlist.id - session.commit() - except Exception: - raise ApplicationError("Can't create Playlist") - - if template_id != 0: - copy_playlist(template_id, playlist_id) - - new_playlist = playlist_by_id(playlist_id) - if not new_playlist: - raise ApplicationError("Can't retrieve new Playlist") - - return new_playlist - - -def copy_playlist(src_id: int, dst_id: int) -> None: - """Copy playlist entries""" - - with db.Session() as session: - src_rows = session.scalars( - select(PlaylistRows).where(PlaylistRows.playlist_id == src_id) - ).all() - - for plr in src_rows: - PlaylistRows( - session=session, - playlist_id=dst_id, - row_number=plr.row_number, - note=plr.note, - track_id=plr.track_id, - ) - - session.commit() + session.commit() def playlist_row_count(playlist_id: int) -> int: @@ -840,48 +830,58 @@ def playlist_row_count(playlist_id: int) -> int: return count -# @log_call -def insert_row( - playlist_id: int, row_number: int, track_id: int | None, note: str -) -> PlaylistRowDTO: +def playlist_save_as_template(playlist_id: int, template_name: str) -> None: """ - Insert a new row into playlist and return new row DTO + Save playlist as templated + """ + + new_template = playlist_create(template_name, 0, as_template=True) + + playlist_copy(playlist_id, new_template.id) + + +def playlists_templates_all() -> list[PlaylistDTO]: + """ + Return a list of playlist templates + """ + + return _playlists_where(Playlists.is_template.is_(True)) + + +def playlists_template_by_id(playlist_id: int) -> PlaylistDTO | None: + """ + Return a list of closed playlists + """ + + playlist_list = _playlists_where( + Playlists.playlist_id == playlist_id, Playlists.is_template.is_(True) + ) + if not playlist_list: + return None + if len(playlist_list) > 1: + raise ApplicationError(f"Duplicate {playlist_id=}") + return playlist_list[0] + + +# @log_call +def playlist_update_row_numbers( + playlist_id: int, id_to_row_number: list[dict[int, int]] +) -> None: + """ + Update playlistrows rownumbers for passed playlistrow_ids + playlist_id is only needed for sanity checking """ with db.Session() as session: - # Sanity check - _check_playlist_integrity(session, playlist_id, fix=False) - - # Make space for new row - _shift_rows( - session=session, - playlist_id=playlist_id, - starting_row=row_number, - shift_by=1, - ) - - playlist_row = PlaylistRows( - session=session, - playlist_id=playlist_id, - row_number=row_number, - note=note, - track_id=track_id, - ) + session.execute(update(PlaylistRows), id_to_row_number) session.commit() - playlist_row_id = playlist_row.id # Sanity check - _check_playlist_integrity(session, playlist_id, fix=False) - - new_playlist_row = get_playlist_row(playlistrow_id=playlist_row_id) - if not new_playlist_row: - raise ApplicationError("Can't retrieve new playlist row") - - return new_playlist_row + _playlist_check_playlist(session, playlist_id, fix=False) # @log_call -def remove_comments(playlist_id: int, row_numbers: list[int]) -> None: +def playlist_remove_comments(playlist_id: int, row_numbers: list[int]) -> None: """ Remove comments from rows in playlist """ @@ -899,7 +899,7 @@ def remove_comments(playlist_id: int, row_numbers: list[int]) -> None: # @log_call -def remove_rows(playlist_id: int, row_numbers: list[int]) -> None: +def playlist_remove_rows(playlist_id: int, row_numbers: list[int]) -> None: """ Remove rows from playlist @@ -915,24 +915,11 @@ def remove_rows(playlist_id: int, row_numbers: list[int]) -> None: ) ) # Fixup row number to remove gaps - _check_playlist_integrity(session, playlist_id, fix=True) + _playlist_check_playlist(session, playlist_id, fix=True) session.commit() -# @log_call -def update_template_favourite(template_id: int, favourite: bool) -> None: - """Update template favourite""" - - with db.Session() as session: - session.execute( - update(Playlists) - .where(Playlists.id == template_id) - .values(favourite=favourite) - ) - session.commit() - - # @log_call def playlist_save_tabs(playlist_id_to_tab: dict[int, int]) -> None: """ @@ -955,10 +942,23 @@ def playlist_save_tabs(playlist_id_to_tab: dict[int, int]) -> None: session.commit() +# @log_call +def playlist_update_template_favourite(template_id: int, favourite: bool) -> None: + """Update template favourite""" + + with db.Session() as session: + session.execute( + update(Playlists) + .where(Playlists.id == template_id) + .values(favourite=favourite) + ) + session.commit() + + # Playlist Rows # @log_call -def get_playlist_row(playlistrow_id: int) -> PlaylistRowDTO | None: +def playlistrow_by_id(playlistrow_id: int) -> PlaylistRowDTO | None: """ Return specific row DTO """ @@ -988,7 +988,7 @@ def get_playlist_row(playlistrow_id: int) -> PlaylistRowDTO | None: return dto -def get_playlist_rows( +def playlistrows_by_playlist( playlist_id: int, check_playlist_itegrity: bool = True ) -> list[PlaylistRowDTO]: @@ -996,7 +996,7 @@ def get_playlist_rows( # TODO: would be good to be confident at removing this if check_playlist_itegrity: - _check_playlist_integrity( + _playlist_check_playlist( session=session, playlist_id=playlist_id, fix=False ) @@ -1029,7 +1029,7 @@ def get_playlist_rows( # Playdates # @log_call -def get_last_played_dates(track_id: int, limit: int = 5) -> str: +def playdates_get_last(track_id: int, limit: int = 5) -> str: """ Return the most recent 'limit' dates that this track has been played as a text list @@ -1051,7 +1051,7 @@ def get_last_played_dates(track_id: int, limit: int = 5) -> str: ) -def update_playdates(track_id: int) -> None: +def playdates_update(track_id: int) -> None: """ Update playdates for passed track """ @@ -1132,7 +1132,7 @@ def _queries_where( return results -def get_all_queries(favourites_only: bool = False) -> list[QueryDTO]: +def queries_all(favourites_only: bool = False) -> list[QueryDTO]: """Return a list of all queries""" query = Queries.id > 0 @@ -1150,44 +1150,7 @@ def query_by_id(query_id: int) -> QueryDTO | None: return query_list[0] -def update_query_filter(query_id: int, filter: Filter) -> None: - """Update query filter""" - - with db.Session() as session: - session.execute( - update(Queries).where(Queries.id == query_id).values(filter=filter) - ) - session.commit() - - -def delete_query(query_id: int) -> None: - """Delete query""" - - with db.Session() as session: - query = session.get(Queries, query_id) - session.delete(query) - session.commit() - - -def update_query_name(query_id: int, name: str) -> None: - """Update query name""" - - with db.Session() as session: - session.execute(update(Queries).where(Queries.id == query_id).values(name=name)) - session.commit() - - -def update_query_favourite(query_id: int, favourite: bool) -> None: - """Update query favourite""" - - with db.Session() as session: - session.execute( - update(Queries).where(Queries.id == query_id).values(favourite=favourite) - ) - session.commit() - - -def create_query(name: str, filter: Filter) -> QueryDTO: +def query_create(name: str, filter: Filter) -> QueryDTO: """ Create a query and return the DTO """ @@ -1207,8 +1170,45 @@ def create_query(name: str, filter: Filter) -> QueryDTO: return new_query +def query_delete(query_id: int) -> None: + """Delete query""" + + with db.Session() as session: + query = session.get(Queries, query_id) + session.delete(query) + session.commit() + + +def query_update_favourite(query_id: int, favourite: bool) -> None: + """Update query favourite""" + + with db.Session() as session: + session.execute( + update(Queries).where(Queries.id == query_id).values(favourite=favourite) + ) + session.commit() + + +def query_update_filter(query_id: int, filter: Filter) -> None: + """Update query filter""" + + with db.Session() as session: + session.execute( + update(Queries).where(Queries.id == query_id).values(filter=filter) + ) + session.commit() + + +def query_update_name(query_id: int, name: str) -> None: + """Update query name""" + + with db.Session() as session: + session.execute(update(Queries).where(Queries.id == query_id).values(name=name)) + session.commit() + + # Misc -def get_setting(name: str) -> int | None: +def setting_get(name: str) -> int | None: """ Get int setting """ @@ -1225,7 +1225,7 @@ def get_setting(name: str) -> int | None: return record.f_int -def set_setting(name: str, value: int) -> None: +def setting_set(name: str, value: int) -> None: """ Add int setting """ @@ -1244,7 +1244,7 @@ def set_setting(name: str, value: int) -> None: session.commit() -def get_db_name() -> str: +def db_name_get() -> str: """Return database name""" with db.Session() as session: diff --git a/app/file_importer.py b/app/file_importer.py index 55d1eb4..37e09fb 100644 --- a/app/file_importer.py +++ b/app/file_importer.py @@ -640,7 +640,7 @@ class DoTrackImport(QThread): if self.track_id == 0: track_dto = ds.create_track(self.destination_track_path, metadata) else: - track_dto = ds.update_track( + track_dto = ds.track_update( self.destination_track_path, self.track_id, metadata ) diff --git a/app/musicmuster.py b/app/musicmuster.py index 1f2b5d4..5b3fabe 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -67,7 +67,7 @@ from classes import ( Filter, MusicMusterSignals, PlaylistDTO, - PlayTrack, + TrackAndPlaylist, QueryDTO, TrackInfo, ) @@ -1595,7 +1595,7 @@ class Window(QMainWindow): except subprocess.CalledProcessError as exc_info: git_tag = str(exc_info.output) - dbname = ds.get_db_name() + dbname = ds.db_name_get() QMessageBox.information( self, @@ -2142,7 +2142,7 @@ class Window(QMainWindow): # Notify others self.signals.signal_track_started.emit( - PlayTrack(self.track_sequence.current.playlist_id, + TrackAndPlaylist(self.track_sequence.current.playlist_id, self.track_sequence.current.track_id) ) diff --git a/app/playlistmodel.py b/app/playlistmodel.py index b5d1da7..5edf3c7 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -36,7 +36,7 @@ from classes import ( InsertRows, InsertTrack, MusicMusterSignals, - PlayTrack, + TrackAndPlaylist, ) from config import Config from helpers import ( @@ -102,7 +102,7 @@ class PlaylistModel(QAbstractTableModel): self.signals.signal_track_started.connect(self.track_started) # Populate self.playlist_rows - for dto in ds.get_playlist_rows(self.playlist_id): + for dto in ds.playlistrows_by_playlist(self.playlist_id): self.playlist_rows[dto.row_number] = PlaylistRow(dto) self.update_track_times() @@ -148,16 +148,16 @@ class PlaylistModel(QAbstractTableModel): return header_row # @log_call - def add_track_to_header(self, track_details: InsertTrack) -> None: + def add_track_to_header(self, track_and_playlist: TrackAndPlaylist) -> None: """ Handle signal_add_track_to_header """ - if track_details.playlist_id != self.playlist_id: + if track_and_playlist.playlist_id != self.playlist_id: return if not self.selected_rows: - return + raise ApplicationError("Add track to header but no row selected") if len(self.selected_rows) > 1: self.signals.show_warning_signal.emit( @@ -172,9 +172,7 @@ class PlaylistModel(QAbstractTableModel): ) return - if selected_row.note: - selected_row.note += " " + track_details.note - selected_row.track_id = track_details.track_id + selected_row.track_id = track_and_playlist.track_id # Update local copy self.refresh_row(selected_row.row_number) @@ -257,7 +255,7 @@ class PlaylistModel(QAbstractTableModel): return len(Col) # @log_call - def track_started(self, play_track: PlayTrack) -> None: + def track_started(self, play_track: TrackAndPlaylist) -> None: """ Notification from musicmuster that the current track has just started playing @@ -296,7 +294,7 @@ class PlaylistModel(QAbstractTableModel): self.obs_scene_change(row_number) # Update Playdates in database - ds.update_playdates(track_id) + ds.playdates_update(track_id) # Mark track as played in playlist playlist_dto.played = True @@ -713,7 +711,7 @@ class PlaylistModel(QAbstractTableModel): super().beginInsertRows(QModelIndex(), new_row_number, new_row_number) - _ = ds.insert_row( + _ = ds.playlist_insert_row( playlist_id=self.playlist_id, row_number=new_row_number, track_id=track_id, @@ -1022,7 +1020,7 @@ class PlaylistModel(QAbstractTableModel): # build a new playlist_rows new_playlist_rows: dict[int, PlaylistRow] = {} - for dto in ds.get_playlist_rows(self.playlist_id): + for dto in ds.playlistrows_by_playlist(self.playlist_id): if dto.playlistrow_id not in plrid_to_row: new_playlist_rows[dto.row_number] = PlaylistRow(dto) else: diff --git a/app/playlistrow.py b/app/playlistrow.py index c733bab..aee0fb8 100644 --- a/app/playlistrow.py +++ b/app/playlistrow.py @@ -161,7 +161,7 @@ class PlaylistRow: if self.track_id > 0: raise ApplicationError("Attempting to add track to row with existing track ({self=}") - ds.add_track_to_header(track_id) + ds.add_track_to_header(playlistrow_id=self.playlistrow_id, track_id=track_id) # Need to update with track information track = ds.track_by_id(track_id) @@ -169,12 +169,6 @@ class PlaylistRow: for attr, value in track.__dataclass_fields__.items(): setattr(self, attr, value) - # TODO: set up write access to track_id. Should only update if - # track_id == 0. Need to update all other track fields at the - # same time. - print(f"set track_id attribute for {self=}, {value=}") - pass - # Expose PlaylistRowDTO fields as properties @property def note(self): @@ -583,7 +577,7 @@ class TrackSequence: for ts in [self.next, self.current, self.previous]: if not ts: continue - playlist_row_dto = ds.get_playlist_row(ts.playlistrow_id) + playlist_row_dto = ds.playlistrow_by_id(ts.playlistrow_id) if not playlist_row_dto: raise ApplicationError(f"(Can't retrieve PlaylistRows entry, {self=}") ts = PlaylistRow(playlist_row_dto) diff --git a/app/playlists.py b/app/playlists.py index dee8f1a..75ebdb0 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -40,7 +40,7 @@ from classes import ( Col, MusicMusterSignals, PlaylistStyle, - PlayTrack, + TrackAndPlaylist, TrackInfo ) from config import Config @@ -519,7 +519,9 @@ class PlaylistTab(QTableView): return menu_item def _add_track(self) -> None: - """Add a track to a section header making it a normal track row""" + """ + Add a track to a section header making it a normal track row. + """ dlg = TrackInsertDialog( parent=self.musicmuster, @@ -688,7 +690,7 @@ class PlaylistTab(QTableView): self.resizeRowsToContents() # Save settings - ds.set_setting( + ds.setting_set( f"playlist_col_{column_number}_width", self.columnWidth(column_number) ) @@ -724,7 +726,7 @@ class PlaylistTab(QTableView): cb.setText(track_path, mode=cb.Mode.Clipboard) # @log_call - def track_started(self, play_track: PlayTrack) -> None: + def track_started(self, play_track: TrackAndPlaylist) -> None: """ Called when track starts playing """ diff --git a/app/querylistmodel.py b/app/querylistmodel.py index c1a2432..2af079f 100644 --- a/app/querylistmodel.py +++ b/app/querylistmodel.py @@ -271,4 +271,4 @@ class QuerylistModel(QAbstractTableModel): track_id = self.querylist_rows[row].track_id if not track_id: return QVariant() - return ds.get_last_played_dates(track_id) + return ds.playdates_get_last(track_id) diff --git a/app/utilities.py b/app/utilities.py index 3b5a61a..eca3990 100755 --- a/app/utilities.py +++ b/app/utilities.py @@ -26,7 +26,7 @@ def check_db() -> None: Check all paths in database exist """ - db_paths = set([a.path for a in ds.get_all_tracks()]) + db_paths = set([a.path for a in ds.tracks_all()]) os_paths_list = [] for root, _dirs, files in os.walk(Config.ROOT): @@ -88,7 +88,7 @@ def update_bitrates() -> None: Update bitrates on all tracks in database """ - for track in ds.get_all_tracks(): + for track in ds.tracks_all(): try: t = get_tags(track.path) # TODO this won't persist as we're updating DTO diff --git a/tests/X_test_file_importer.py b/tests/X_test_file_importer.py index 3bb2433..90216c9 100644 --- a/tests/X_test_file_importer.py +++ b/tests/X_test_file_importer.py @@ -55,7 +55,7 @@ class MyTestCase(unittest.TestCase): # Create a playlist for all tests playlist_name = "file importer playlist" - playlist = ds.create_playlist(name=playlist_name, template_id=0) + playlist = ds.playlist_create(name=playlist_name, template_id=0) cls.widget._open_playlist(playlist) # Create our musicstore diff --git a/tests/test_ds.py b/tests/test_ds.py index 3a416e9..e83acb4 100644 --- a/tests/test_ds.py +++ b/tests/test_ds.py @@ -163,7 +163,7 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order = [] - for row in ds.get_playlist_rows(playlist.playlist_id): + for row in ds.playlistrows_by_playlist(playlist.playlist_id): new_order.append(int(row.note)) assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9] @@ -177,7 +177,7 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order = [] - for row in ds.get_playlist_rows(playlist.playlist_id): + for row in ds.playlistrows_by_playlist(playlist.playlist_id): new_order.append(int(row.note)) assert new_order == [0, 1, 2, 4, 3, 5, 6, 7, 8, 9] @@ -191,7 +191,7 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order = [] - for row in ds.get_playlist_rows(playlist.playlist_id): + for row in ds.playlistrows_by_playlist(playlist.playlist_id): new_order.append(int(row.note)) assert new_order == [0, 1, 4, 2, 3, 5, 6, 7, 8, 9] @@ -205,7 +205,7 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order = [] - for row in ds.get_playlist_rows(playlist.playlist_id): + for row in ds.playlistrows_by_playlist(playlist.playlist_id): new_order.append(int(row.note)) assert new_order == [0, 2, 3, 6, 7, 8, 1, 4, 5, 10, 9] @@ -219,7 +219,7 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order = [] - for row in ds.get_playlist_rows(playlist.playlist_id): + for row in ds.playlistrows_by_playlist(playlist.playlist_id): new_order.append(int(row.note)) assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9, 10] @@ -233,7 +233,7 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order = [] - for row in ds.get_playlist_rows(playlist.playlist_id): + for row in ds.playlistrows_by_playlist(playlist.playlist_id): new_order.append(int(row.note)) assert new_order == [0, 1, 2, 4, 7, 8, 9, 10, 3, 5, 6] @@ -247,7 +247,7 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order = [] - for row in ds.get_playlist_rows(playlist.playlist_id): + for row in ds.playlistrows_by_playlist(playlist.playlist_id): new_order.append(int(row.note)) assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9] @@ -262,7 +262,7 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order = [] - for row in ds.get_playlist_rows(playlist.playlist_id): + for row in ds.playlistrows_by_playlist(playlist.playlist_id): new_order.append(int(row.note)) assert new_order == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -280,11 +280,11 @@ class MyTestCase(unittest.TestCase): # Check we have all rows and plr_rownums are correct new_order_src = [] - for row in ds.get_playlist_rows(playlist_src.playlist_id): + for row in ds.playlistrows_by_playlist(playlist_src.playlist_id): new_order_src.append(int(row.note)) assert new_order_src == [0, 1, 3, 5, 7, 8, 9, 10] new_order_dst = [] - for row in ds.get_playlist_rows(playlist_dst.playlist_id): + for row in ds.playlistrows_by_playlist(playlist_dst.playlist_id): new_order_dst.append(int(row.note)) assert new_order_dst == [0, 1, 2, 3, 4, 2, 4, 6, 5, 6, 7, 8, 9, 10] diff --git a/tests/test_file_importer.py b/tests/test_file_importer.py index 7544cd4..0e81232 100644 --- a/tests/test_file_importer.py +++ b/tests/test_file_importer.py @@ -52,7 +52,7 @@ class MyTestCase(unittest.TestCase): # Create a playlist for all tests playlist_name = "file importer playlist" - playlist = ds.create_playlist(name=playlist_name, template_id=0) + playlist = ds.playlist_create(name=playlist_name, template_id=0) cls.widget._open_playlist(playlist) # Create our musicstore diff --git a/tests/test_playlistmodel.py b/tests/test_playlistmodel.py index ec2344a..84185c8 100644 --- a/tests/test_playlistmodel.py +++ b/tests/test_playlistmodel.py @@ -8,14 +8,14 @@ from PyQt6.QtCore import Qt, QModelIndex # App imports from app.helpers import get_all_track_metadata -from app import playlistmodel +from app import ds, playlistmodel from app.models import ( db, Playlists, Tracks, ) from classes import ( - InsertTrack, + TrackAndPlaylist, ) @@ -36,18 +36,13 @@ class TestMMMiscTracks(unittest.TestCase): db.create_all() # Create a playlist and model - with db.Session() as session: - self.playlist = Playlists(session, PLAYLIST_NAME, template_id=0) - self.model = playlistmodel.PlaylistModel( - self.playlist.id, is_template=False - ) + self.playlist = ds.playlist_create(PLAYLIST_NAME, template_id=0) + self.model = playlistmodel.PlaylistModel(self.playlist.playlist_id, is_template=False) - for row in range(len(self.test_tracks)): - track_path = self.test_tracks[row % len(self.test_tracks)] - track = Tracks(session, **get_all_track_metadata(track_path)) - self.model.insert_row(track_id=track.id, note=f"{row=}") - - session.commit() + for row in range(len(self.test_tracks)): + track_path = self.test_tracks[row % len(self.test_tracks)] + track = ds.track_create(**get_all_track_metadata(track_path)) + self.model.insert_row(track_id=track.id, note=f"{row=}") def tearDown(self): db.drop_all() @@ -98,28 +93,25 @@ class TestMMMiscNoPlaylist(unittest.TestCase): def test_insert_track_new_playlist(self): # insert a track into a new playlist - with db.Session() as session: - playlist = Playlists(session, self.PLAYLIST_NAME, template_id=0) - # Create a model - model = playlistmodel.PlaylistModel(playlist.id, is_template=False) - # test repr - _ = str(model) + playlist = ds.playlist_create(self.PLAYLIST_NAME, template_id=0) + # Create a model + model = playlistmodel.PlaylistModel(playlist.id, is_template=False) + # test repr + _ = str(model) - track_path = self.test_tracks[0] - metadata = get_all_track_metadata(track_path) - track = Tracks(session, **metadata) - model.insert_row(track_id=track.id) + track_path = self.test_tracks[0] + metadata = get_all_track_metadata(track_path) + track = ds.track_create(metadata) + model.insert_row(track_id=track.id) - prd = model.playlist_rows[model.rowCount() - 1] - # test repr - _ = str(prd) + prd = model.playlist_rows[model.rowCount() - 1] + # test repr + _ = str(prd) - assert ( - model._edit_role( - model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd - ) - == metadata["title"] - ) + assert ( + model._edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd) + == metadata["title"] + ) class TestMMMiscRowMove(unittest.TestCase): @@ -129,15 +121,10 @@ class TestMMMiscRowMove(unittest.TestCase): def setUp(self): db.create_all() - with db.Session() as session: - self.playlist = Playlists(session, self.PLAYLIST_NAME, template_id=0) - self.model = playlistmodel.PlaylistModel( - self.playlist.id, is_template=False - ) - for row in range(self.ROWS_TO_CREATE): - self.model.insert_row(note=str(row)) - - session.commit() + self.playlist = ds.playlist_create(self.PLAYLIST_NAME, template_id=0) + self.model = playlistmodel.PlaylistModel(self.playlist.id, is_template=False) + for row in range(self.ROWS_TO_CREATE): + self.model.insert_row(note=str(row)) def tearDown(self): db.drop_all() @@ -166,6 +153,9 @@ class TestMMMiscRowMove(unittest.TestCase): note_text = "test text" insert_row = 6 + # Fake selected row in model + self.model.selected_rows = [self.model.playlist_rows[insert_row]] + self.model.insert_row(note=note_text) assert self.model.rowCount() == self.ROWS_TO_CREATE + 1 prd = self.model.playlist_rows[insert_row] @@ -189,13 +179,8 @@ class TestMMMiscRowMove(unittest.TestCase): self.model.selected_rows = [self.model.playlist_rows[insert_row]] prd = self.model.playlist_rows[1] - import pdb; pdb.set_trace() self.model.add_track_to_header( - InsertTrack( - playlist_id=self.model.playlist_id, - track_id=prd.track_id, - note=note_text, - ) + TrackAndPlaylist(playlist_id=self.model.playlist_id, track_id=prd.track_id) ) def test_reverse_row_groups_one_row(self): @@ -216,17 +201,19 @@ class TestMMMiscRowMove(unittest.TestCase): def test_move_one_row_between_playlists_to_end(self): from_rows = [3] to_row = self.ROWS_TO_CREATE - destination_playlist = "destination" + destination_playlist_name = "destination" model_src = self.model - with db.Session() as session: - playlist_dst = Playlists(session, destination_playlist, template_id=0) - model_dst = playlistmodel.PlaylistModel(playlist_dst.id, is_template=False) - for row in range(self.ROWS_TO_CREATE): - model_dst.insert_row(note=str(row)) + playlist_dst = ds.playlist_create(destination_playlist_name, template_id=0) + model_dst = playlistmodel.PlaylistModel( + playlist_dst.playlist_id, is_template=False + ) + for row in range(self.ROWS_TO_CREATE): + model_dst.insert_row(note=str(row)) - model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id) - model_dst.refresh_data(session) + ds.playlist_move_rows( + from_rows, self.playlist.playlist_id, to_row, playlist_dst.playlist_id + ) assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows) assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows) @@ -237,17 +224,19 @@ class TestMMMiscRowMove(unittest.TestCase): def test_move_one_row_between_playlists_to_middle(self): from_rows = [3] to_row = 2 - destination_playlist = "destination" + destination_playlist_name = "destination" model_src = self.model - with db.Session() as session: - playlist_dst = Playlists(session, destination_playlist, template_id=0) - model_dst = playlistmodel.PlaylistModel(playlist_dst.id, is_template=False) - for row in range(self.ROWS_TO_CREATE): - model_dst.insert_row(note=str(row)) + playlist_dst = ds.playlist_create(destination_playlist_name, template_id=0) + model_dst = playlistmodel.PlaylistModel( + playlist_dst.playlist_id, is_template=False + ) + for row in range(self.ROWS_TO_CREATE): + model_dst.insert_row(note=str(row)) - model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id) - model_dst.refresh_data(session) + ds.playlist_move_rows( + from_rows, self.playlist.playlist_id, to_row, playlist_dst.playlist_id + ) # Check the rows of the destination model row_notes = [] @@ -264,17 +253,20 @@ class TestMMMiscRowMove(unittest.TestCase): def test_move_multiple_rows_between_playlists_to_end(self): from_rows = [1, 3, 4] to_row = 2 - destination_playlist = "destination" + destination_playlist_name = "destination" model_src = self.model - with db.Session() as session: - playlist_dst = Playlists(session, destination_playlist, template_id=0) - model_dst = playlistmodel.PlaylistModel(playlist_dst.id, is_template=False) - for row in range(self.ROWS_TO_CREATE): - model_dst.insert_row(note=str(row)) - model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id) - model_dst.refresh_data(session) + playlist_dst = ds.playlist_create(destination_playlist_name, template_id=0) + model_dst = playlistmodel.PlaylistModel( + playlist_dst.playlist_id, is_template=False + ) + for row in range(self.ROWS_TO_CREATE): + model_dst.insert_row(note=str(row)) + + ds.playlist_move_rows( + from_rows, self.playlist.id, playlist_dst.playlist_id, to_row + ) # Check the rows of the destination model row_notes = []