From 858c86d9075e85245a18cc4349ddb321db01859f Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Wed, 25 Oct 2023 22:17:52 +0100 Subject: [PATCH] test_insert_header_row passes --- app/musicmuster.py | 25 +++++++++++------ app/playlistmodel.py | 65 ++++++++++++++++++++++++++++++------------- app/playlists.py | 12 ++++++++ test_playlistmodel.py | 24 ++++++++++++++-- 4 files changed, 96 insertions(+), 30 deletions(-) diff --git a/app/musicmuster.py b/app/musicmuster.py index 6252209..646535e 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -22,6 +22,8 @@ from typing import ( Sequence, ) +from playlistmodel import PlaylistModel + from sqlalchemy import text from PyQt6.QtCore import ( @@ -1038,9 +1040,11 @@ class Window(QMainWindow, Ui_MainWindow): """Show dialog box to enter header text and add to playlist""" try: - playlist_tab = self.visible_playlist_tab() + model = cast(PlaylistModel, self.visible_playlist_tab().model()) + if model is None: + return except AttributeError: - # Just return if there's no visible playlist tab + # Just return if there's no visible playlist tab model return # Get header text @@ -1050,9 +1054,10 @@ class Window(QMainWindow, Ui_MainWindow): dlg.resize(500, 100) ok = dlg.exec() if ok: - with Session() as session: - playlist_tab.insert_header(session, dlg.textValue()) - playlist_tab.save_playlist(session) + model.insert_header_row( + self.visible_playlist_tab().get_selected_row_number(), + dlg.textValue() + ) def insert_track(self) -> None: """Show dialog box to select and add track from database""" @@ -1513,20 +1518,24 @@ class Window(QMainWindow, Ui_MainWindow): visible_playlist_id = self.visible_playlist_tab().playlist_id # Get row number of duplicate rows - sql = text(f""" + sql = text( + f""" SELECT max(plr_rownum) FROM playlist_rows WHERE playlist_id = {visible_playlist_id} AND track_id != 0 GROUP BY track_id HAVING count(id) > 1 - """) + """ + ) with Session() as session: row_numbers = [int(a) for a in session.execute(sql).scalars().all()] if row_numbers: self.visible_playlist_tab().select_rows(row_numbers) - self.statusbar.showMessage(f"{len(row_numbers)} duplicate rows selected", 10000) + self.statusbar.showMessage( + f"{len(row_numbers)} duplicate rows selected", 10000 + ) def select_next_row(self) -> None: """Select next or first row in playlist""" diff --git a/app/playlistmodel.py b/app/playlistmodel.py index def80ab..462478e 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -282,42 +282,72 @@ class PlaylistModel(QAbstractTableModel): return QVariant() - def insert_row(self, session: scoped_session, row_number: int) -> int: + def insert_header_row(self, row_number: Optional[int], text: str) -> Optional[int]: + """ + Insert a header row. Return row number or None if insertion failed. + """ + + with Session() as session: + prd = self._insert_row(session, row_number) + # Update playlist_rows + prd.note = text + # Get row from db and update + plr = session.get(PlaylistRows, prd.plrid) + if plr: + plr.note = text + self.refresh_row(session, plr.plr_rownum) + self.invalidate_row(plr.plr_rownum) + return plr.plr_rownum + + return None + + def _insert_row( + self, session: scoped_session, row_number: Optional[int] + ) -> PlaylistRowData: """ Make space for a row at row_number. If row_number is greater than length of list plus 1, or if row number is -1, put row at end of list. - Return new row number that has an empty, valid entry in self.playlist_rows. + Return the new PlaylistData structure """ - if row_number > len(self.playlist_rows) or row_number == -1: - new_row_number = len(self.playlist_rows) + 1 + modified_rows: List[int] = [] + + if row_number is None or row_number > len(self.playlist_rows): + new_row_number = len(self.playlist_rows) elif row_number < 0: raise ValueError( f"playlistmodel.insert_row, invalid row number ({row_number})" ) else: new_row_number = row_number + modified_rows.append(new_row_number) # Move rows below new row down - modified_rows: List[int] = [] for i in reversed(range(new_row_number, len(self.playlist_rows))): self.playlist_rows[i + 1] = self.playlist_rows[i] self.playlist_rows[i + 1].plr_rownum += 1 modified_rows.append(i + 1) - # Replace old row - self.playlist_rows[new_row_number] = PlaylistRowData( - PlaylistRows( - session=session, playlist_id=self.playlist_id, row_number=new_row_number - ) + # Insert new row, possibly replace old row + plr = PlaylistRows( + session=session, playlist_id=self.playlist_id, row_number=new_row_number ) + prd = PlaylistRowData(plr) + # Add row to playlist_rows + self.playlist_rows[new_row_number] = prd - # Refresh rows - self.invalidate_rows(modified_rows) + return prd - return new_row_number + def invalidate_row(self, modified_row: int) -> None: + """ + Signal to view to refresh invlidated row + """ + + self.dataChanged.emit( + self.index(modified_row, 0), self.index(modified_row, self.columnCount()) + ) def invalidate_rows(self, modified_rows: List[int]) -> None: """ @@ -325,10 +355,7 @@ class PlaylistModel(QAbstractTableModel): """ for modified_row in modified_rows: - self.dataChanged.emit( - self.index(modified_row, 0), - self.index(modified_row, self.columnCount()), - ) + self.invalidate_row(modified_row) def move_rows(self, from_rows: List[int], to_row: int) -> None: """ @@ -379,12 +406,13 @@ class PlaylistModel(QAbstractTableModel): # Fix in self.playlist_rows self.playlist_rows[idx].plr_rownum = idx # Update display - self.invalidate_rows([idx]) + self.invalidate_row(idx) def refresh_data(self): """Populate dicts for data calls""" # Populate self.playlist_rows with playlist data + self.playlist_rows.clear() with Session() as session: for p in PlaylistRows.deep_rows(session, self.playlist_id): self.playlist_rows[p.plr_rownum] = PlaylistRowData(p) @@ -393,7 +421,6 @@ class PlaylistModel(QAbstractTableModel): """Populate dict for one row for data calls""" p = PlaylistRows.deep_row(session, self.playlist_id, row_number) - self.playlist_rows.clear() self.playlist_rows[p.plr_rownum] = PlaylistRowData(p) def rowCount(self, index: QModelIndex = QModelIndex()) -> int: diff --git a/app/playlists.py b/app/playlists.py index 72ec598..52f8b76 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -522,6 +522,18 @@ class PlaylistTab(QTableView): self.clearSelection() # self.setDragEnabled(False) + def get_selected_row_number(self) -> Optional[int]: + """ + Return the selected row number or None if none selected. + """ + + sm = self.selectionModel() + if sm and sm.hasSelection(): + index = sm.currentIndex() + if index.isValid(): + return index.row() + return None + # def get_new_row_number(self) -> int: # """ # Return the selected row or the row count if no row selected diff --git a/test_playlistmodel.py b/test_playlistmodel.py index 5513fba..76f36bc 100644 --- a/test_playlistmodel.py +++ b/test_playlistmodel.py @@ -12,7 +12,8 @@ def create_model_with_playlist_rows( # Create a model model = playlistmodel.PlaylistModel(playlist.id, None) for row in range(rows): - newrow = model.insert_row(session, row) + plr = model._insert_row(session, row) + newrow = plr.plr_rownum model.playlist_rows[newrow].note = str(newrow) session.commit() @@ -27,7 +28,7 @@ def test_insert_row(monkeypatch, Session): # Create a model model = playlistmodel.PlaylistModel(playlist.id, None) assert model.rowCount() == 0 - model.insert_row(session, 0) + model._insert_row(session, 0) assert model.rowCount() == 1 @@ -39,7 +40,7 @@ def test_insert_high_row(monkeypatch, Session): # Create a model model = playlistmodel.PlaylistModel(playlist.id, None) assert model.rowCount() == 0 - model.insert_row(session, 5) + model._insert_row(session, 5) assert model.rowCount() == 1 @@ -189,3 +190,20 @@ def test_move_rows_test8(monkeypatch, Session): assert model.playlist_rows[row].plr_rownum == row new_order.append(int(model.playlist_rows[row].note)) assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9] + + +def test_insert_header_row(monkeypatch, Session): + # insert header row + + monkeypatch.setattr(playlistmodel, "Session", Session) + note_text = "test text" + + with Session() as session: + model = create_model_with_playlist_rows(session, 11) + row_number = model.insert_header_row(None, note_text) + session.flush() + assert model.rowCount() == row_number + 1 + prd = model.playlist_rows[row_number] + # Test against edit_role because display_role for headers is + # handled differently (sets up row span) + assert model.edit_role(row_number, playlistmodel.Col.NOTE.value, prd) == note_text