Compare commits

...

3 Commits

Author SHA1 Message Date
Keith Edmunds
aec994bafd Rename ds functions; fix add track to header 2025-04-13 09:12:33 +01:00
Keith Edmunds
0c717241ff Command out @log_call decorators 2025-04-12 19:24:25 +01:00
Keith Edmunds
728d012257 WIP: working on tests 2025-04-12 19:16:09 +01:00
16 changed files with 1158 additions and 665 deletions

View File

@ -207,7 +207,7 @@ class InsertTrack:
@dataclass @dataclass
class PlayTrack: class TrackAndPlaylist:
playlist_id: int playlist_id: int
track_id: int track_id: int
@ -229,7 +229,7 @@ class MusicMusterSignals(QObject):
search_songfacts_signal = pyqtSignal(str) search_songfacts_signal = pyqtSignal(str)
search_wikipedia_signal = pyqtSignal(str) search_wikipedia_signal = pyqtSignal(str)
show_warning_signal = pyqtSignal(str, 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_begin_insert_rows = pyqtSignal(InsertRows)
signal_end_insert_rows = pyqtSignal(int) signal_end_insert_rows = pyqtSignal(int)
signal_insert_track = pyqtSignal(InsertTrack) signal_insert_track = pyqtSignal(InsertTrack)
@ -239,7 +239,7 @@ class MusicMusterSignals(QObject):
# specify that here as it requires us to import PlaylistRow from # specify that here as it requires us to import PlaylistRow from
# playlistrow.py, which itself imports MusicMusterSignals # playlistrow.py, which itself imports MusicMusterSignals
signal_set_next_track = pyqtSignal(object) 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) span_cells_signal = pyqtSignal(int, int, int, int, int)
status_message_signal = pyqtSignal(str, int) status_message_signal = pyqtSignal(str, int)
track_ended_signal = pyqtSignal() track_ended_signal = pyqtSignal()

View File

@ -18,7 +18,12 @@ from PyQt6.QtWidgets import (
# Third party imports # Third party imports
# App imports # App imports
from classes import ApplicationError, InsertTrack, MusicMusterSignals from classes import (
ApplicationError,
InsertTrack,
MusicMusterSignals,
TrackAndPlaylist,
)
from helpers import ( from helpers import (
get_relative_date, get_relative_date,
ms_to_mmss, ms_to_mmss,
@ -94,8 +99,8 @@ class TrackInsertDialog(QDialog):
self.setLayout(layout) self.setLayout(layout)
self.resize(800, 600) self.resize(800, 600)
width = ds.get_setting("dbdialog_width") or 800 width = ds.setting_get("dbdialog_width") or 800
height = ds.get_setting("dbdialog_height") or 800 height = ds.setting_get("dbdialog_height") or 800
self.resize(width, height) self.resize(width, height)
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
@ -141,7 +146,12 @@ class TrackInsertDialog(QDialog):
self.title_edit.setFocus() self.title_edit.setFocus()
if self.add_to_header: 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() self.accept()
else: else:
self.signals.signal_insert_track.emit(insert_track_data) self.signals.signal_insert_track.emit(insert_track_data)

749
app/ds.py

File diff suppressed because it is too large Load Diff

View File

@ -640,7 +640,7 @@ class DoTrackImport(QThread):
if self.track_id == 0: if self.track_id == 0:
track_dto = ds.create_track(self.destination_track_path, metadata) track_dto = ds.create_track(self.destination_track_path, metadata)
else: else:
track_dto = ds.update_track( track_dto = ds.track_update(
self.destination_track_path, self.track_id, metadata self.destination_track_path, self.track_id, metadata
) )

View File

@ -168,7 +168,7 @@ def get_name(prompt: str, default: str = "") -> str | None:
def get_relative_date( def get_relative_date(
past_date: Optional[dt.datetime], now: Optional[dt.datetime] = None past_date: Optional[dt.datetime], reference_date: Optional[dt.datetime] = None
) -> str: ) -> str:
""" """
Return how long before reference_date past_date is as string. Return how long before reference_date past_date is as string.
@ -182,33 +182,37 @@ def get_relative_date(
if not past_date or past_date == Config.EPOCH: if not past_date or past_date == Config.EPOCH:
return "Never" return "Never"
if not now: if not reference_date:
now = dt.datetime.now() reference_date = dt.datetime.now()
# Check parameters # Check parameters
if past_date > now: if past_date > reference_date:
raise ApplicationError("get_relative_date() past_date is after relative_date") raise ApplicationError("get_relative_date() past_date is after relative_date")
delta = now - past_date delta = reference_date - past_date
days = delta.days days = delta.days
if days == 0: if days == 0:
return "(Today)" return Config.LAST_PLAYED_TODAY_STRING + " " + past_date.strftime("%H:%M")
elif days == 1: elif days == 1:
return "(Yesterday)" return "(Yesterday)"
years, days_remain = divmod(days, 365) years, days_remain_years = divmod(days, 365)
months, days_final = divmod(days_remain, 30) months, days_remain_months = divmod(days_remain_years, 30)
weeks, days_final = divmod(days_remain_months, 7)
parts = [] parts = []
if years: if years:
parts.append(f"{years}y") parts.append(f"{years}y")
if months: if months:
parts.append(f"{months}m") parts.append(f"{months}m")
if weeks:
parts.append(f"{weeks}w")
if days_final: if days_final:
parts.append(f"{days_final}d") parts.append(f"{days_final}d")
formatted = " ".join(parts) formatted = ", ".join(parts)
return f"({formatted} ago)" return formatted
def get_tags(path: str) -> Tags: def get_tags(path: str) -> Tags:

View File

@ -66,7 +66,8 @@ from classes import (
ApplicationError, ApplicationError,
Filter, Filter,
MusicMusterSignals, MusicMusterSignals,
PlayTrack, PlaylistDTO,
TrackAndPlaylist,
QueryDTO, QueryDTO,
TrackInfo, TrackInfo,
) )
@ -481,7 +482,7 @@ class ManageQueries(ItemlistManager):
self.populate_table(query_list) self.populate_table(query_list)
@log_call # @log_call
def delete_item(self, query_id: int) -> None: def delete_item(self, query_id: int) -> None:
"""delete query""" """delete query"""
@ -578,7 +579,7 @@ class ManageTemplates(ItemlistManager):
self.populate_table(template_list) self.populate_table(template_list)
@log_call # @log_call
def delete_item(self, template_id: int) -> None: def delete_item(self, template_id: int) -> None:
"""delete template""" """delete template"""
@ -1402,7 +1403,7 @@ class Window(QMainWindow):
# # # # # # # # # # Playlist management functions # # # # # # # # # # # # # # # # # # # # Playlist management functions # # # # # # # # # #
@log_call # @log_call
def _create_playlist(self, name: str, template_id: int) -> Playlists: def _create_playlist(self, name: str, template_id: int) -> Playlists:
""" """
Create a playlist in the database, populate it from the template Create a playlist in the database, populate it from the template
@ -1411,8 +1412,8 @@ class Window(QMainWindow):
return ds.create_playlist(name, template_id) return ds.create_playlist(name, template_id)
@log_call # @log_call
def _open_playlist(self, playlist: Playlists, is_template: bool = False) -> int: def _open_playlist(self, playlist: PlaylistDTO, is_template: bool = False) -> int:
""" """
With passed playlist: With passed playlist:
- create models - create models
@ -1440,7 +1441,7 @@ class Window(QMainWindow):
return idx return idx
@log_call # @log_call
def create_playlist_from_template(self, template_id: int) -> None: def create_playlist_from_template(self, template_id: int) -> None:
""" """
Prompt for new playlist name and create from passed template_id Prompt for new playlist name and create from passed template_id
@ -1460,7 +1461,7 @@ class Window(QMainWindow):
_ = ds.create_playlist(playlist_name, template_id) _ = ds.create_playlist(playlist_name, template_id)
@log_call # @log_call
def delete_playlist(self, checked: bool = False) -> None: def delete_playlist(self, checked: bool = False) -> None:
""" """
Delete current playlist. checked paramater passed by menu system Delete current playlist. checked paramater passed by menu system
@ -1594,7 +1595,7 @@ class Window(QMainWindow):
except subprocess.CalledProcessError as exc_info: except subprocess.CalledProcessError as exc_info:
git_tag = str(exc_info.output) git_tag = str(exc_info.output)
dbname = ds.get_db_name() dbname = ds.db_name_get()
QMessageBox.information( QMessageBox.information(
self, self,
@ -1704,7 +1705,7 @@ class Window(QMainWindow):
self.signals.search_songfacts_signal.connect(self.open_songfacts_browser) self.signals.search_songfacts_signal.connect(self.open_songfacts_browser)
self.signals.search_wikipedia_signal.connect(self.open_wikipedia_browser) self.signals.search_wikipedia_signal.connect(self.open_wikipedia_browser)
@log_call # @log_call
def current_row_or_end(self) -> int: def current_row_or_end(self) -> int:
""" """
If a row or rows are selected, return the row number of the first If a row or rows are selected, return the row number of the first
@ -1755,7 +1756,7 @@ class Window(QMainWindow):
self.footer_section.btnDrop3db.isChecked() self.footer_section.btnDrop3db.isChecked()
) )
@log_call # @log_call
def enable_escape(self, enabled: bool) -> None: def enable_escape(self, enabled: bool) -> None:
""" """
Manage signal to enable/disable handling ESC character. Manage signal to enable/disable handling ESC character.
@ -1767,7 +1768,7 @@ class Window(QMainWindow):
if "clear_selection" in self.menu_actions: if "clear_selection" in self.menu_actions:
self.menu_actions["clear_selection"].setEnabled(enabled) self.menu_actions["clear_selection"].setEnabled(enabled)
@log_call # @log_call
def end_of_track_actions(self) -> None: def end_of_track_actions(self) -> None:
""" """
@ -1904,7 +1905,7 @@ class Window(QMainWindow):
dlg = TrackInsertDialog(parent=self, playlist_id=self.active_tab().playlist_id) dlg = TrackInsertDialog(parent=self, playlist_id=self.active_tab().playlist_id)
dlg.exec() dlg.exec()
@log_call # @log_call
def load_last_playlists(self) -> None: def load_last_playlists(self) -> None:
"""Load the playlists that were open when app was last closed""" """Load the playlists that were open when app was last closed"""
@ -1955,7 +1956,7 @@ class Window(QMainWindow):
f"mark_rows_for_moving(): {self.move_source_rows=} {self.move_source_model=}" f"mark_rows_for_moving(): {self.move_source_rows=} {self.move_source_model=}"
) )
@log_call # @log_call
def move_playlist_rows(self, row_numbers: list[int]) -> None: def move_playlist_rows(self, row_numbers: list[int]) -> None:
""" """
Move passed playlist rows to another playlist Move passed playlist rows to another playlist
@ -2032,7 +2033,7 @@ class Window(QMainWindow):
webbrowser.get("browser").open_new_tab(url) webbrowser.get("browser").open_new_tab(url)
@log_call # @log_call
def paste_rows(self, checked: bool = False) -> None: def paste_rows(self, checked: bool = False) -> None:
""" """
Paste earlier cut rows. Paste earlier cut rows.
@ -2066,7 +2067,7 @@ class Window(QMainWindow):
if set_next_row: if set_next_row:
to_playlist_model.set_next_row(set_next_row) to_playlist_model.set_next_row(set_next_row)
@log_call # @log_call
def play_next( def play_next(
self, position: Optional[float] = None, checked: bool = False self, position: Optional[float] = None, checked: bool = False
) -> None: ) -> None:
@ -2141,7 +2142,7 @@ class Window(QMainWindow):
# Notify others # Notify others
self.signals.signal_track_started.emit( 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) self.track_sequence.current.track_id)
) )
@ -2421,7 +2422,7 @@ class Window(QMainWindow):
height = ds.get_setting("mainwindow_height") or 100 height = ds.get_setting("mainwindow_height") or 100
self.setGeometry(x, y, width, height) self.setGeometry(x, y, width, height)
@log_call # @log_call
def set_selected_track_next(self, checked: bool = False) -> None: def set_selected_track_next(self, checked: bool = False) -> None:
""" """
Set currently-selected row on visible playlist tab as next track Set currently-selected row on visible playlist tab as next track
@ -2435,7 +2436,7 @@ class Window(QMainWindow):
# else: # else:
# log.error("No active tab") # log.error("No active tab")
@log_call # @log_call
def set_tab_colour(self, widget: PlaylistTab, colour: QColor) -> None: def set_tab_colour(self, widget: PlaylistTab, colour: QColor) -> None:
""" """
Find the tab containing the widget and set the text colour Find the tab containing the widget and set the text colour
@ -2497,7 +2498,7 @@ class Window(QMainWindow):
self.active_tab().scroll_to_top(playlist_track.row_number) self.active_tab().scroll_to_top(playlist_track.row_number)
@log_call # @log_call
def stop(self, checked: bool = False) -> None: def stop(self, checked: bool = False) -> None:
"""Stop playing immediately""" """Stop playing immediately"""

