Compare commits

..

No commits in common. "262ab202fcc5394de0cf6760e42ff2ec001fe97c" and "eae8870d4d154253e54bb73a252bd2ecfd7aff86" have entirely different histories.

7 changed files with 219 additions and 339 deletions

View File

@ -90,7 +90,7 @@ class Config(object):
ROWS_FROM_ZERO = True
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
SCROLL_TOP_MARGIN = 3
START_GAP_WARNING_THRESHOLD = 300
START_GAP_WARNING_THRESHOLD = 500
TEXT_NO_TRACK_NO_NOTE = "[Section header]"
TOD_TIME_FORMAT = "%H:%M:%S"
TRACK_TIME_FORMAT = "%H:%M:%S"

View File

@ -25,6 +25,7 @@ from sqlalchemy import (
from sqlalchemy.orm import (
DeclarativeBase,
joinedload,
lazyload,
Mapped,
mapped_column,
relationship,
@ -216,8 +217,7 @@ class Playlists(Base):
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(32), unique=True)
last_used: Mapped[Optional[datetime]] = mapped_column(DateTime, default=None)
tab: Mapped[Optional[int]] = mapped_column(default=None)
open: Mapped[bool] = mapped_column(default=False)
tab: Mapped[Optional[int]] = mapped_column(default=None, unique=True)
is_template: Mapped[bool] = mapped_column(default=False)
deleted: Mapped[bool] = mapped_column(default=False)
rows: Mapped[List["PlaylistRows"]] = relationship(
@ -230,7 +230,7 @@ class Playlists(Base):
def __repr__(self) -> str:
return (
f"<Playlists(id={self.id}, name={self.name}, "
f"is_templatee={self.is_template}, open={self.open}>"
f"is_templatee={self.is_template}>"
)
def __init__(self, session: scoped_session, name: str):
@ -238,10 +238,19 @@ class Playlists(Base):
session.add(self)
session.flush()
def close(self) -> None:
def close(self, session: scoped_session) -> None:
"""Mark playlist as unloaded"""
self.open = False
closed_idx = self.tab
self.tab = None
# Closing this tab will mean all higher-number tabs have moved
# down by one
session.execute(
update(Playlists)
.where(Playlists.tab > closed_idx)
.values(tab=Playlists.tab - 1)
)
@classmethod
def create_playlist_from_template(
@ -274,7 +283,7 @@ class Playlists(Base):
return session.scalars(
select(cls)
.filter(cls.is_template.is_(False))
.order_by(cls.last_used.desc())
.order_by(cls.tab.desc(), cls.last_used.desc())
).all()
@classmethod
@ -292,7 +301,7 @@ class Playlists(Base):
return session.scalars(
select(cls)
.filter(
cls.open.is_(False),
cls.tab.is_(None),
cls.is_template.is_(False),
cls.deleted.is_(False),
)
@ -302,29 +311,32 @@ class Playlists(Base):
@classmethod
def get_open(cls, session: scoped_session) -> Sequence[Optional["Playlists"]]:
"""
Return a list of loaded playlists ordered by tab.
Return a list of loaded playlists ordered by tab order.
"""
return session.scalars(
select(cls).where(cls.open.is_(True))
.order_by(cls.tab)
select(cls).where(cls.tab.is_not(None)).order_by(cls.tab)
).all()
def mark_open(self) -> None:
def mark_open(self, session: scoped_session, tab_index: int) -> None:
"""Mark playlist as loaded and used now"""
self.open = True
self.tab = tab_index
self.last_used = datetime.now()
@staticmethod
def name_is_available(session: scoped_session, name: str) -> bool:
"""
Return True if no playlist of this name exists else false.
"""
def move_tab(session: scoped_session, frm: int, to: int) -> None:
"""Move tabs"""
return session.execute(
select(Playlists)
.where(Playlists.name == name)
).first() is None
row_frm = session.execute(select(Playlists).filter_by(tab=frm)).scalar_one()
row_to = session.execute(select(Playlists).filter_by(tab=to)).scalar_one()
row_frm.tab = None
row_to.tab = None
session.commit()
row_to.tab = frm
row_frm.tab = to
def rename(self, session: scoped_session, new_name: str) -> None:
"""
@ -477,17 +489,17 @@ class PlaylistRows(Base):
session.flush()
@staticmethod
def delete_row(
session: scoped_session, playlist_id: int, row_number: int
def delete_rows(
session: scoped_session, playlist_id: int, row_numbers: List[int]
) -> None:
"""
Delete passed row in given playlist.
Delete passed rows in given playlist.
"""
session.execute(
delete(PlaylistRows).where(
PlaylistRows.playlist_id == playlist_id,
PlaylistRows.plr_rownum == row_number,
PlaylistRows.plr_rownum.in_(row_numbers),
)
)
@ -672,6 +684,7 @@ class Settings(Base):
f_string: Mapped[Optional[str]] = mapped_column(String(128), default=None)
def __repr__(self) -> str:
value = self.f_datetime or self.f_int or self.f_string
return (
f"<Settings(id={self.id}, name={self.name}, "
f"f_datetime={self.f_datetime}, f_int={self.f_int}, f_string={self.f_string}>"

View File

@ -442,14 +442,6 @@ class Window(QMainWindow, Ui_MainWindow):
if record.f_int != splitter_bottom:
record.update(session, {"f_int": splitter_bottom})
# Save tab number of open playlists
for idx in range(self.tabPlaylist.count()):
playlist_id = self.tabPlaylist.widget(idx).playlist_id
playlist = session.get(Playlists, playlist_id)
if playlist:
playlist.tab = idx
session.flush()
# Save current tab
record = settings["active_tab"]
record.update(session, {"f_int": self.tabPlaylist.currentIndex()})
@ -471,25 +463,29 @@ class Window(QMainWindow, Ui_MainWindow):
Return True if tab closed else False.
"""
# Don't close current track playlist
current_track_playlist_id = track_sequence.now.playlist_id
closing_tab_playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
if current_track_playlist_id:
if closing_tab_playlist_id == current_track_playlist_id:
self.statusbar.showMessage("Can't close current track playlist", 5000)
return False
# TODO Reimplement without ussing self.current_track.playlist_tab
# # Don't close current track playlist
# if self.tabPlaylist.widget(tab_index) == (self.current_track.playlist_tab):
# self.statusbar.showMessage("Can't close current track playlist", 5000)
# return False
# Record playlist as closed and update remaining playlist tabs
with Session() as session:
playlist = session.get(Playlists, closing_tab_playlist_id)
if playlist:
playlist.close()
# # Attempt to close next track playlist
# if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
# self.next_track.playlist_tab.clear_next()
# Close playlist and remove tab
self.tabPlaylist.widget(tab_index).close()
self.tabPlaylist.removeTab(tab_index)
# # Record playlist as closed and update remaining playlist tabs
# with Session() as session:
# playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
# playlist = session.get(Playlists, playlist_id)
# if playlist:
# playlist.close(session)
return True
# # Close playlist and remove tab
# self.tabPlaylist.widget(tab_index).close()
# self.tabPlaylist.removeTab(tab_index)
# return True
def connect_signals_slots(self) -> None:
self.action_About.triggered.connect(self.about)
@ -529,9 +525,7 @@ class Window(QMainWindow, Ui_MainWindow):
lambda: self.tabPlaylist.currentWidget().lookup_row_in_wikipedia()
)
self.actionSearch.triggered.connect(self.search_playlist)
self.actionSelect_duplicate_rows.triggered.connect(
lambda: self.active_tab().select_duplicate_rows()
)
self.actionSelect_duplicate_rows.triggered.connect(self.select_duplicate_rows)
self.actionSelect_next_track.triggered.connect(self.select_next_row)
self.actionSelect_previous_track.triggered.connect(self.select_previous_row)
self.actionMoveUnplayed.triggered.connect(self.move_unplayed)
@ -545,8 +539,10 @@ class Window(QMainWindow, Ui_MainWindow):
self.btnStop.clicked.connect(self.stop)
self.hdrCurrentTrack.clicked.connect(self.show_current)
self.hdrNextTrack.clicked.connect(self.show_next)
self.tabPlaylist.currentChanged.connect(self.tab_change)
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
self.tabBar = self.tabPlaylist.tabBar()
self.tabBar.tabMoved.connect(self.move_tab)
self.txtSearch.returnPressed.connect(self.search_playlist_return)
self.signals.enable_escape_signal.connect(self.enable_escape)
@ -561,26 +557,22 @@ class Window(QMainWindow, Ui_MainWindow):
) -> Optional[Playlists]:
"""Create new playlist"""
playlist_name = self.solicit_playlist_name(session)
playlist_name = self.solicit_playlist_name()
if not playlist_name:
return None
playlist = Playlists(session, playlist_name)
if playlist:
playlist.mark_open()
return playlist
return None
def create_and_show_playlist(self) -> None:
"""Create new playlist and display it"""
with Session() as session:
playlist = self.create_playlist(session)
if playlist:
self.create_playlist_tab(playlist)
self.create_playlist_tab(session, playlist)
def create_playlist_tab(self, playlist: Playlists) -> int:
def create_playlist_tab(self, session: scoped_session, playlist: Playlists) -> int:
"""
Take the passed playlist database object, create a playlist tab and
add tab to display. Return index number of tab.
@ -859,9 +851,8 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.resize(500, 100)
ok = dlg.exec()
if ok:
model.insert_row(
proposed_row_number=self.active_tab().get_selected_row_number(),
note=dlg.textValue(),
model.insert_header_row(
self.active_tab().get_selected_row_number(), dlg.textValue()
)
def insert_track(self) -> None:
@ -881,7 +872,7 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session:
for playlist in Playlists.get_open(session):
if playlist:
_ = self.create_playlist_tab(playlist)
_ = self.create_playlist_tab(session, playlist)
# Set active tab
record = Settings.get_int_settings(session, "active_tab")
if record.f_int and record.f_int >= 0:
@ -975,6 +966,12 @@ class Window(QMainWindow, Ui_MainWindow):
self.move_playlist_rows(session, selected_plrs)
def move_tab(self, frm: int, to: int) -> None:
"""Handle tabs being moved"""
with Session() as session:
Playlists.move_tab(session, frm, to)
def move_unplayed(self) -> None:
"""
Move unplayed rows to another playlist
@ -997,22 +994,18 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.exec()
template = dlg.playlist
if template:
playlist_name = self.solicit_playlist_name(session)
playlist_name = self.solicit_playlist_name()
if not playlist_name:
return
playlist = Playlists.create_playlist_from_template(
session, template, playlist_name
)
if not playlist:
return
tab_index = self.create_playlist_tab(session, playlist)
playlist.mark_open(session, tab_index)
# Need to ensure that the new playlist is committed to
# the database before it is opened by the model.
session.commit()
if playlist:
playlist.mark_open()
self.create_playlist_tab(playlist)
def open_playlist(self) -> None:
def open_playlist(self):
"""Open existing playlist"""
with Session() as session:
@ -1021,8 +1014,8 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.exec()
playlist = dlg.playlist
if playlist:
self.create_playlist_tab(playlist)
playlist.mark_open()
tab_index = self.create_playlist_tab(session, playlist)
playlist.mark_open(session, tab_index)
def paste_rows(self) -> None:
"""
@ -1298,6 +1291,35 @@ class Window(QMainWindow, Ui_MainWindow):
self.active_tab().set_search(self.txtSearch.text())
self.enable_play_next_controls()
def select_duplicate_rows(self) -> None:
"""
Select the last of any rows with duplicate tracks in current playlist.
This allows the selection to typically come towards the end of the playlist away
from any show specific sections.
If there a track is selected on three or more rows, only the last one is selected.
"""
visible_playlist_id = self.active_tab().playlist_id
# Get row number of duplicate rows
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.active_tab().select_rows(row_numbers)
self.statusbar.showMessage(
f"{len(row_numbers)} duplicate rows selected", 10000
)
def select_next_row(self) -> None:
"""Select next or first row in playlist"""
@ -1364,30 +1386,18 @@ class Window(QMainWindow, Ui_MainWindow):
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
# self.tabPlaylist.currentWidget().scroll_next_to_top()
def solicit_playlist_name(
self, session: scoped_session, default: str = ""
) -> Optional[str]:
"""Get name of new playlist from user"""
def solicit_playlist_name(self, default: Optional[str] = "") -> Optional[str]:
"""Get name of playlist from user"""
dlg = QInputDialog(self)
dlg.setInputMode(QInputDialog.InputMode.TextInput)
dlg.setLabelText("Playlist name:")
while True:
if default:
dlg.setTextValue(default)
dlg.resize(500, 100)
ok = dlg.exec()
if ok:
proposed_name = dlg.textValue()
if Playlists.name_is_available(session, proposed_name):
return proposed_name
else:
helpers.show_warning(
self,
"Name in use",
f"There's already a playlist called '{proposed_name}'",
)
continue
return dlg.textValue()
else:
return None
@ -1453,6 +1463,15 @@ class Window(QMainWindow, Ui_MainWindow):
# Enable controls
self.enable_play_next_controls()
def tab_change(self):
"""Called when active tab changed"""
try:
self.tabPlaylist.currentWidget().tab_visible()
except AttributeError:
# May also be called when last tab is closed
pass
def set_next_plr_id(
self, next_plr_id: Optional[int], playlist_tab: PlaylistTab
) -> None:

View File

@ -124,9 +124,6 @@ class PlaylistModel(QAbstractTableModel):
self.signals.add_track_to_playlist_signal.connect(self.add_track)
with Session() as session:
# Ensure row numbers in playlist are contiguous
PlaylistRows.fixup_rownumbers(session, playlist_id)
# Populate self.playlist_rows
self.refresh_data(session)
self.update_track_times()
@ -150,7 +147,15 @@ class PlaylistModel(QAbstractTableModel):
if playlist_id != self.playlist_id:
return
self.insert_row(proposed_row_number=new_row_number, track_id=track_id, note=note)
# Insert track if we have one
if track_id:
self.insert_track_row(new_row_number, track_id, note)
# If we only have a note, add as a header row
elif note:
self.insert_header_row(new_row_number, note)
else:
# No track, no note, no point
return
def add_track_to_header(
self,
@ -340,17 +345,10 @@ class PlaylistModel(QAbstractTableModel):
def delete_rows(self, row_numbers: List[int]) -> None:
"""
Delete passed rows from model
Need to delete them in contiguous groups wrapped in beginRemoveRows / endRemoveRows
calls. To keep it simple, if inefficient, delete rows one by one.
"""
with Session() as session:
for row_number in row_numbers:
super().beginRemoveRows(QModelIndex(), row_number, row_number)
PlaylistRows.delete_row(session, self.playlist_id, row_number)
super().endRemoveRows()
PlaylistRows.delete_rows(session, self.playlist_id, row_numbers)
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
self.refresh_data(session)
self.update_track_times()
@ -399,26 +397,6 @@ class PlaylistModel(QAbstractTableModel):
return QVariant()
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]
(ie, ignore the first, not-yet-duplicate, track).
"""
found = []
result = []
for i in range(len(self.playlist_rows)):
track_id = self.playlist_rows[i].track_id
if track_id is None:
continue
if track_id in found:
result.append(i)
else:
found.append(track_id)
return result
def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
"""
Return text for editing
@ -470,25 +448,6 @@ class PlaylistModel(QAbstractTableModel):
return QVariant(boldfont)
def _get_new_row_number(self, proposed_row_number: Optional[int]) -> int:
"""
Sanitises proposed new row number.
If proposed_row_number given, ensure it is valid.
If not given, return row number to add to end of model.
"""
if proposed_row_number is None or proposed_row_number > len(self.playlist_rows):
# We are adding to the end of the list
new_row_number = len(self.playlist_rows)
elif proposed_row_number < 0:
# Add to start of list
new_row_number = 0
else:
new_row_number = proposed_row_number
return new_row_number
def get_row_track_path(self, row_number: int) -> str:
"""
Return path of track associated with row or empty string if no track associated
@ -507,13 +466,6 @@ class PlaylistModel(QAbstractTableModel):
return duration
def get_row_info(self, row_number: int) -> PlaylistRowData:
"""
Return info about passed row
"""
return self.playlist_rows[row_number]
def headerData(
self,
section: int,
@ -648,33 +600,66 @@ class PlaylistModel(QAbstractTableModel):
return self.playlist_rows[row_number].played
def insert_row(
self,
proposed_row_number: Optional[int],
track_id: Optional[int] = None,
note: Optional[str] = None,
def insert_header_row(self, row_number: Optional[int], text: str) -> None:
"""
Insert a header row.
"""
with Session() as session:
plr = self._insert_row(session, row_number)
# Update the PlaylistRows object
plr.note = text
# Repopulate self.playlist_rows
self.refresh_data(session)
# Update the display from the new row onwards
self.invalidate_rows(list(range(plr.plr_rownum, len(self.playlist_rows))))
def _insert_row(
self, session: scoped_session, row_number: Optional[int]
) -> PlaylistRows:
"""
Insert a row in the database.
If row_number is greater than length of list plus 1, or if row
number is None, put row at end of list.
Move existing rows to make space if ncessary.
Return the new PlaylistRows object.
"""
if row_number is None or row_number > len(self.playlist_rows):
# We are adding to the end of the list so we can optimise
new_row_number = len(self.playlist_rows)
return PlaylistRows(session, self.playlist_id, new_row_number)
elif row_number < 0:
raise ValueError(
f"playlistmodel._insert_row, invalid row number ({row_number})"
)
else:
new_row_number = row_number
# Insert the new row and return it
return PlaylistRows.insert_row(session, self.playlist_id, new_row_number)
def insert_track_row(
self, row_number: Optional[int], track_id: int, text: Optional[str]
) -> None:
"""
Insert a track row.
"""
new_row_number = self._get_new_row_number(proposed_row_number)
with Session() as session:
super().beginInsertRows(QModelIndex(), new_row_number, new_row_number)
plr = PlaylistRows.insert_row(session, self.playlist_id, new_row_number)
plr = self._insert_row(session, row_number)
# Update the PlaylistRows object
plr.track_id = track_id
if note:
plr.note = note
if text:
plr.note = text
# Repopulate self.playlist_rows
self.refresh_data(session)
super().endInsertRows()
# Update the display from the new row onwards
self.invalidate_rows(list(range(plr.plr_rownum, len(self.playlist_rows))))
return plr
def invalidate_row(self, modified_row: int) -> None:
"""
Signal to view to refresh invlidated row

View File

@ -205,26 +205,7 @@ class PlaylistTab(QTableView):
self.setModel(PlaylistModel(playlist_id))
self._set_column_widths()
def closeEditor(
self, editor: QWidget | None, hint: QAbstractItemDelegate.EndEditHint
) -> None:
"""
Override closeEditor to enable play controls and update display.
"""
self.musicmuster.enable_play_next_controls()
self.musicmuster.actionSetNext.setEnabled(True)
self.musicmuster.action_Clear_selection.setEnabled(True)
super(PlaylistTab, self).closeEditor(editor, hint)
# Optimise row heights after increasing row height for editing
self.resizeRowsToContents()
# Update start times in case a start time in a note has been
# edited
model = cast(PlaylistModel, self.model())
model.update_track_times()
# ########## Events other than cell editing ##########
def dropEvent(self, event):
if event.source() is not self or (
@ -249,25 +230,6 @@ class PlaylistTab(QTableView):
event.accept()
def edit(
self,
index: QModelIndex,
trigger: QAbstractItemView.EditTrigger,
event: Optional[QEvent],
) -> bool:
"""
Override PySide2.QAbstractItemView.edit to catch when editing starts
Editing only ever starts with a double click on a cell
"""
# 'result' will only be true on double-click
result = super().edit(index, trigger, event)
if result:
self.musicmuster.disable_play_next_controls()
return result
def _add_context_menu(
self,
text: str,
@ -576,9 +538,9 @@ class PlaylistTab(QTableView):
parent_menu=sort_menu,
)
# Info
# Info TODO
if track_row:
self._add_context_menu("Info", lambda: self._info_row(row_number))
self._add_context_menu("Info", lambda: print("Track info"))
# Track path TODO
if track_row:
@ -682,23 +644,25 @@ class PlaylistTab(QTableView):
# items in that row selected)
return sorted(list(set([a.row() for a in self.selectedIndexes()])))
def _info_row(self, row_number: int) -> None:
def _info_row(self, track_id: int) -> None:
"""Display popup with info re row"""
model = cast(PlaylistModel, self.model())
prd = model.get_row_info(row_number)
if prd:
with Session() as session:
track = session.get(Tracks, track_id)
if track:
txt = (
f"Title: {prd.title}\n"
f"Artist: {prd.artist}\n"
f"Track ID: {prd.track_id}\n"
f"Track duration: {ms_to_mmss(prd.duration)}\n"
f"Track bitrate: {prd.bitrate}\n"
f"Title: {track.title}\n"
f"Artist: {track.artist}\n"
f"Track ID: {track.id}\n"
f"Track duration: {ms_to_mmss(track.duration)}\n"
f"Track bitrate: {track.bitrate}\n"
f"Track fade at: {ms_to_mmss(track.fade_at)}\n"
f"Track silence at: {ms_to_mmss(track.silence_at)}"
"\n\n"
f"Path: {prd.path}\n"
f"Path: {track.path}\n"
)
else:
txt = f"Can't find info about row{row_number}"
txt = f"Can't find {track_id=}"
info: QMessageBox = QMessageBox(self)
info.setIcon(QMessageBox.Icon.Information)
@ -915,26 +879,6 @@ class PlaylistTab(QTableView):
# if match_row is not None:
# self.selectRow(row_number)
def select_duplicate_rows(self) -> None:
"""
Select the last of any rows with duplicate tracks in current playlist.
This allows the selection to typically come towards the end of the playlist away
from any show specific sections.
"""
# Clear any selected rows to avoid confustion
self.clear_selection()
# We need to be in MultiSelection mode
self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
# Get the duplicate rows
model = cast(PlaylistModel, self.model())
duplicate_rows = model.get_duplicate_rows()
# Select the rows
for duplicate_row in duplicate_rows:
self.selectRow(duplicate_row)
# Reset selection mode
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
def selectionChanged(
self, selected: QItemSelection, deselected: QItemSelection
) -> None:

View File

@ -1,60 +0,0 @@
"""Add 'open' field to Playlists
Revision ID: 5bb2c572e1e5
Revises: 3a53a9fb26ab
Create Date: 2023-11-18 14:19:02.643914
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '5bb2c572e1e5'
down_revision = '3a53a9fb26ab'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('carts', 'duration',
existing_type=mysql.INTEGER(display_width=11),
nullable=True)
op.alter_column('carts', 'path',
existing_type=mysql.VARCHAR(length=2048),
nullable=True)
op.alter_column('carts', 'enabled',
existing_type=mysql.TINYINT(display_width=1),
nullable=True)
op.alter_column('playlist_rows', 'note',
existing_type=mysql.VARCHAR(length=2048),
nullable=False)
op.add_column('playlists', sa.Column('open', sa.Boolean(), nullable=False))
op.alter_column('settings', 'name',
existing_type=mysql.VARCHAR(length=32),
type_=sa.String(length=64),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('settings', 'name',
existing_type=sa.String(length=64),
type_=mysql.VARCHAR(length=32),
existing_nullable=False)
op.drop_column('playlists', 'open')
op.alter_column('playlist_rows', 'note',
existing_type=mysql.VARCHAR(length=2048),
nullable=True)
op.alter_column('carts', 'enabled',
existing_type=mysql.TINYINT(display_width=1),
nullable=False)
op.alter_column('carts', 'path',
existing_type=mysql.VARCHAR(length=2048),
nullable=False)
op.alter_column('carts', 'duration',
existing_type=mysql.INTEGER(display_width=11),
nullable=False)
# ### end Alembic commands ###

View File

@ -2,8 +2,6 @@ from app.models import (
Playlists,
Tracks,
)
from PyQt6.QtCore import Qt
from app.helpers import get_file_metadata
from app import playlistmodel
from dbconfig import scoped_session
@ -27,7 +25,7 @@ def create_model_with_tracks(session: scoped_session) -> "playlistmodel.Playlist
track_path = test_tracks[row % len(test_tracks)]
metadata = get_file_metadata(track_path)
track = Tracks(session, **metadata)
model.insert_row(proposed_row_number=row, track_id=track.id, note=f"{row=}")
model.insert_track_row(row, track.id, f"{row=}")
session.commit()
return model
@ -40,8 +38,10 @@ def create_model_with_playlist_rows(
# Create a model
model = playlistmodel.PlaylistModel(playlist.id)
for row in range(rows):
plr = model.insert_row(proposed_row_number=row, note=str(row))
model.playlist_rows[plr.plr_rownum] = playlistmodel.PlaylistRowData(plr)
plr = model._insert_row(session, row)
newrow = plr.plr_rownum
plr.note = str(newrow)
model.playlist_rows[newrow] = playlistmodel.PlaylistRowData(plr)
session.commit()
return model
@ -195,7 +195,7 @@ def test_insert_header_row_end(monkeypatch, session):
initial_row_count = 11
model = create_model_with_playlist_rows(session, initial_row_count)
model.insert_row(proposed_row_number=None, note=note_text)
model.insert_header_row(None, note_text)
assert model.rowCount() == initial_row_count + 1
prd = model.playlist_rows[model.rowCount() - 1]
# Test against edit_role because display_role for headers is
@ -215,7 +215,7 @@ def test_insert_header_row_middle(monkeypatch, session):
insert_row = 6
model = create_model_with_playlist_rows(session, initial_row_count)
model.insert_row(proposed_row_number=insert_row, note=note_text)
model.insert_header_row(insert_row, note_text)
assert model.rowCount() == initial_row_count + 1
prd = model.playlist_rows[insert_row]
# Test against edit_role because display_role for headers is
@ -239,35 +239,14 @@ def test_timing_one_track(monkeypatch, session):
monkeypatch.setattr(playlistmodel, "Session", session)
model = create_model_with_tracks(session)
model.insert_row(proposed_row_number=START_ROW, note="start+")
model.insert_row(proposed_row_number=END_ROW, note="-")
model.insert_header_row(START_ROW, "start+")
model.insert_header_row(END_ROW, "-")
prd = model.playlist_rows[START_ROW]
qv_value = model.display_role(START_ROW, playlistmodel.HEADER_NOTES_COLUMN, prd)
assert qv_value.value() == "start [1 tracks, 4:23 unplayed]"
def test_insert_track_new_playlist(monkeypatch, session):
# insert a track into a new playlist
monkeypatch.setattr(playlistmodel, "Session", session)
playlist = Playlists(session, "test playlist")
# Create a model
model = playlistmodel.PlaylistModel(playlist.id)
track_path = test_tracks[0]
metadata = get_file_metadata(track_path)
track = Tracks(session, **metadata)
model.insert_row(proposed_row_number=0, track_id=track.id)
prd = model.playlist_rows[model.rowCount() - 1]
assert (
model.edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd)
== metadata["title"]
)
# def test_edit_header(monkeypatch, session): # edit header row in middle of playlist
# monkeypatch.setattr(playlistmodel, "Session", session)