WIP V3: move row initial tests working
More tests to write
This commit is contained in:
parent
da658f0ae3
commit
86a1678f41
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
21
conftest.py
21
conftest.py
@ -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)
|
||||
|
||||
@ -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
105
test_playlistmodel.py
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user