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]
|
dbname = MYSQL_CONNECT.split("/")[-1]
|
||||||
log.debug(f"Database: {dbname}")
|
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(
|
engine = create_engine(
|
||||||
MYSQL_CONNECT,
|
MYSQL_CONNECT,
|
||||||
echo=Config.DISPLAY_SQL,
|
echo=Config.DISPLAY_SQL,
|
||||||
|
|||||||
@ -123,13 +123,7 @@ class NoteColours(Base):
|
|||||||
Return all records
|
Return all records
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return session.execute(select(cls)).scalars().all()
|
||||||
session.execute(
|
|
||||||
select(cls)
|
|
||||||
)
|
|
||||||
.scalars()
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_colour(session: scoped_session, text: str) -> Optional[str]:
|
def get_colour(session: scoped_session, text: str) -> Optional[str]:
|
||||||
@ -419,9 +413,9 @@ class PlaylistRows(Base):
|
|||||||
self,
|
self,
|
||||||
session: scoped_session,
|
session: scoped_session,
|
||||||
playlist_id: int,
|
playlist_id: int,
|
||||||
track_id: Optional[int],
|
|
||||||
row_number: int,
|
row_number: int,
|
||||||
note: str = "",
|
note: str = "",
|
||||||
|
track_id: Optional[int] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create PlaylistRows object"""
|
"""Create PlaylistRows object"""
|
||||||
|
|
||||||
@ -454,7 +448,13 @@ class PlaylistRows(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for plr in src_rows:
|
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
|
@staticmethod
|
||||||
def delete_higher_rows(
|
def delete_higher_rows(
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import auto, Enum
|
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 (
|
from PyQt6.QtCore import (
|
||||||
QAbstractTableModel,
|
QAbstractTableModel,
|
||||||
@ -17,8 +19,6 @@ from PyQt6.QtGui import (
|
|||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
from dbconfig import Session
|
|
||||||
|
|
||||||
from helpers import (
|
from helpers import (
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
)
|
)
|
||||||
@ -282,6 +282,110 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return QVariant()
|
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):
|
def refresh_data(self):
|
||||||
"""Populate dicts for data calls"""
|
"""Populate dicts for data calls"""
|
||||||
|
|
||||||
@ -294,6 +398,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
"""Populate dict for one row for data calls"""
|
"""Populate dict for one row for data calls"""
|
||||||
|
|
||||||
p = PlaylistRows.deep_row(session, self.playlist_id, row_number)
|
p = PlaylistRows.deep_row(session, self.playlist_id, row_number)
|
||||||
|
self.playlist_rows.clear()
|
||||||
self.playlist_rows[p.plr_rownum] = PlaylistRowData(p)
|
self.playlist_rows[p.plr_rownum] = PlaylistRowData(p)
|
||||||
|
|
||||||
def rowCount(self, index: QModelIndex = QModelIndex()) -> int:
|
def rowCount(self, index: QModelIndex = QModelIndex()) -> int:
|
||||||
|
|||||||
@ -227,7 +227,7 @@ class PlaylistTab(QTableView):
|
|||||||
and 0 <= max(from_rows) <= self.model().rowCount()
|
and 0 <= max(from_rows) <= self.model().rowCount()
|
||||||
and 0 <= to_row <= 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()
|
event.accept()
|
||||||
super().dropEvent(event)
|
super().dropEvent(event)
|
||||||
|
|
||||||
@ -605,7 +605,7 @@ class PlaylistTab(QTableView):
|
|||||||
# """
|
# """
|
||||||
|
|
||||||
# row_number = self.get_new_row_number()
|
# 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.insert_row(session, plr)
|
||||||
# self._set_row_header_text(session, row_number, note)
|
# self._set_row_header_text(session, row_number, note)
|
||||||
# self.save_playlist(session)
|
# self.save_playlist(session)
|
||||||
@ -694,7 +694,7 @@ class PlaylistTab(QTableView):
|
|||||||
# return self._move_row(session, existing_plr, row_number)
|
# return self._move_row(session, existing_plr, row_number)
|
||||||
|
|
||||||
# # Build playlist_row object
|
# # 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.insert_row(session, plr)
|
||||||
# self.save_playlist(session)
|
# self.save_playlist(session)
|
||||||
# self._update_start_end_times(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
|
from app.models import Base, Tracks
|
||||||
|
|
||||||
|
DB_CONNECTION = \
|
||||||
|
"mysql+mysqldb://musicmuster_testing:musicmuster_testing@localhost/dev_musicmuster_testing"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def db_engine():
|
def db_engine():
|
||||||
engine = create_engine(
|
engine = create_engine(DB_CONNECTION, isolation_level="READ COMMITTED")
|
||||||
"mysql+mysqldb://musicmuster_testing:musicmuster_testing@localhost/dev_musicmuster_testing"
|
|
||||||
)
|
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
yield engine
|
yield engine
|
||||||
engine.dispose()
|
engine.dispose()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope="function")
|
||||||
def session(db_engine):
|
def Session(db_engine):
|
||||||
connection = db_engine.connect()
|
connection = db_engine.connect()
|
||||||
transaction = connection.begin()
|
transaction = connection.begin()
|
||||||
Session = sessionmaker(bind=connection)
|
sm = sessionmaker(bind=connection)
|
||||||
session = scoped_session(Session)
|
session = scoped_session(sm)
|
||||||
|
# print(f"PyTest SqlA: session acquired [{hex(id(session))}]")
|
||||||
yield session
|
yield session
|
||||||
|
# print(f" PyTest SqlA: session released and cleaned up [{hex(id(session))}]")
|
||||||
session.remove()
|
session.remove()
|
||||||
transaction.rollback()
|
transaction.rollback()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope="function")
|
||||||
def track1(session):
|
def track1(session):
|
||||||
track_path = "testdata/isa.mp3"
|
track_path = "testdata/isa.mp3"
|
||||||
metadata = helpers.get_file_metadata(track_path)
|
metadata = helpers.get_file_metadata(track_path)
|
||||||
@ -39,7 +42,7 @@ def track1(session):
|
|||||||
return track
|
return track
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope="function")
|
||||||
def track2(session):
|
def track2(session):
|
||||||
track_path = "testdata/mom.mp3"
|
track_path = "testdata/mom.mp3"
|
||||||
metadata = helpers.get_file_metadata(track_path)
|
metadata = helpers.get_file_metadata(track_path)
|
||||||
|
|||||||
@ -55,6 +55,7 @@ mypy_path = "/home/kae/git/musicmuster/app"
|
|||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "--exitfirst --showlocals --capture=no"
|
addopts = "--exitfirst --showlocals --capture=no"
|
||||||
pythonpath = [".", "app"]
|
pythonpath = [".", "app"]
|
||||||
|
filterwarnings = "ignore:'audioop' is deprecated"
|
||||||
|
|
||||||
[tool.vulture]
|
[tool.vulture]
|
||||||
exclude = ["migrations", "app/ui", "archive"]
|
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