WIP V3: move row initial tests working

More tests to write
This commit is contained in:
Keith Edmunds 2023-10-24 20:48:28 +01:00
parent da658f0ae3
commit 86a1678f41
7 changed files with 238 additions and 37 deletions

View File

@ -15,19 +15,6 @@ else:
dbname = MYSQL_CONNECT.split("/")[-1]
log.debug(f"Database: {dbname}")
# MM_ENV = os.environ.get('MM_ENV', 'PRODUCTION')
# testing = False
# if MM_ENV == 'TESTING':
# dbname = os.environ.get('MM_TESTING_DBNAME', 'musicmuster_testing')
# dbuser = os.environ.get('MM_TESTING_DBUSER', 'musicmuster_testing')
# dbpw = os.environ.get('MM_TESTING_DBPW', 'musicmuster_testing')
# dbhost = os.environ.get('MM_TESTING_DBHOST', 'localhost')
# testing = True
# else:
# raise ValueError(f"Unknown MusicMuster environment: {MM_ENV=}")
#
# MYSQL_CONNECT = f"mysql+mysqldb://{dbuser}:{dbpw}@{dbhost}/{dbname}"
engine = create_engine(
MYSQL_CONNECT,
echo=Config.DISPLAY_SQL,

View File

@ -123,13 +123,7 @@ class NoteColours(Base):
Return all records
"""
return (
session.execute(
select(cls)
)
.scalars()
.all()
)
return session.execute(select(cls)).scalars().all()
@staticmethod
def get_colour(session: scoped_session, text: str) -> Optional[str]:
@ -419,9 +413,9 @@ class PlaylistRows(Base):
self,
session: scoped_session,
playlist_id: int,
track_id: Optional[int],
row_number: int,
note: str = "",
track_id: Optional[int] = None,
) -> None:
"""Create PlaylistRows object"""
@ -454,7 +448,13 @@ class PlaylistRows(Base):
)
for plr in src_rows:
PlaylistRows(session, dst_id, plr.track_id, plr.plr_rownum, plr.note)
PlaylistRows(
session=session,
playlist_id=dst_id,
row_number=plr.plr_rownum,
note=plr.note,
track_id=plr.track_id,
)
@staticmethod
def delete_higher_rows(

View File

@ -1,6 +1,8 @@
from datetime import datetime
from enum import auto, Enum
from typing import Optional, TYPE_CHECKING
from typing import List, Optional, TYPE_CHECKING
from dbconfig import scoped_session, Session
from PyQt6.QtCore import (
QAbstractTableModel,
@ -17,8 +19,6 @@ from PyQt6.QtGui import (
from config import Config
from dbconfig import Session
from helpers import (
file_is_unreadable,
)
@ -282,6 +282,110 @@ class PlaylistModel(QAbstractTableModel):
return QVariant()
def insert_row(self, session: scoped_session, row_number: int) -> int:
"""
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.
"""
if row_number > len(self.playlist_rows) or row_number == -1:
new_row_number = len(self.playlist_rows) + 1
elif row_number < 0:
raise ValueError(
f"playlistmodel.insert_row, invalid row number ({row_number})"
)
else:
new_row_number = 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
)
)
# Refresh rows
self.invalidate_rows(modified_rows)
return new_row_number
def invalidate_rows(self, modified_rows: List[int]) -> None:
"""
Signal to view to refresh invlidated rows
"""
for modified_row in modified_rows:
self.dataChanged.emit(
self.index(modified_row, 0),
self.index(modified_row, self.columnCount()),
)
def move_rows(self, from_rows: List[int], to_row: int) -> None:
"""
Move the playlist rows given to to_row and below.
"""
# New thinking:
# Move relocated rows to correct place in mirror array
# Copy souurce to mirror around them
# Move mirror to original
# Fixup plr rownumbers and update db and display
# Signal rows have changed
# Prep
# modified_rows: List[int] = []
# moving_rows: dict[int, PlaylistRowData] = {}
new_playlist_rows: dict[int, PlaylistRowData] = {}
# Move the from_row records from the playlist_rows dict to the
# new_playlist_rows dict
next_to_row = to_row
for from_row in from_rows:
new_playlist_rows[next_to_row] = self.playlist_rows[from_row]
del self.playlist_rows[from_row]
next_to_row += 1
# Move the remaining rows to the gaps in new_playlist_rows
new_row = 0
for old_row in self.playlist_rows.keys():
# Find next gap
while new_row in new_playlist_rows:
new_row += 1
new_playlist_rows[new_row] = self.playlist_rows[old_row]
new_row += 1
# Make copy of rows live
self.playlist_rows = new_playlist_rows
# Update PlaylistRows table and notify display of rows that
# moved
with Session() as session:
for idx in range(len(self.playlist_rows)):
if self.playlist_rows[idx].plr_rownum == idx:
continue
# Row number in this row is incorred. Fix it in
# database:
plr = session.get(PlaylistRows, self.playlist_rows[idx].plrid)
if not plr:
print(f"\nCan't find plr in playlistmodel:move_rows {idx=}")
continue
plr.plr_rownum = idx
# Fix in self.playlist_rows
self.playlist_rows[idx].plr_rownum = idx
# Update display
self.invalidate_rows([idx])
print(f"Fixup {idx=}")
def refresh_data(self):
"""Populate dicts for data calls"""
@ -294,6 +398,7 @@ 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:

View File

@ -227,7 +227,7 @@ class PlaylistTab(QTableView):
and 0 <= max(from_rows) <= self.model().rowCount()
and 0 <= to_row <= self.model().rowCount()
):
print(f"move_rows({from_rows=}, {to_row=})")
self.model().move_rows(from_rows, to_row)
event.accept()
super().dropEvent(event)
@ -605,7 +605,7 @@ class PlaylistTab(QTableView):
# """
# row_number = self.get_new_row_number()
# plr = PlaylistRows(session, self.playlist_id, None, row_number, note)
# TODO: check arg order plr = PlaylistRows(session, self.playlist_id, None, row_number, note)
# self.insert_row(session, plr)
# self._set_row_header_text(session, row_number, note)
# self.save_playlist(session)
@ -694,7 +694,7 @@ class PlaylistTab(QTableView):
# return self._move_row(session, existing_plr, row_number)
# # Build playlist_row object
# plr = PlaylistRows(session, self.playlist_id, track.id, row_number, note)
# plr = TODO: check arg order PlaylistRows(session, self.playlist_id, track.id, row_number, note)
# self.insert_row(session, plr)
# self.save_playlist(session)
# self._update_start_end_times(session)

View File

@ -8,30 +8,33 @@ from sqlalchemy.orm import scoped_session, sessionmaker
from app.models import Base, Tracks
DB_CONNECTION = \
"mysql+mysqldb://musicmuster_testing:musicmuster_testing@localhost/dev_musicmuster_testing"
@pytest.fixture(scope="session")
def db_engine():
engine = create_engine(
"mysql+mysqldb://musicmuster_testing:musicmuster_testing@localhost/dev_musicmuster_testing"
)
engine = create_engine(DB_CONNECTION, isolation_level="READ COMMITTED")
Base.metadata.create_all(engine)
yield engine
engine.dispose()
@pytest.fixture(scope='function')
def session(db_engine):
@pytest.fixture(scope="function")
def Session(db_engine):
connection = db_engine.connect()
transaction = connection.begin()
Session = sessionmaker(bind=connection)
session = scoped_session(Session)
sm = sessionmaker(bind=connection)
session = scoped_session(sm)
# print(f"PyTest SqlA: session acquired [{hex(id(session))}]")
yield session
# print(f" PyTest SqlA: session released and cleaned up [{hex(id(session))}]")
session.remove()
transaction.rollback()
connection.close()
@pytest.fixture(scope='function')
@pytest.fixture(scope="function")
def track1(session):
track_path = "testdata/isa.mp3"
metadata = helpers.get_file_metadata(track_path)
@ -39,7 +42,7 @@ def track1(session):
return track
@pytest.fixture(scope='function')
@pytest.fixture(scope="function")
def track2(session):
track_path = "testdata/mom.mp3"
metadata = helpers.get_file_metadata(track_path)

View File

@ -55,6 +55,7 @@ mypy_path = "/home/kae/git/musicmuster/app"
[tool.pytest.ini_options]
addopts = "--exitfirst --showlocals --capture=no"
pythonpath = [".", "app"]
filterwarnings = "ignore:'audioop' is deprecated"
[tool.vulture]
exclude = ["migrations", "app/ui", "archive"]

105
test_playlistmodel.py Normal file
View File

@ -0,0 +1,105 @@
from app.models import (
Playlists,
)
from app import playlistmodel
from dbconfig import scoped_session
def create_model_with_playlist_rows(
session: scoped_session, rows: int
) -> "playlistmodel.PlaylistModel":
playlist = Playlists(session, "test playlist")
# Create a model
model = playlistmodel.PlaylistModel(playlist.id, None)
for row in range(rows):
newrow = model.insert_row(session, row)
model.playlist_rows[newrow].note = str(newrow)
session.commit()
return model
def test_insert_row(monkeypatch, Session):
monkeypatch.setattr(playlistmodel, "Session", Session)
# Create a playlist
with Session() as session:
playlist = Playlists(Session, "test playlist")
# Create a model
model = playlistmodel.PlaylistModel(playlist.id, None)
assert model.rowCount() == 0
model.insert_row(session, 0)
assert model.rowCount() == 1
def test_insert_high_row(monkeypatch, Session):
monkeypatch.setattr(playlistmodel, "Session", Session)
# Create a playlist
with Session() as session:
playlist = Playlists(Session, "test playlist")
# Create a model
model = playlistmodel.PlaylistModel(playlist.id, None)
assert model.rowCount() == 0
model.insert_row(session, 5)
assert model.rowCount() == 1
def test_11_row_playlist(monkeypatch, Session):
# Create multirow playlist
monkeypatch.setattr(playlistmodel, "Session", Session)
with Session() as session:
model = create_model_with_playlist_rows(session, 11)
assert model.rowCount() == 11
assert max(model.playlist_rows.keys()) == 10
for row in range(model.rowCount()):
assert row in model.playlist_rows
assert model.playlist_rows[row].plr_rownum == row
def test_move_rows_test2(monkeypatch, Session):
# move row 3 to row 5
monkeypatch.setattr(playlistmodel, "Session", Session)
with Session() as session:
model = create_model_with_playlist_rows(session, 11)
model.move_rows([3], 5)
# Check we have all rows and plr_rownums are correct
for row in range(model.rowCount()):
assert row in model.playlist_rows
assert model.playlist_rows[row].plr_rownum == row
if row not in [3, 4, 5]:
assert model.playlist_rows[row].note == str(row)
elif row == 3:
assert model.playlist_rows[row].note == str(4)
elif row == 4:
assert model.playlist_rows[row].note == str(5)
elif row == 5:
assert model.playlist_rows[row].note == str(3)
# def test_move_rows_test3(Session):
# # move row 4 to row 3
# pass
# def test_move_rows_test4(Session):
# # move row 4 to row 2
# pass
# def test_move_rows_test5(Session):
# # move rows [1, 4, 5, 10] → 8
# pass
# def test_move_rows_test6(Session):
# # move rows [3, 6] → 5
# pass
# def test_move_rows_test7(Session):
# # move rows [3, 5, 6] → 8
# pass
# def test_move_rows_test8(Session):
# # move rows [7, 8, 10] → 5
# pass