From 67c48f5022246896d97ca5c09e582c562b4fdbae Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Tue, 4 Mar 2025 10:32:11 +0000 Subject: [PATCH] Select from query working (may need tidying) --- app/classes.py | 2 +- app/config.py | 6 +++-- app/models.py | 59 +++++++++++++++++++++++++++++++++++-------- app/musicmuster.py | 11 +++++--- app/querylistmodel.py | 5 ++-- 5 files changed, 64 insertions(+), 19 deletions(-) diff --git a/app/classes.py b/app/classes.py index aac3c94..2563919 100644 --- a/app/classes.py +++ b/app/classes.py @@ -84,7 +84,7 @@ class Filter: path_type: str = "contains" path: Optional[str] = None last_played_number: Optional[int] = None - last_played_type: str = "before" + last_played_comparator: str = "before" last_played_unit: str = "years" duration_type: str = "longer than" duration_number: int = 0 diff --git a/app/config.py b/app/config.py index 41f50b8..5d5ef6c 100644 --- a/app/config.py +++ b/app/config.py @@ -39,6 +39,7 @@ class Config(object): DISPLAY_SQL = False DO_NOT_IMPORT = "Do not import" ENGINE_OPTIONS = dict(pool_pre_ping=True) + # ENGINE_OPTIONS = dict(pool_pre_ping=True, echo=True) EPOCH = dt.datetime(1970, 1, 1) ERRORS_FROM = ["noreply@midnighthax.com"] ERRORS_TO = ["kae@midnighthax.com"] @@ -55,10 +56,11 @@ class Config(object): FILTER_DURATION_SHORTER = "shorter than" FILTER_PATH_CONTAINS = "contains" FILTER_PATH_EXCLUDING = "excluding" - FILTER_PLAYED_BEFORE = "before" + FILTER_PLAYED_COMPARATOR_ANYTIME = "Any time" + FILTER_PLAYED_COMPARATOR_BEFORE = "before" + FILTER_PLAYED_COMPARATOR_NEVER = "never" FILTER_PLAYED_DAYS = "days" FILTER_PLAYED_MONTHS = "months" - FILTER_PLAYED_NEVER = "never" FILTER_PLAYED_WEEKS = "weeks" FILTER_PLAYED_YEARS = "years" FUZZYMATCH_MINIMUM_LIST = 60.0 diff --git a/app/models.py b/app/models.py index 80a1b98..92344ee 100644 --- a/app/models.py +++ b/app/models.py @@ -599,7 +599,11 @@ class PlaylistRows(dbtables.PlaylistRowsTable): class Queries(dbtables.QueriesTable): def __init__( - self, session: Session, name: str, filter: dbtables.Filter, favourite: bool = False + self, + session: Session, + name: str, + filter: dbtables.Filter, + favourite: bool = False, ) -> None: """Create new query""" @@ -620,9 +624,7 @@ class Queries(dbtables.QueriesTable): """Returns a list of favourite queries ordered by name""" return session.scalars( - select(cls) - .where(cls.favourite.is_(True)) - .order_by(cls.name) + select(cls).where(cls.favourite.is_(True)).order_by(cls.name) ).all() @@ -711,12 +713,16 @@ class Tracks(dbtables.TracksTable): ) @classmethod - def get_filtered_tracks(cls, session: Session, filter: Filter) -> Sequence["Tracks"]: + def get_filtered_tracks( + cls, session: Session, filter: Filter + ) -> Sequence["Tracks"]: """ Return tracks matching filter """ query = select(cls) + + # Path specification if filter.path: if filter.path_type == "contains": query = query.where(cls.path.ilike(f"%{filter.path}%")) @@ -724,14 +730,14 @@ class Tracks(dbtables.TracksTable): query = query.where(cls.path.notilike(f"%{filter.path}%")) else: raise ApplicationError(f"Can't process filter path ({filter=})") - # TODO - # if last_played_number: - # need group_by track_id and having max/min lastplayed gt/lt, etc + + # Duration specification seconds_duration = filter.duration_number if filter.duration_unit == Config.FILTER_DURATION_MINUTES: seconds_duration *= 60 elif filter.duration_unit != Config.FILTER_DURATION_SECONDS: raise ApplicationError(f"Can't process filter duration ({filter=})") + if filter.duration_type == Config.FILTER_DURATION_LONGER: query = query.where(cls.duration >= seconds_duration) elif filter.duration_unit == Config.FILTER_DURATION_SHORTER: @@ -739,8 +745,41 @@ class Tracks(dbtables.TracksTable): else: raise ApplicationError(f"Can't process filter duration type ({filter=})") - records = session.scalars( - query).unique().all() + # Last played specification + if ( + filter.last_played_number + and filter.last_played_comparator != Config.FILTER_PLAYED_COMPARATOR_ANYTIME + ): + now = dt.datetime.now() + since = now - dt.timedelta(days=365 * filter.last_played_number) + if filter.duration_unit == Config.FILTER_PLAYED_DAYS: + since = now - dt.timedelta(days=filter.last_played_number) + elif filter.duration_unit == Config.FILTER_PLAYED_WEEKS: + since = now - dt.timedelta(days=7 * filter.last_played_number) + if filter.duration_unit == Config.FILTER_PLAYED_MONTHS: + since = now - dt.timedelta(days=30 * filter.last_played_number) + + if filter.last_played_comparator == Config.FILTER_PLAYED_COMPARATOR_NEVER: + # Select tracks that have never been played + query = query.outerjoin(Playdates, cls.id == Playdates.track_id).where( + Playdates.id.is_(None) + ) + elif ( + filter.last_played_comparator == Config.FILTER_PLAYED_COMPARATOR_BEFORE + ): + subquery = ( + select( + Playdates.track_id, + func.max(Playdates.lastplayed).label("max_last_played"), + ) + .group_by(Playdates.track_id) + .subquery() + ) + query = query.join(subquery, Tracks.id == subquery.c.track_id).where( + subquery.c.max_last_played < since + ) + + records = session.scalars(query).unique().all() return records diff --git a/app/musicmuster.py b/app/musicmuster.py index fb46c5b..61fe8bd 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -48,7 +48,6 @@ from PyQt6.QtWidgets import ( QMenu, QMessageBox, QPushButton, - QSizePolicy, QSpinBox, QTableView, QTableWidget, @@ -215,10 +214,14 @@ class FilterDialog(QDialog): last_played_label = QLabel("Last played") self.last_played_combo = QComboBox() self.last_played_combo.addItems( - [Config.FILTER_PLAYED_BEFORE, Config.FILTER_PLAYED_NEVER] + [ + Config.FILTER_PLAYED_COMPARATOR_BEFORE, + Config.FILTER_PLAYED_COMPARATOR_NEVER, + Config.FILTER_PLAYED_COMPARATOR_ANYTIME, + ] ) for idx in range(self.last_played_combo.count()): - if self.last_played_combo.itemText(idx) == filter.last_played_type: + if self.last_played_combo.itemText(idx) == filter.last_played_comparator: self.last_played_combo.setCurrentIndex(idx) break @@ -316,7 +319,7 @@ class FilterDialog(QDialog): self.filter.path_type = self.path_combo.currentText() self.filter.path = self.path_text.text() self.filter.last_played_number = self.last_played_spinbox.value() - self.filter.last_played_type = self.last_played_combo.currentText() + self.filter.last_played_comparator = self.last_played_combo.currentText() self.filter.last_played_unit = self.last_played_unit.currentText() self.filter.duration_type = self.duration_combo.currentText() self.filter.duration_number = self.duration_spinbox.value() diff --git a/app/querylistmodel.py b/app/querylistmodel.py index a56f3d3..e2ed657 100644 --- a/app/querylistmodel.py +++ b/app/querylistmodel.py @@ -230,8 +230,9 @@ class QuerylistModel(QAbstractTableModel): try: results = Tracks.get_filtered_tracks(self.session, self.filter) for result in results: - if hasattr(result, "lastplayed"): - lastplayed = result["lastplayed"] + if hasattr(result, "playdates"): + pds = result.playdates + lastplayed = max([a.lastplayed for a in pds]) else: lastplayed = None queryrow = QueryRow(