View File

@ -36,7 +36,7 @@ from classes import (
InsertRows, InsertRows,
InsertTrack, InsertTrack,
MusicMusterSignals, MusicMusterSignals,
PlayTrack, TrackAndPlaylist,
) )
from config import Config from config import Config
from helpers import ( from helpers import (
@ -102,7 +102,7 @@ class PlaylistModel(QAbstractTableModel):
self.signals.signal_track_started.connect(self.track_started) self.signals.signal_track_started.connect(self.track_started)
# Populate self.playlist_rows # 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.playlist_rows[dto.row_number] = PlaylistRow(dto)
self.update_track_times() self.update_track_times()
@ -147,17 +147,17 @@ class PlaylistModel(QAbstractTableModel):
return header_row return header_row
@log_call # @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 Handle signal_add_track_to_header
""" """
if track_details.playlist_id != self.playlist_id: if track_and_playlist.playlist_id != self.playlist_id:
return return
if not self.selected_rows: if not self.selected_rows:
return raise ApplicationError("Add track to header but no row selected")
if len(self.selected_rows) > 1: if len(self.selected_rows) > 1:
self.signals.show_warning_signal.emit( self.signals.show_warning_signal.emit(
@ -172,9 +172,7 @@ class PlaylistModel(QAbstractTableModel):
) )
return return
if selected_row.note: selected_row.track_id = track_and_playlist.track_id
selected_row.note += " " + track_details.note
selected_row.track_id = track_details.track_id
# Update local copy # Update local copy
self.refresh_row(selected_row.row_number) self.refresh_row(selected_row.row_number)
@ -241,7 +239,7 @@ class PlaylistModel(QAbstractTableModel):
return QBrush() return QBrush()
@log_call # @log_call
def begin_reset_model(self, playlist_id: int) -> None: def begin_reset_model(self, playlist_id: int) -> None:
""" """
Reset model if playlist_id is ours Reset model if playlist_id is ours
@ -256,8 +254,8 @@ class PlaylistModel(QAbstractTableModel):
return len(Col) return len(Col)
@log_call # @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 Notification from musicmuster that the current track has just
started playing started playing
@ -296,7 +294,7 @@ class PlaylistModel(QAbstractTableModel):
self.obs_scene_change(row_number) self.obs_scene_change(row_number)
# Update Playdates in database # Update Playdates in database
ds.update_playdates(track_id) ds.playdates_update(track_id)
# Mark track as played in playlist # Mark track as played in playlist
playlist_dto.played = True playlist_dto.played = True
@ -382,7 +380,7 @@ class PlaylistModel(QAbstractTableModel):
return QVariant() return QVariant()
@log_call # @log_call
def delete_rows(self, row_numbers: list[int]) -> None: def delete_rows(self, row_numbers: list[int]) -> None:
""" """
Delete passed rows from model Delete passed rows from model
@ -465,7 +463,7 @@ class PlaylistModel(QAbstractTableModel):
return "" return ""
@log_call # @log_call
def end_reset_model(self, playlist_id: int) -> None: def end_reset_model(self, playlist_id: int) -> None:
""" """
End model reset if this is our playlist End model reset if this is our playlist
@ -544,7 +542,7 @@ class PlaylistModel(QAbstractTableModel):
return boldfont return boldfont
@log_call # @log_call
def get_duplicate_rows(self) -> list[int]: def get_duplicate_rows(self) -> list[int]:
""" """
Return a list of duplicate rows. If track appears in rows 2, 3 and 4, return [3, 4] Return a list of duplicate rows. If track appears in rows 2, 3 and 4, return [3, 4]
@ -565,7 +563,7 @@ class PlaylistModel(QAbstractTableModel):
return result return result
@log_call # @log_call
def _get_new_row_number(self) -> int: def _get_new_row_number(self) -> int:
""" """
Get row number for new row. Get row number for new row.
@ -586,7 +584,7 @@ class PlaylistModel(QAbstractTableModel):
return self.playlist_rows[row_number] return self.playlist_rows[row_number]
@log_call # @log_call
def get_row_track_id(self, row_number: int) -> Optional[int]: def get_row_track_id(self, row_number: int) -> Optional[int]:
""" """
Return id of track associated with row or None if no track associated Return id of track associated with row or None if no track associated
@ -703,7 +701,7 @@ class PlaylistModel(QAbstractTableModel):
] ]
self.invalidate_row(row_number, roles) self.invalidate_row(row_number, roles)
@log_call # @log_call
def insert_row(self, track_id: Optional[int] = None, note: str = "",) -> None: def insert_row(self, track_id: Optional[int] = None, note: str = "",) -> None:
""" """
Insert a row. Insert a row.
@ -713,18 +711,15 @@ class PlaylistModel(QAbstractTableModel):
super().beginInsertRows(QModelIndex(), new_row_number, new_row_number) super().beginInsertRows(QModelIndex(), new_row_number, new_row_number)
new_row = ds.insert_row( _ = ds.playlist_insert_row(
playlist_id=self.playlist_id, playlist_id=self.playlist_id,
row_number=new_row_number, row_number=new_row_number,
track_id=track_id, track_id=track_id,
note=note, note=note,
) )
# Need to refresh self.playlist_rows because row numbers will have
# Move rows down to make room # changed
for destination_row in range(len(self.playlist_rows), new_row_number, -1): self.refresh_data()
self.playlist_rows[destination_row] = self.playlist_rows[destination_row - 1]
# Insert into self.playlist_rows
self.playlist_rows[new_row_number] = PlaylistRow(new_row)
super().endInsertRows() super().endInsertRows()
@ -741,7 +736,7 @@ class PlaylistModel(QAbstractTableModel):
list(range(new_row_number, len(self.playlist_rows))), roles_to_invalidate list(range(new_row_number, len(self.playlist_rows))), roles_to_invalidate
) )
@log_call # @log_call
def invalidate_row(self, modified_row: int, roles: list[Qt.ItemDataRole]) -> None: def invalidate_row(self, modified_row: int, roles: list[Qt.ItemDataRole]) -> None:
""" """
Signal to view to refresh invalidated row Signal to view to refresh invalidated row
@ -773,7 +768,7 @@ class PlaylistModel(QAbstractTableModel):
return self.playlist_rows[row_number].path == "" return self.playlist_rows[row_number].path == ""
return False return False
@log_call # @log_call
def is_played_row(self, row_number: int) -> bool: def is_played_row(self, row_number: int) -> bool:
""" """
Return True if row is an unplayed track row, else False Return True if row is an unplayed track row, else False
@ -809,7 +804,7 @@ class PlaylistModel(QAbstractTableModel):
] ]
self.invalidate_rows(row_numbers, roles) self.invalidate_rows(row_numbers, roles)
@log_call # @log_call
def move_rows(self, from_rows: list[int], to_row_number: int) -> bool: def move_rows(self, from_rows: list[int], to_row_number: int) -> bool:
""" """
Move the playlist rows in from_rows to to_row. Return True if successful Move the playlist rows in from_rows to to_row. Return True if successful
@ -859,7 +854,7 @@ class PlaylistModel(QAbstractTableModel):
# self.invalidate_rows(list(row_map.keys()), roles) # self.invalidate_rows(list(row_map.keys()), roles)
return True return True
@log_call # @log_call
def move_rows_between_playlists( def move_rows_between_playlists(
self, self,
from_rows: list[PlaylistRow], from_rows: list[PlaylistRow],
@ -927,7 +922,7 @@ class PlaylistModel(QAbstractTableModel):
super().endInsertRows() super().endInsertRows()
@log_call # @log_call
def move_track_add_note( def move_track_add_note(
self, new_row_number: int, existing_plr: PlaylistRow, note: str self, new_row_number: int, existing_plr: PlaylistRow, note: str
) -> None: ) -> None:
@ -946,7 +941,7 @@ class PlaylistModel(QAbstractTableModel):
self.move_rows([existing_plr.row_number], new_row_number) self.move_rows([existing_plr.row_number], new_row_number)
self.signals.resize_rows_signal.emit(self.playlist_id) self.signals.resize_rows_signal.emit(self.playlist_id)
@log_call # @log_call
def obs_scene_change(self, row_number: int) -> None: def obs_scene_change(self, row_number: int) -> None:
""" """
Check this row and any preceding headers for OBS scene change command Check this row and any preceding headers for OBS scene change command
@ -981,7 +976,7 @@ class PlaylistModel(QAbstractTableModel):
log.warning(f"{self}: OBS connection refused") log.warning(f"{self}: OBS connection refused")
return return
@log_call # @log_call
def previous_track_ended(self) -> None: def previous_track_ended(self) -> None:
""" """
Notification from musicmuster that the previous track has ended. Notification from musicmuster that the previous track has ended.
@ -1025,7 +1020,7 @@ class PlaylistModel(QAbstractTableModel):
# build a new playlist_rows # build a new playlist_rows
new_playlist_rows: dict[int, PlaylistRow] = {} 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: if dto.playlistrow_id not in plrid_to_row:
new_playlist_rows[dto.row_number] = PlaylistRow(dto) new_playlist_rows[dto.row_number] = PlaylistRow(dto)
else: else:
@ -1046,7 +1041,7 @@ class PlaylistModel(QAbstractTableModel):
self.playlist_rows[row_number] = PlaylistRow(refreshed_row) self.playlist_rows[row_number] = PlaylistRow(refreshed_row)
@log_call # @log_call
def remove_track(self, row_number: int) -> None: def remove_track(self, row_number: int) -> None:
""" """
Remove track from row, retaining row as a header row Remove track from row, retaining row as a header row
@ -1077,7 +1072,7 @@ class PlaylistModel(QAbstractTableModel):
self.invalidate_row(row_number, roles) self.invalidate_row(row_number, roles)
self.signals.resize_rows_signal.emit(self.playlist_id) self.signals.resize_rows_signal.emit(self.playlist_id)
@log_call # @log_call
def reset_track_sequence_row_numbers(self) -> None: def reset_track_sequence_row_numbers(self) -> None:
""" """
Signal handler for when row ordering has changed. Signal handler for when row ordering has changed.
@ -1117,7 +1112,7 @@ class PlaylistModel(QAbstractTableModel):
] ]
self.invalidate_rows(row_numbers, roles) self.invalidate_rows(row_numbers, roles)
@log_call # @log_call
def _reversed_contiguous_row_groups( def _reversed_contiguous_row_groups(
self, row_numbers: list[int] self, row_numbers: list[int]
) -> list[list[int]]: ) -> list[list[int]]:
@ -1258,7 +1253,7 @@ class PlaylistModel(QAbstractTableModel):
return True return True
@log_call # @log_call
def set_selected_rows(self, playlist_id: int, selected_row_numbers: list[int]) -> None: def set_selected_rows(self, playlist_id: int, selected_row_numbers: list[int]) -> None:
""" """
Handle signal_playlist_selected_rows to keep track of which rows Handle signal_playlist_selected_rows to keep track of which rows
@ -1270,7 +1265,7 @@ class PlaylistModel(QAbstractTableModel):
self.selected_rows = [self.playlist_rows[a] for a in selected_row_numbers] self.selected_rows = [self.playlist_rows[a] for a in selected_row_numbers]
@log_call # @log_call
def set_next_row(self, playlist_id: int) -> None: def set_next_row(self, playlist_id: int) -> None:
""" """
Handle signal_set_next_row Handle signal_set_next_row
@ -1315,7 +1310,7 @@ class PlaylistModel(QAbstractTableModel):
self.signals.next_track_changed_signal.emit() self.signals.next_track_changed_signal.emit()
self.update_track_times() self.update_track_times()
@log_call # @log_call
def setData( def setData(
self, self,
index: QModelIndex, index: QModelIndex,
@ -1452,7 +1447,7 @@ class PlaylistModel(QAbstractTableModel):
return ds.get_last_played_dates(track_id) return ds.get_last_played_dates(track_id)
@log_call # @log_call
def update_or_insert(self, track_id: int, row_number: int) -> None: def update_or_insert(self, track_id: int, row_number: int) -> None:
""" """
If the passed track_id exists in this playlist, update the If the passed track_id exists in this playlist, update the
@ -1476,7 +1471,7 @@ class PlaylistModel(QAbstractTableModel):
else: else:
self.insert_row(track_id=track_id) self.insert_row(track_id=track_id)
@log_call # @log_call
def update_track_times(self) -> None: def update_track_times(self) -> None:
""" """
Update track start/end times in self.playlist_rows Update track start/end times in self.playlist_rows

View File

@ -161,7 +161,7 @@ class PlaylistRow:
if self.track_id > 0: if self.track_id > 0:
raise ApplicationError("Attempting to add track to row with existing track ({self=}") 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 # Need to update with track information
track = ds.track_by_id(track_id) track = ds.track_by_id(track_id)
@ -169,12 +169,6 @@ class PlaylistRow:
for attr, value in track.__dataclass_fields__.items(): for attr, value in track.__dataclass_fields__.items():
setattr(self, attr, value) 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 # Expose PlaylistRowDTO fields as properties
@property @property
def note(self): def note(self):
@ -583,7 +577,7 @@ class TrackSequence:
for ts in [self.next, self.current, self.previous]: for ts in [self.next, self.current, self.previous]:
if not ts: if not ts:
continue 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: if not playlist_row_dto:
raise ApplicationError(f"(Can't retrieve PlaylistRows entry, {self=}") raise ApplicationError(f"(Can't retrieve PlaylistRows entry, {self=}")
ts = PlaylistRow(playlist_row_dto) ts = PlaylistRow(playlist_row_dto)

View File

@ -40,7 +40,7 @@ from classes import (
Col, Col,
MusicMusterSignals, MusicMusterSignals,
PlaylistStyle, PlaylistStyle,
PlayTrack, TrackAndPlaylist,
TrackInfo TrackInfo
) )
from config import Config from config import Config
@ -367,7 +367,7 @@ class PlaylistTab(QTableView):
# Deselect edited line # Deselect edited line
self.clear_selection() self.clear_selection()
@log_call # @log_call
def dropEvent(self, event: Optional[QDropEvent]) -> None: def dropEvent(self, event: Optional[QDropEvent]) -> None:
""" """
Move dropped rows Move dropped rows
@ -519,7 +519,9 @@ class PlaylistTab(QTableView):
return menu_item return menu_item
def _add_track(self) -> None: 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( dlg = TrackInsertDialog(
parent=self.musicmuster, parent=self.musicmuster,
@ -688,7 +690,7 @@ class PlaylistTab(QTableView):
self.resizeRowsToContents() self.resizeRowsToContents()
# Save settings # Save settings
ds.set_setting( ds.setting_set(
f"playlist_col_{column_number}_width", self.columnWidth(column_number) f"playlist_col_{column_number}_width", self.columnWidth(column_number)
) )
@ -723,8 +725,8 @@ class PlaylistTab(QTableView):
cb.clear(mode=cb.Mode.Clipboard) cb.clear(mode=cb.Mode.Clipboard)
cb.setText(track_path, mode=cb.Mode.Clipboard) cb.setText(track_path, mode=cb.Mode.Clipboard)
@log_call # @log_call
def track_started(self, play_track: PlayTrack) -> None: def track_started(self, play_track: TrackAndPlaylist) -> None:
""" """
Called when track starts playing Called when track starts playing
""" """
@ -816,7 +818,7 @@ class PlaylistTab(QTableView):
else: else:
return TrackInfo(track_id, selected_row) return TrackInfo(track_id, selected_row)
@log_call # @log_call
def get_selected_row(self) -> Optional[int]: def get_selected_row(self) -> Optional[int]:
""" """
Return selected row number. If no rows or multiple rows selected, return None Return selected row number. If no rows or multiple rows selected, return None
@ -828,7 +830,7 @@ class PlaylistTab(QTableView):
else: else:
return None return None
@log_call # @log_call
def get_selected_rows(self) -> list[int]: def get_selected_rows(self) -> list[int]:
"""Return a list of model-selected row numbers sorted by row""" """Return a list of model-selected row numbers sorted by row"""
@ -841,7 +843,7 @@ class PlaylistTab(QTableView):
return sorted(list(set([self.model().mapToSource(a).row() for a in selected_indexes]))) return sorted(list(set([self.model().mapToSource(a).row() for a in selected_indexes])))
@log_call # @log_call
def get_top_visible_row(self) -> int: def get_top_visible_row(self) -> int:
""" """
Get the viewport of the table view Get the viewport of the table view
@ -1010,7 +1012,7 @@ class PlaylistTab(QTableView):
# Reset selection mode # Reset selection mode
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
@log_call # @log_call
def source_model_selected_row_number(self) -> Optional[int]: def source_model_selected_row_number(self) -> Optional[int]:
""" """
Return the model row number corresponding to the selected row or None Return the model row number corresponding to the selected row or None
@ -1021,7 +1023,7 @@ class PlaylistTab(QTableView):
return None return None
return self.model().mapToSource(selected_index).row() return self.model().mapToSource(selected_index).row()
@log_call # @log_call
def selected_model_row_numbers(self) -> list[int]: def selected_model_row_numbers(self) -> list[int]:
""" """
Return a list of model row numbers corresponding to the selected rows or Return a list of model row numbers corresponding to the selected rows or

View File

@ -271,4 +271,4 @@ class QuerylistModel(QAbstractTableModel):
track_id = self.querylist_rows[row].track_id track_id = self.querylist_rows[row].track_id
if not track_id: if not track_id:
return QVariant() return QVariant()
return ds.get_last_played_dates(track_id) return ds.playdates_get_last(track_id)

View File

@ -26,7 +26,7 @@ def check_db() -> None:
Check all paths in database exist 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 = [] os_paths_list = []
for root, _dirs, files in os.walk(Config.ROOT): for root, _dirs, files in os.walk(Config.ROOT):
@ -88,7 +88,7 @@ def update_bitrates() -> None:
Update bitrates on all tracks in database Update bitrates on all tracks in database
""" """
for track in ds.get_all_tracks(): for track in ds.tracks_all():
try: try:
t = get_tags(track.path) t = get_tags(track.path)
# TODO this won't persist as we're updating DTO # TODO this won't persist as we're updating DTO

View File

@ -0,0 +1,480 @@
"""
Tests are named 'test_nnn_xxxx' where 'nn n' is a number. This is used to ensure that
the tests run in order as we rely (in some cases) upon the results of an earlier test.
Yes, we shouldn't do that.
"""
# Standard library imports
import os
import shutil
import tempfile
import unittest
from unittest.mock import MagicMock, patch
# PyQt imports
from PyQt6.QtWidgets import QDialog, QFileDialog
# Third party imports
from mutagen.mp3 import MP3 # type: ignore
import pytest
from pytestqt.plugin import QtBot # type: ignore
# App imports
from app import ds, musicmuster
from app.models import (
db,
Tracks,
)
from config import Config
from file_importer import FileImporter
# Custom fixture to adapt qtbot for use with unittest.TestCase
@pytest.fixture(scope="class")
def qtbot_adapter(qapp, request):
"""Adapt qtbot fixture for usefixtures and unittest.TestCase"""
request.cls.qtbot = QtBot(request)
# Fixture for tmp_path to be available in the class
@pytest.fixture(scope="class")
def class_tmp_path(request, tmp_path_factory):
"""Provide a class-wide tmp_path"""
request.cls.tmp_path = tmp_path_factory.mktemp("pytest_tmp")
@pytest.mark.usefixtures("qtbot_adapter", "class_tmp_path")
class MyTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Runs once before any test in this class"""
db.create_all()
cls.widget = musicmuster.Window()
# Create a playlist for all tests
playlist_name = "file importer playlist"
playlist = ds.playlist_create(name=playlist_name, template_id=0)
cls.widget._open_playlist(playlist)
# Create our musicstore
cls.import_source = tempfile.mkdtemp(suffix="_MMsource_pytest", dir="/tmp")
Config.REPLACE_FILES_DEFAULT_SOURCE = cls.import_source
cls.musicstore = tempfile.mkdtemp(suffix="_MMstore_pytest", dir="/tmp")
Config.IMPORT_DESTINATION = cls.musicstore
@classmethod
def tearDownClass(cls):
"""Runs once after all tests"""
db.drop_all()
shutil.rmtree(cls.musicstore)
shutil.rmtree(cls.import_source)
def setUp(self):
"""Runs before each test"""
with self.qtbot.waitExposed(self.widget):
self.widget.show()
def tearDown(self):
"""Runs after each test"""
self.widget.close() # Close UI to prevent side effects
def wait_for_workers(self, timeout: int = 10000):
"""
Let import threads workers run to completion
"""
def workers_empty():
assert FileImporter.workers == {}
self.qtbot.waitUntil(workers_empty, timeout=timeout)
def test_001_import_no_files(self):
"""Try importing with no files to import"""
with patch("file_importer.show_OK") as mock_show_ok:
self.widget.import_files_wrapper()
mock_show_ok.assert_called_once_with(
"File import",
f"No files in {Config.REPLACE_FILES_DEFAULT_SOURCE} to import",
None,
)
def test_002_import_file_and_cancel(self):
"""Cancel file import"""
test_track_path = "testdata/isa.mp3"
shutil.copy(test_track_path, self.import_source)
with (
patch("file_importer.PickMatch") as MockPickMatch,
patch("file_importer.show_OK") as mock_show_ok,
):
# Create a mock instance of PickMatch
mock_dialog_instance = MagicMock()
MockPickMatch.return_value = mock_dialog_instance
# Simulate the user clicking OK in the dialog
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Rejected
mock_dialog_instance.selected_track_id = -1 # Simulated return value
self.widget.import_files_wrapper()
# Ensure PickMatch was instantiated correctly
MockPickMatch.assert_called_once_with(
new_track_description="I'm So Afraid (Fleetwood Mac)",
choices=[("Do not import", -1, ""), ("Import as new track", 0, "")],
default=1,
)
# Verify exec() was called
mock_dialog_instance.exec.assert_called_once()
# Ensure selected_track_id was accessed after dialog.exec()
assert mock_dialog_instance.selected_track_id < 0
mock_show_ok.assert_called_once_with(
"File not imported",
"isa.mp3 will not be imported because you asked not to import this file",
)
def test_003_import_first_file(self):
"""Import file into empty directory"""
test_track_path = "testdata/isa.mp3"
shutil.copy(test_track_path, self.import_source)
with patch("file_importer.PickMatch") as MockPickMatch:
# Create a mock instance of PickMatch
mock_dialog_instance = MagicMock()
MockPickMatch.return_value = mock_dialog_instance
# Simulate the user clicking OK in the dialog
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Accepted
mock_dialog_instance.selected_track_id = 0 # Simulated return value
self.widget.import_files_wrapper()
# Ensure PickMatch was instantiated correctly
MockPickMatch.assert_called_once_with(
new_track_description="I'm So Afraid (Fleetwood Mac)",
choices=[("Do not import", -1, ""), ("Import as new track", 0, "")],
default=1,
)
# Verify exec() was called
mock_dialog_instance.exec.assert_called_once()
# Ensure selected_track_id was accessed after dialog.exec()
assert mock_dialog_instance.selected_track_id == 0
self.wait_for_workers()
# Check track was imported
tracks = ds.get_all_tracks()
assert len(tracks) == 1
track = tracks[0]
assert track.title == "I'm So Afraid"
assert track.artist == "Fleetwood Mac"
track_file = os.path.join(
self.musicstore, os.path.basename(test_track_path)
)
assert track.path == track_file
assert os.path.exists(track_file)
assert os.listdir(self.import_source) == []
def test_004_import_second_file(self):
"""Import a second file"""
test_track_path = "testdata/lovecats.mp3"
shutil.copy(test_track_path, self.import_source)
with patch("file_importer.PickMatch") as MockPickMatch:
# Create a mock instance of PickMatch
mock_dialog_instance = MagicMock()
MockPickMatch.return_value = mock_dialog_instance
# Simulate the user clicking OK in the dialog
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Accepted
mock_dialog_instance.selected_track_id = 0 # Simulated return value
self.widget.import_files_wrapper()
# Ensure PickMatch was instantiated correctly
MockPickMatch.assert_called_once_with(
new_track_description="The Lovecats (The Cure)",
choices=[("Do not import", -1, ""), ("Import as new track", 0, "")],
default=1,
)
# Verify exec() was called
mock_dialog_instance.exec.assert_called_once()
# Ensure selected_track_id was accessed after dialog.exec()
assert mock_dialog_instance.selected_track_id == 0
self.wait_for_workers()
# Check track was imported
tracks = ds.get_all_tracks()
assert len(tracks) == 2
track = tracks[1]
assert track.title == "The Lovecats"
assert track.artist == "The Cure"
track_file = os.path.join(
self.musicstore, os.path.basename(test_track_path)
)
assert track.path == track_file
assert os.path.exists(track_file)
assert os.listdir(self.import_source) == []
def test_005_replace_file(self):
"""Import the same file again and update existing track"""
test_track_path = "testdata/lovecats.mp3"
shutil.copy(test_track_path, self.import_source)
with patch("file_importer.PickMatch") as MockPickMatch:
# Create a mock instance of PickMatch
mock_dialog_instance = MagicMock()
MockPickMatch.return_value = mock_dialog_instance
# Simulate the user clicking OK in the dialog
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Accepted
mock_dialog_instance.selected_track_id = 2 # Simulated return value
self.widget.import_files_wrapper()
# Ensure PickMatch was instantiated correctly
MockPickMatch.assert_called_once_with(
new_track_description="The Lovecats (The Cure)",
choices=[
("Do not import", -1, ""),
("Import as new track", 0, ""),
(
"The Lovecats (The Cure) (100%)",
2,
os.path.join(
self.musicstore, os.path.basename(test_track_path)
),
),
],
default=2,
)
# Verify exec() was called
mock_dialog_instance.exec.assert_called_once()
self.wait_for_workers()
# Check track was imported
tracks = ds.get_all_tracks()
assert len(tracks) == 2
track = tracks[1]
assert track.title == "The Lovecats"
assert track.artist == "The Cure"
assert track.track_id == 2
track_file = os.path.join(
self.musicstore, os.path.basename(test_track_path)
)
assert track.path == track_file
assert os.path.exists(track_file)
assert os.listdir(self.import_source) == []
def test_006_import_file_no_tags(self) -> None:
"""Try to import untagged file"""
test_track_path = "testdata/lovecats.mp3"
test_filename = os.path.basename(test_track_path)
shutil.copy(test_track_path, self.import_source)
import_file = os.path.join(self.import_source, test_filename)
assert os.path.exists(import_file)
# Remove tags
src = MP3(import_file)
src.delete()
src.save()
with patch("file_importer.show_OK") as mock_show_ok:
self.widget.import_files_wrapper()
mock_show_ok.assert_called_once_with(
"File not imported",
f"{test_filename} will not be imported because of tag errors "
f"(Missing tags in {import_file})",
)
def test_007_import_unreadable_file(self) -> None:
"""Import unreadable file"""
test_track_path = "testdata/lovecats.mp3"
test_filename = os.path.basename(test_track_path)
shutil.copy(test_track_path, self.import_source)
import_file = os.path.join(self.import_source, test_filename)
assert os.path.exists(import_file)
# Make undreadable
os.chmod(import_file, 0)
with patch("file_importer.show_OK") as mock_show_ok:
self.widget.import_files_wrapper()
mock_show_ok.assert_called_once_with(
"File not imported",
f"{test_filename} will not be imported because {import_file} is unreadable",
)
# clean up
os.chmod(import_file, 0o777)
os.unlink(import_file)
def test_008_import_new_file_existing_destination(self) -> None:
"""Import duplicate file"""
test_track_path = "testdata/lovecats.mp3"
test_filename = os.path.basename(test_track_path)
new_destination = os.path.join(self.musicstore, "lc2.mp3")
shutil.copy(test_track_path, self.import_source)
import_file = os.path.join(self.import_source, test_filename)
assert os.path.exists(import_file)
with (
patch("file_importer.PickMatch") as MockPickMatch,
patch.object(
QFileDialog, "getSaveFileName", return_value=(new_destination, "")
) as mock_file_dialog,
patch("file_importer.show_OK") as mock_show_ok,
):
mock_file_dialog.return_value = (
new_destination,
"",
) # Ensure mock correctly returns expected value
# Create a mock instance of PickMatch
mock_dialog_instance = MagicMock()
MockPickMatch.return_value = mock_dialog_instance
# Simulate the user clicking OK in the dialog
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Accepted
mock_dialog_instance.selected_track_id = 0 # Simulated return value
self.widget.import_files_wrapper()
# Ensure PickMatch was instantiated correctly
MockPickMatch.assert_called_once_with(
new_track_description="The Lovecats (The Cure)",
choices=[
("Do not import", -1, ""),
("Import as new track", 0, ""),
(
"The Lovecats (The Cure) (100%)",
2,
os.path.join(
self.musicstore, os.path.basename(test_track_path)
),
),
],
default=2,
)
# Verify exec() was called
mock_dialog_instance.exec.assert_called_once()
destination = os.path.join(self.musicstore, test_filename)
mock_show_ok.assert_called_once_with(
title="Desintation path exists",
msg=f"New import requested but default destination path ({destination}) "
"already exists. Click OK and choose where to save this track",
parent=None,
)
self.wait_for_workers()
# Ensure QFileDialog was called and returned expected value
assert mock_file_dialog.called # Ensure the mock was used
result = mock_file_dialog()
assert result[0] == new_destination # Validate return value
# Check track was imported
tracks = ds.get_all_tracks()
track = tracks[2]
assert track.title == "The Lovecats"
assert track.artist == "The Cure"
assert track.track_id == 3
assert track.path == new_destination
assert os.path.exists(new_destination)
assert os.listdir(self.import_source) == []
# Remove file so as not to interfere with later tests
ds.delete(track)
tracks = ds.get_all_tracks()
assert len(tracks) == 2
os.unlink(new_destination)
assert not os.path.exists(new_destination)
def test_009_import_similar_file(self) -> None:
"""Import file with similar, but different, title"""
test_track_path = "testdata/lovecats.mp3"
test_filename = os.path.basename(test_track_path)
shutil.copy(test_track_path, self.import_source)
import_file = os.path.join(self.import_source, test_filename)
assert os.path.exists(import_file)
# Change title tag
src = MP3(import_file)
src["TIT2"].text[0] += " xyz"
src.save()
with patch("file_importer.PickMatch") as MockPickMatch:
# Create a mock instance of PickMatch
mock_dialog_instance = MagicMock()
MockPickMatch.return_value = mock_dialog_instance
# Simulate the user clicking OK in the dialog
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Accepted
mock_dialog_instance.selected_track_id = 2 # Simulated return value
self.widget.import_files_wrapper()
# Ensure PickMatch was instantiated correctly
MockPickMatch.assert_called_once_with(
new_track_description="The Lovecats xyz (The Cure)",
choices=[
("Do not import", -1, ""),
("Import as new track", 0, ""),
(
"The Lovecats (The Cure) (93%)",
2,
os.path.join(
self.musicstore, os.path.basename(test_track_path)
),
),
],
default=2,
)
# Verify exec() was called
mock_dialog_instance.exec.assert_called_once()
self.wait_for_workers()
# Check track was imported
tracks = ds.get_all_tracks()
assert len(tracks) == 2
track = tracks[1]
assert track.title == "The Lovecats xyz"
assert track.artist == "The Cure"
assert track.track_id == 2
track_file = os.path.join(
self.musicstore, os.path.basename(test_track_path)
)
assert track.path == track_file
assert os.path.exists(track_file)
assert os.listdir(self.import_source) == []

View File

@ -7,7 +7,7 @@ import unittest
# App imports # App imports
from app import playlistmodel from app import playlistmodel
from app import repository from app import ds
from app.models import db from app.models import db
from classes import PlaylistDTO from classes import PlaylistDTO
from helpers import get_all_track_metadata from helpers import get_all_track_metadata
@ -41,7 +41,7 @@ class MyTestCase(unittest.TestCase):
self, playlist_name: str self, playlist_name: str
) -> (PlaylistDTO, PlaylistModel): ) -> (PlaylistDTO, PlaylistModel):
# Create a playlist and model # Create a playlist and model
playlist = repository.create_playlist(name=playlist_name, template_id=0) playlist = ds.create_playlist(name=playlist_name, template_id=0)
assert playlist assert playlist
model = playlistmodel.PlaylistModel(playlist.playlist_id, is_template=False) model = playlistmodel.PlaylistModel(playlist.playlist_id, is_template=False)
assert model assert model
@ -52,25 +52,25 @@ class MyTestCase(unittest.TestCase):
(playlist, model) = self.create_playlist_and_model(playlist_name) (playlist, model) = self.create_playlist_and_model(playlist_name)
# Create tracks # Create tracks
metadata1 = get_all_track_metadata(self.isa_path) metadata1 = get_all_track_metadata(self.isa_path)
self.track1 = repository.create_track(self.isa_path, metadata1) self.track1 = ds.create_track(self.isa_path, metadata1)
metadata2 = get_all_track_metadata(self.mom_path) metadata2 = get_all_track_metadata(self.mom_path)
self.track2 = repository.create_track(self.mom_path, metadata2) self.track2 = ds.create_track(self.mom_path, metadata2)
# Add tracks and header to playlist # Add tracks and header to playlist
self.row0 = repository.insert_row( self.row0 = ds.insert_row(
playlist.playlist_id, playlist.playlist_id,
row_number=0, row_number=0,
track_id=self.track1.track_id, track_id=self.track1.track_id,
note="track 1", note="track 1",
) )
self.row1 = repository.insert_row( self.row1 = ds.insert_row(
playlist.playlist_id, playlist.playlist_id,
row_number=1, row_number=1,
track_id=0, track_id=0,
note="Header row", note="Header row",
) )
self.row2 = repository.insert_row( self.row2 = ds.insert_row(
playlist.playlist_id, playlist.playlist_id,
row_number=2, row_number=2,
track_id=self.track2.track_id, track_id=self.track2.track_id,
@ -82,7 +82,7 @@ class MyTestCase(unittest.TestCase):
) -> (PlaylistDTO, PlaylistModel): ) -> (PlaylistDTO, PlaylistModel):
(playlist, model) = self.create_playlist_and_model(playlist_name) (playlist, model) = self.create_playlist_and_model(playlist_name)
for row_number in range(number_of_rows): for row_number in range(number_of_rows):
repository.insert_row( ds.insert_row(
playlist.playlist_id, row_number, None, str(row_number) playlist.playlist_id, row_number, None, str(row_number)
) )
@ -97,60 +97,60 @@ class MyTestCase(unittest.TestCase):
"""Add a track to a header row""" """Add a track to a header row"""
self.create_playlist_model_tracks("my playlist") self.create_playlist_model_tracks("my playlist")
repository.add_track_to_header(self.row1.playlistrow_id, self.track2.track_id) ds.add_track_to_header(self.row1.playlistrow_id, self.track2.track_id)
result = repository.get_playlist_row(self.row1.playlistrow_id) result = ds.get_playlist_row(self.row1.playlistrow_id)
assert result.track.track_id == self.track2.track_id assert result.track.track_id == self.track2.track_id
def test_create_track(self): def test_create_track(self):
metadata = get_all_track_metadata(self.isa_path) metadata = get_all_track_metadata(self.isa_path)
repository.create_track(self.isa_path, metadata) ds.create_track(self.isa_path, metadata)
results = repository.get_all_tracks() results = ds.get_all_tracks()
assert len(results) == 1 assert len(results) == 1
assert results[0].path == self.isa_path assert results[0].path == self.isa_path
def test_get_track_by_id(self): def test_get_track_by_id(self):
metadata = get_all_track_metadata(self.isa_path) metadata = get_all_track_metadata(self.isa_path)
dto = repository.create_track(self.isa_path, metadata) dto = ds.create_track(self.isa_path, metadata)
result = repository.track_by_id(dto.track_id) result = ds.track_by_id(dto.track_id)
assert result.path == self.isa_path assert result.path == self.isa_path
def test_get_track_by_artist(self): def test_get_track_by_artist(self):
metadata = get_all_track_metadata(self.isa_path) metadata = get_all_track_metadata(self.isa_path)
_ = repository.create_track(self.isa_path, metadata) _ = ds.create_track(self.isa_path, metadata)
metadata = get_all_track_metadata(self.mom_path) metadata = get_all_track_metadata(self.mom_path)
_ = repository.create_track(self.mom_path, metadata) _ = ds.create_track(self.mom_path, metadata)
result_isa = repository.tracks_by_artist(self.isa_artist) result_isa = ds.tracks_by_artist(self.isa_artist)
assert len(result_isa) == 1 assert len(result_isa) == 1
assert result_isa[0].artist == self.isa_artist assert result_isa[0].artist == self.isa_artist
result_mom = repository.tracks_by_artist(self.mom_artist) result_mom = ds.tracks_by_artist(self.mom_artist)
assert len(result_mom) == 1 assert len(result_mom) == 1
assert result_mom[0].artist == self.mom_artist assert result_mom[0].artist == self.mom_artist
def test_get_track_by_title(self): def test_get_track_by_title(self):
metadata_isa = get_all_track_metadata(self.isa_path) metadata_isa = get_all_track_metadata(self.isa_path)
_ = repository.create_track(self.isa_path, metadata_isa) _ = ds.create_track(self.isa_path, metadata_isa)
metadata_mom = get_all_track_metadata(self.mom_path) metadata_mom = get_all_track_metadata(self.mom_path)
_ = repository.create_track(self.mom_path, metadata_mom) _ = ds.create_track(self.mom_path, metadata_mom)
result_isa = repository.tracks_by_title(self.isa_title) result_isa = ds.tracks_by_title(self.isa_title)
assert len(result_isa) == 1 assert len(result_isa) == 1
assert result_isa[0].title == self.isa_title assert result_isa[0].title == self.isa_title
result_mom = repository.tracks_by_title(self.mom_title) result_mom = ds.tracks_by_title(self.mom_title)
assert len(result_mom) == 1 assert len(result_mom) == 1
assert result_mom[0].title == self.mom_title assert result_mom[0].title == self.mom_title
def test_tracks_get_all_tracks(self): def test_tracks_get_all_tracks(self):
self.create_playlist_model_tracks(playlist_name="test_track_get_all_tracks") self.create_playlist_model_tracks(playlist_name="test_track_get_all_tracks")
all_tracks = repository.get_all_tracks() all_tracks = ds.get_all_tracks()
assert len(all_tracks) == 2 assert len(all_tracks) == 2
def test_tracks_by_path(self): def test_tracks_by_path(self):
metadata_isa = get_all_track_metadata(self.isa_path) metadata_isa = get_all_track_metadata(self.isa_path)
_ = repository.create_track(self.isa_path, metadata_isa) _ = ds.create_track(self.isa_path, metadata_isa)
metadata_mom = get_all_track_metadata(self.mom_path) metadata_mom = get_all_track_metadata(self.mom_path)
_ = repository.create_track(self.mom_path, metadata_mom) _ = ds.create_track(self.mom_path, metadata_mom)
result_isa = repository.track_by_path(self.isa_path) result_isa = ds.track_by_path(self.isa_path)
assert result_isa.title == self.isa_title assert result_isa.title == self.isa_title
result_mom = repository.track_by_path(self.mom_path) result_mom = ds.track_by_path(self.mom_path)
assert result_mom.title == self.mom_title assert result_mom.title == self.mom_title
def test_move_rows_test1(self): def test_move_rows_test1(self):
@ -159,11 +159,11 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 10 number_of_rows = 10
(playlist, model) = self.create_rows("test_move_rows_test1", number_of_rows) (playlist, model) = self.create_rows("test_move_rows_test1", number_of_rows)
repository.move_rows([3], playlist.playlist_id, 5) ds.move_rows([3], playlist.playlist_id, 5)
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order = [] new_order = []
for row in repository.get_playlist_rows(playlist.playlist_id): for row in ds.playlistrows_by_playlist(playlist.playlist_id):
new_order.append(int(row.note)) new_order.append(int(row.note))
assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9] assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9]
@ -173,11 +173,11 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 10 number_of_rows = 10
(playlist, model) = self.create_rows("test_move_rows_test2", number_of_rows) (playlist, model) = self.create_rows("test_move_rows_test2", number_of_rows)
repository.move_rows([4], playlist.playlist_id, 3) ds.move_rows([4], playlist.playlist_id, 3)
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order = [] new_order = []
for row in repository.get_playlist_rows(playlist.playlist_id): for row in ds.playlistrows_by_playlist(playlist.playlist_id):
new_order.append(int(row.note)) new_order.append(int(row.note))
assert new_order == [0, 1, 2, 4, 3, 5, 6, 7, 8, 9] assert new_order == [0, 1, 2, 4, 3, 5, 6, 7, 8, 9]
@ -187,11 +187,11 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 10 number_of_rows = 10
(playlist, model) = self.create_rows("test_move_rows_test3", number_of_rows) (playlist, model) = self.create_rows("test_move_rows_test3", number_of_rows)
repository.move_rows([4], playlist.playlist_id, 2) ds.move_rows([4], playlist.playlist_id, 2)
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order = [] new_order = []
for row in repository.get_playlist_rows(playlist.playlist_id): for row in ds.playlistrows_by_playlist(playlist.playlist_id):
new_order.append(int(row.note)) new_order.append(int(row.note))
assert new_order == [0, 1, 4, 2, 3, 5, 6, 7, 8, 9] assert new_order == [0, 1, 4, 2, 3, 5, 6, 7, 8, 9]
@ -201,11 +201,11 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11 number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test4", number_of_rows) (playlist, model) = self.create_rows("test_move_rows_test4", number_of_rows)
repository.move_rows([1, 4, 5, 10], playlist.playlist_id, 8) ds.move_rows([1, 4, 5, 10], playlist.playlist_id, 8)
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order = [] new_order = []
for row in repository.get_playlist_rows(playlist.playlist_id): for row in ds.playlistrows_by_playlist(playlist.playlist_id):
new_order.append(int(row.note)) new_order.append(int(row.note))
assert new_order == [0, 2, 3, 6, 7, 8, 1, 4, 5, 10, 9] assert new_order == [0, 2, 3, 6, 7, 8, 1, 4, 5, 10, 9]
@ -215,11 +215,11 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11 number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test5", number_of_rows) (playlist, model) = self.create_rows("test_move_rows_test5", number_of_rows)
repository.move_rows([3, 6], playlist.playlist_id, 5) ds.move_rows([3, 6], playlist.playlist_id, 5)
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order = [] new_order = []
for row in repository.get_playlist_rows(playlist.playlist_id): for row in ds.playlistrows_by_playlist(playlist.playlist_id):
new_order.append(int(row.note)) new_order.append(int(row.note))
assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9, 10] assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9, 10]
@ -229,11 +229,11 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11 number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test6", number_of_rows) (playlist, model) = self.create_rows("test_move_rows_test6", number_of_rows)
repository.move_rows([3, 5, 6], playlist.playlist_id, 8) ds.move_rows([3, 5, 6], playlist.playlist_id, 8)
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order = [] new_order = []
for row in repository.get_playlist_rows(playlist.playlist_id): for row in ds.playlistrows_by_playlist(playlist.playlist_id):
new_order.append(int(row.note)) new_order.append(int(row.note))
assert new_order == [0, 1, 2, 4, 7, 8, 9, 10, 3, 5, 6] assert new_order == [0, 1, 2, 4, 7, 8, 9, 10, 3, 5, 6]
@ -243,11 +243,11 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11 number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test7", number_of_rows) (playlist, model) = self.create_rows("test_move_rows_test7", number_of_rows)
repository.move_rows([7, 8, 10], playlist.playlist_id, 5) ds.move_rows([7, 8, 10], playlist.playlist_id, 5)
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order = [] new_order = []
for row in repository.get_playlist_rows(playlist.playlist_id): for row in ds.playlistrows_by_playlist(playlist.playlist_id):
new_order.append(int(row.note)) new_order.append(int(row.note))
assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9] assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9]
@ -258,11 +258,11 @@ class MyTestCase(unittest.TestCase):
number_of_rows = 11 number_of_rows = 11
(playlist, model) = self.create_rows("test_move_rows_test8", number_of_rows) (playlist, model) = self.create_rows("test_move_rows_test8", number_of_rows)
repository.move_rows([0, 1, 2, 3], playlist.playlist_id, 0) ds.move_rows([0, 1, 2, 3], playlist.playlist_id, 0)
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order = [] new_order = []
for row in repository.get_playlist_rows(playlist.playlist_id): for row in ds.playlistrows_by_playlist(playlist.playlist_id):
new_order.append(int(row.note)) new_order.append(int(row.note))
assert new_order == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] assert new_order == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
@ -274,17 +274,17 @@ class MyTestCase(unittest.TestCase):
(playlist_src, model_src) = self.create_rows("src playlist", number_of_rows) (playlist_src, model_src) = self.create_rows("src playlist", number_of_rows)
(playlist_dst, model_dst) = self.create_rows("dst playlist", number_of_rows) (playlist_dst, model_dst) = self.create_rows("dst playlist", number_of_rows)
repository.move_rows( ds.move_rows(
rows_to_move, playlist_src.playlist_id, to_row, playlist_dst.playlist_id rows_to_move, playlist_src.playlist_id, to_row, playlist_dst.playlist_id
) )
# Check we have all rows and plr_rownums are correct # Check we have all rows and plr_rownums are correct
new_order_src = [] new_order_src = []
for row in repository.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)) new_order_src.append(int(row.note))
assert new_order_src == [0, 1, 3, 5, 7, 8, 9, 10] assert new_order_src == [0, 1, 3, 5, 7, 8, 9, 10]
new_order_dst = [] new_order_dst = []
for row in repository.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)) 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] assert new_order_dst == [0, 1, 2, 3, 4, 2, 4, 6, 5, 6, 7, 8, 9, 10]

View File

@ -20,12 +20,8 @@ import pytest
from pytestqt.plugin import QtBot # type: ignore from pytestqt.plugin import QtBot # type: ignore
# App imports # App imports
from app import musicmuster from app import ds, musicmuster
from app.models import ( from app.models import db
db,
Playlists,
Tracks,
)
from config import Config from config import Config
from file_importer import FileImporter from file_importer import FileImporter
@ -56,8 +52,7 @@ class MyTestCase(unittest.TestCase):
# Create a playlist for all tests # Create a playlist for all tests
playlist_name = "file importer playlist" playlist_name = "file importer playlist"
with db.Session() as session: playlist = ds.playlist_create(name=playlist_name, template_id=0)
playlist = Playlists(session=session, name=playlist_name, template_id=0)
cls.widget._open_playlist(playlist) cls.widget._open_playlist(playlist)
# Create our musicstore # Create our musicstore
@ -176,8 +171,7 @@ class MyTestCase(unittest.TestCase):
self.wait_for_workers() self.wait_for_workers()
# Check track was imported # Check track was imported
with db.Session() as session: tracks = ds.get_all_tracks()
tracks = Tracks.get_all(session)
assert len(tracks) == 1 assert len(tracks) == 1
track = tracks[0] track = tracks[0]
assert track.title == "I'm So Afraid" assert track.title == "I'm So Afraid"
@ -222,8 +216,7 @@ class MyTestCase(unittest.TestCase):
self.wait_for_workers() self.wait_for_workers()
# Check track was imported # Check track was imported
with db.Session() as session: tracks = ds.get_all_tracks()
tracks = Tracks.get_all(session)
assert len(tracks) == 2 assert len(tracks) == 2
track = tracks[1] track = tracks[1]
assert track.title == "The Lovecats" assert track.title == "The Lovecats"
@ -275,13 +268,12 @@ class MyTestCase(unittest.TestCase):
self.wait_for_workers() self.wait_for_workers()
# Check track was imported # Check track was imported
with db.Session() as session: tracks = ds.get_all_tracks()
tracks = Tracks.get_all(session)
assert len(tracks) == 2 assert len(tracks) == 2
track = tracks[1] track = tracks[1]
assert track.title == "The Lovecats" assert track.title == "The Lovecats"
assert track.artist == "The Cure" assert track.artist == "The Cure"
assert track.id == 2 assert track.track_id == 2
track_file = os.path.join( track_file = os.path.join(
self.musicstore, os.path.basename(test_track_path) self.musicstore, os.path.basename(test_track_path)
) )
@ -405,22 +397,19 @@ class MyTestCase(unittest.TestCase):
assert result[0] == new_destination # Validate return value assert result[0] == new_destination # Validate return value
# Check track was imported # Check track was imported
with db.Session() as session: tracks = ds.get_all_tracks()
tracks = Tracks.get_all(session)
assert len(tracks) == 3
track = tracks[2] track = tracks[2]
assert track.title == "The Lovecats" assert track.title == "The Lovecats"
assert track.artist == "The Cure" assert track.artist == "The Cure"
assert track.id == 3 assert track.track_id == 3
assert track.path == new_destination assert track.path == new_destination
assert os.path.exists(new_destination) assert os.path.exists(new_destination)
assert os.listdir(self.import_source) == [] assert os.listdir(self.import_source) == []
# Remove file so as not to interfere with later tests # Remove file so as not to interfere with later tests
session.delete(track) ds.delete(track)
tracks = Tracks.get_all(session) tracks = ds.get_all_tracks()
assert len(tracks) == 2 assert len(tracks) == 2
session.commit()
os.unlink(new_destination) os.unlink(new_destination)
assert not os.path.exists(new_destination) assert not os.path.exists(new_destination)
@ -474,13 +463,12 @@ class MyTestCase(unittest.TestCase):
self.wait_for_workers() self.wait_for_workers()
# Check track was imported # Check track was imported
with db.Session() as session: tracks = ds.get_all_tracks()
tracks = Tracks.get_all(session)
assert len(tracks) == 2 assert len(tracks) == 2
track = tracks[1] track = tracks[1]
assert track.title == "The Lovecats xyz" assert track.title == "The Lovecats xyz"
assert track.artist == "The Cure" assert track.artist == "The Cure"
assert track.id == 2 assert track.track_id == 2
track_file = os.path.join( track_file = os.path.join(
self.musicstore, os.path.basename(test_track_path) self.musicstore, os.path.basename(test_track_path)
) )

View File

@ -64,9 +64,9 @@ class TestMMHelpers(unittest.TestCase):
today_at_11 = dt.datetime.now().replace(hour=11, minute=0) today_at_11 = dt.datetime.now().replace(hour=11, minute=0)
assert get_relative_date(today_at_10, today_at_11) == "Today 10:00" assert get_relative_date(today_at_10, today_at_11) == "Today 10:00"
eight_days_ago = today_at_10 - dt.timedelta(days=8) eight_days_ago = today_at_10 - dt.timedelta(days=8)
assert get_relative_date(eight_days_ago, today_at_11) == "1 week, 1 day" assert get_relative_date(eight_days_ago, today_at_11) == "1w, 1d"
sixteen_days_ago = today_at_10 - dt.timedelta(days=16) sixteen_days_ago = today_at_10 - dt.timedelta(days=16)
assert get_relative_date(sixteen_days_ago, today_at_11) == "2 weeks, 2 days" assert get_relative_date(sixteen_days_ago, today_at_11) == "2w, 2d"
def test_leading_silence(self): def test_leading_silence(self):
test_track_path = "testdata/isa.mp3" test_track_path = "testdata/isa.mp3"

View File

@ -8,12 +8,15 @@ from PyQt6.QtCore import Qt, QModelIndex
# App imports # App imports
from app.helpers import get_all_track_metadata from app.helpers import get_all_track_metadata
from app import playlistmodel from app import ds, playlistmodel
from app.models import ( from app.models import (
db, db,
Playlists, Playlists,
Tracks, Tracks,
) )
from classes import (
TrackAndPlaylist,
)
class TestMMMiscTracks(unittest.TestCase): class TestMMMiscTracks(unittest.TestCase):
@ -33,18 +36,13 @@ class TestMMMiscTracks(unittest.TestCase):
db.create_all() db.create_all()
# Create a playlist and model # Create a playlist and model
with db.Session() as session: self.playlist = ds.playlist_create(PLAYLIST_NAME, template_id=0)
self.playlist = Playlists(session, PLAYLIST_NAME, template_id=0) self.model = playlistmodel.PlaylistModel(self.playlist.playlist_id, is_template=False)
self.model = playlistmodel.PlaylistModel(self.playlist.id, is_template=False)
for row in range(len(self.test_tracks)): for row in range(len(self.test_tracks)):
track_path = self.test_tracks[row % len(self.test_tracks)] track_path = self.test_tracks[row % len(self.test_tracks)]
track = Tracks(session, **get_all_track_metadata(track_path)) track = ds.track_create(**get_all_track_metadata(track_path))
self.model.insert_row( self.model.insert_row(track_id=track.id, note=f"{row=}")
proposed_row_number=row, track_id=track.id, note=f"{row=}"
)
session.commit()
def tearDown(self): def tearDown(self):
db.drop_all() db.drop_all()
@ -62,8 +60,11 @@ class TestMMMiscTracks(unittest.TestCase):
START_ROW = 0 START_ROW = 0
END_ROW = 2 END_ROW = 2
self.model.insert_row(proposed_row_number=START_ROW, note="start+") # Fake selected row in model
self.model.insert_row(proposed_row_number=END_ROW, note="-") self.model.selected_rows = [self.model.playlist_rows[START_ROW]]
self.model.insert_row(note="start+")
self.model.selected_rows = [self.model.playlist_rows[END_ROW]]
self.model.insert_row(note="-")
prd = self.model.playlist_rows[START_ROW] prd = self.model.playlist_rows[START_ROW]
qv_value = self.model._display_role( qv_value = self.model._display_role(
@ -92,8 +93,7 @@ class TestMMMiscNoPlaylist(unittest.TestCase):
def test_insert_track_new_playlist(self): def test_insert_track_new_playlist(self):
# insert a track into a new playlist # insert a track into a new playlist
with db.Session() as session: playlist = ds.playlist_create(self.PLAYLIST_NAME, template_id=0)
playlist = Playlists(session, self.PLAYLIST_NAME, template_id=0)
# Create a model # Create a model
model = playlistmodel.PlaylistModel(playlist.id, is_template=False) model = playlistmodel.PlaylistModel(playlist.id, is_template=False)
# test repr # test repr
@ -101,17 +101,15 @@ class TestMMMiscNoPlaylist(unittest.TestCase):
track_path = self.test_tracks[0] track_path = self.test_tracks[0]
metadata = get_all_track_metadata(track_path) metadata = get_all_track_metadata(track_path)
track = Tracks(session, **metadata) track = ds.track_create(metadata)
model.insert_row(proposed_row_number=0, track_id=track.id) model.insert_row(track_id=track.id)
prd = model.playlist_rows[model.rowCount() - 1] prd = model.playlist_rows[model.rowCount() - 1]
# test repr # test repr
_ = str(prd) _ = str(prd)
assert ( assert (
model._edit_role( model._edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd)
model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd
)
== metadata["title"] == metadata["title"]
) )
@ -123,13 +121,10 @@ class TestMMMiscRowMove(unittest.TestCase):
def setUp(self): def setUp(self):
db.create_all() db.create_all()
with db.Session() as session: self.playlist = ds.playlist_create(self.PLAYLIST_NAME, template_id=0)
self.playlist = Playlists(session, self.PLAYLIST_NAME, template_id=0)
self.model = playlistmodel.PlaylistModel(self.playlist.id, is_template=False) self.model = playlistmodel.PlaylistModel(self.playlist.id, is_template=False)
for row in range(self.ROWS_TO_CREATE): for row in range(self.ROWS_TO_CREATE):
self.model.insert_row(proposed_row_number=row, note=str(row)) self.model.insert_row(note=str(row))
session.commit()
def tearDown(self): def tearDown(self):
db.drop_all() db.drop_all()
@ -140,7 +135,7 @@ class TestMMMiscRowMove(unittest.TestCase):
note_text = "test text" note_text = "test text"
assert self.model.rowCount() == self.ROWS_TO_CREATE assert self.model.rowCount() == self.ROWS_TO_CREATE
self.model.insert_row(proposed_row_number=None, note=note_text) self.model.insert_row(note=note_text)
assert self.model.rowCount() == self.ROWS_TO_CREATE + 1 assert self.model.rowCount() == self.ROWS_TO_CREATE + 1
prd = self.model.playlist_rows[self.model.rowCount() - 1] prd = self.model.playlist_rows[self.model.rowCount() - 1]
# Test against edit_role because display_role for headers is # Test against edit_role because display_role for headers is
@ -158,7 +153,10 @@ class TestMMMiscRowMove(unittest.TestCase):
note_text = "test text" note_text = "test text"
insert_row = 6 insert_row = 6
self.model.insert_row(proposed_row_number=insert_row, note=note_text) # 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 assert self.model.rowCount() == self.ROWS_TO_CREATE + 1
prd = self.model.playlist_rows[insert_row] prd = self.model.playlist_rows[insert_row]
# Test against edit_role because display_role for headers is # Test against edit_role because display_role for headers is
@ -174,11 +172,16 @@ class TestMMMiscRowMove(unittest.TestCase):
note_text = "test text" note_text = "test text"
insert_row = 6 insert_row = 6
self.model.insert_row(proposed_row_number=insert_row, note=note_text) self.model.insert_row(note=note_text)
assert self.model.rowCount() == self.ROWS_TO_CREATE + 1 assert self.model.rowCount() == self.ROWS_TO_CREATE + 1
# Fake selected row in model
self.model.selected_rows = [self.model.playlist_rows[insert_row]]
prd = self.model.playlist_rows[1] prd = self.model.playlist_rows[1]
self.model.add_track_to_header(insert_row, prd.track_id) self.model.add_track_to_header(
TrackAndPlaylist(playlist_id=self.model.playlist_id, track_id=prd.track_id)
)
def test_reverse_row_groups_one_row(self): def test_reverse_row_groups_one_row(self):
rows_to_move = [3] rows_to_move = [3]
@ -198,17 +201,19 @@ class TestMMMiscRowMove(unittest.TestCase):
def test_move_one_row_between_playlists_to_end(self): def test_move_one_row_between_playlists_to_end(self):
from_rows = [3] from_rows = [3]
to_row = self.ROWS_TO_CREATE to_row = self.ROWS_TO_CREATE
destination_playlist = "destination" destination_playlist_name = "destination"
model_src = self.model model_src = self.model
with db.Session() as session: playlist_dst = ds.playlist_create(destination_playlist_name, template_id=0)
playlist_dst = Playlists(session, destination_playlist, template_id=0) model_dst = playlistmodel.PlaylistModel(
model_dst = playlistmodel.PlaylistModel(playlist_dst.id, is_template=False) playlist_dst.playlist_id, is_template=False
)
for row in range(self.ROWS_TO_CREATE): for row in range(self.ROWS_TO_CREATE):
model_dst.insert_row(proposed_row_number=row, note=str(row)) model_dst.insert_row(note=str(row))
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id) ds.playlist_move_rows(
model_dst.refresh_data(session) 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_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows) assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
@ -219,17 +224,19 @@ class TestMMMiscRowMove(unittest.TestCase):
def test_move_one_row_between_playlists_to_middle(self): def test_move_one_row_between_playlists_to_middle(self):
from_rows = [3] from_rows = [3]
to_row = 2 to_row = 2
destination_playlist = "destination" destination_playlist_name = "destination"
model_src = self.model model_src = self.model
with db.Session() as session: playlist_dst = ds.playlist_create(destination_playlist_name, template_id=0)
playlist_dst = Playlists(session, destination_playlist, template_id=0) model_dst = playlistmodel.PlaylistModel(
model_dst = playlistmodel.PlaylistModel(playlist_dst.id, is_template=False) playlist_dst.playlist_id, is_template=False
)
for row in range(self.ROWS_TO_CREATE): for row in range(self.ROWS_TO_CREATE):
model_dst.insert_row(proposed_row_number=row, note=str(row)) model_dst.insert_row(note=str(row))
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id) ds.playlist_move_rows(
model_dst.refresh_data(session) from_rows, self.playlist.playlist_id, to_row, playlist_dst.playlist_id
)
# Check the rows of the destination model # Check the rows of the destination model
row_notes = [] row_notes = []
@ -246,17 +253,20 @@ class TestMMMiscRowMove(unittest.TestCase):
def test_move_multiple_rows_between_playlists_to_end(self): def test_move_multiple_rows_between_playlists_to_end(self):
from_rows = [1, 3, 4] from_rows = [1, 3, 4]
to_row = 2 to_row = 2
destination_playlist = "destination" destination_playlist_name = "destination"
model_src = self.model 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(proposed_row_number=row, note=str(row))
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id) playlist_dst = ds.playlist_create(destination_playlist_name, template_id=0)
model_dst.refresh_data(session) 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 # Check the rows of the destination model
row_notes = [] row_notes = []