From c097811e40dd5b09d11eac508b4dbbe55b2c6717 Mon Sep 17 00:00:00 2001 From: marc Date: Sun, 23 Mar 2025 00:19:33 +0100 Subject: [PATCH] Added stats --- .../fragments/sessions/session_date.html | 16 +++---- .../templates/fragments/tema/pagina.html | 1 + .../templates/fragments/tema/stats.html | 42 ++++++++++++++++++ .../templates/fragments/temes/results.html | 8 ++++ folkugat_web/dal/sql/playlists/query.py | 23 +++++++--- folkugat_web/fragments/tema.py | 44 ++++++++++--------- folkugat_web/fragments/temes.py | 9 +++- folkugat_web/model/temes.py | 18 +++++++- folkugat_web/services/temes/query.py | 25 ++++++++++- folkugat_web/utils.py | 2 +- 10 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 folkugat_web/assets/templates/fragments/tema/stats.html diff --git a/folkugat_web/assets/templates/fragments/sessions/session_date.html b/folkugat_web/assets/templates/fragments/sessions/session_date.html index 3442de3..26934a9 100644 --- a/folkugat_web/assets/templates/fragments/sessions/session_date.html +++ b/folkugat_web/assets/templates/fragments/sessions/session_date.html @@ -1,11 +1,7 @@ -{% if session == None %} - L'últim Dimecres de cada mes -{% else %} - {% set dn = date_names(session.date) %} - {{ dn.day_name }} {{ dn.day }} {{ dn.month_name }} - de {{ session.start_time.strftime("%H:%M") }} - a {{ session.end_time.strftime("%H:%M") }} - {% if session.venue.name %} - a {{ session.venue.name }} - {% endif %} +{% set dn = date_names(session.date) %} +{{ dn.day_name }} {{ dn.day }} {{ dn.month_name }} + de {{ session.start_time.strftime("%H:%M") }} + a {{ session.end_time.strftime("%H:%M") }} +{% if session.venue.name %} + a {{ session.venue.name }} {% endif %} diff --git a/folkugat_web/assets/templates/fragments/tema/pagina.html b/folkugat_web/assets/templates/fragments/tema/pagina.html index 421c6e8..105c8f0 100644 --- a/folkugat_web/assets/templates/fragments/tema/pagina.html +++ b/folkugat_web/assets/templates/fragments/tema/pagina.html @@ -13,5 +13,6 @@ {% include "fragments/tema/lyrics.html" %} {% include "fragments/tema/links.html" %} {% include "fragments/tema/properties.html" %} + {% include "fragments/tema/stats.html" %} diff --git a/folkugat_web/assets/templates/fragments/tema/stats.html b/folkugat_web/assets/templates/fragments/tema/stats.html new file mode 100644 index 0000000..8e3ab06 --- /dev/null +++ b/folkugat_web/assets/templates/fragments/tema/stats.html @@ -0,0 +1,42 @@ +{% if tema.stats %} +

Estadístiques

+
+
+

+ Aquest tema ha sigut tocat en + {% if tema.stats.times_played == 1%} + una sessió. + {% else %} + {{ tema.stats.times_played }} sessions. + {% endif %} +

+

+ S'ha tocat a les sessions següents: +

    + {% for session in tema.stats.sessions_played %} +
  1. + + +
  2. + {% endfor %} +
+

+
+{% endif %} diff --git a/folkugat_web/assets/templates/fragments/temes/results.html b/folkugat_web/assets/templates/fragments/temes/results.html index 6bc5a1b..14c92b5 100644 --- a/folkugat_web/assets/templates/fragments/temes/results.html +++ b/folkugat_web/assets/templates/fragments/temes/results.html @@ -4,6 +4,7 @@ Nom Enllaços + Cops tocat {% for tema in temes %} @@ -15,6 +16,13 @@ {% include "fragments/temes/result_links.html" %} + + {% if tema.stats is none %} + - + {% else %} + {{ tema.stats.times_played }} + {% endif %} + {% endfor %} diff --git a/folkugat_web/dal/sql/playlists/query.py b/folkugat_web/dal/sql/playlists/query.py index f8562d7..d277bbb 100644 --- a/folkugat_web/dal/sql/playlists/query.py +++ b/folkugat_web/dal/sql/playlists/query.py @@ -1,10 +1,11 @@ -from collections.abc import Iterator +from collections.abc import Iterable, Iterator from typing import TypedDict from folkugat_web.dal.sql import Connection, get_connection from folkugat_web.dal.sql.sessions import conversion as sessions_conversion from folkugat_web.model import playlists as model from folkugat_web.model.sessions import Session +from folkugat_web.utils import groupby from . import conversion @@ -55,14 +56,22 @@ def get_playlist_entries( return map(conversion.row_to_playlist_entry, cur.fetchall()) -def get_tune_sessions(tema_ids: list[int], con: Connection | None = None) -> Iterator[Session]: - query = """ +GetTuneSessionsRow = tuple[int, int, str, str, str, str | None, str | None, bool] + + +def get_tune_sessions(tema_ids: list[int], con: Connection | None = None) -> dict[int, list[Session]]: + placeholders = ", ".join(["?" for _ in tema_ids]) + query = f""" SELECT p.tema_id, s.id, s.date, s.start_time, s.end_time, s.venue_name, s.venue_url, s.is_live FROM playlists p JOIN sessions s ON p.session_id = s.id - WHERE p.tema_id IN :tema_ids + WHERE p.tema_id IN ({placeholders}) """ - data = dict(tema_ids=tuple(tema_ids)) with get_connection(con) as con: cur = con.cursor() - _ = cur.execute(query, data) - return map(sessions_conversion.row_to_session, cur.fetchall()) + _ = cur.execute(query, tema_ids) + result_rows: Iterable[GetTuneSessionsRow] = cur.fetchall() + return dict(groupby( + result_rows, + key_fn=lambda row: row[0], + group_fn=lambda rows: list(sessions_conversion.row_to_session(row[1:]) for row in rows) + )) diff --git a/folkugat_web/fragments/tema.py b/folkugat_web/fragments/tema.py index e577a93..7ec8235 100644 --- a/folkugat_web/fragments/tema.py +++ b/folkugat_web/fragments/tema.py @@ -1,6 +1,4 @@ -from typing import Optional - -from fastapi import Request +from fastapi import HTTPException, Request from folkugat_web.model import temes as model from folkugat_web.services.temes import query as temes_q from folkugat_web.services.temes.links import guess_link_type @@ -12,6 +10,8 @@ def title(request: Request, logged_in: bool, tema: model.Tema | None = None, tem if tema_id is None: raise ValueError("Either 'tema' or 'tema_id' must be given!") tema = temes_q.get_tema_by_id(tema_id) + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") return templates.TemplateResponse( "fragments/tema/title.html", { @@ -24,6 +24,8 @@ def title(request: Request, logged_in: bool, tema: model.Tema | None = None, tem def title_editor(request: Request, logged_in: bool, tema_id: int): tema = temes_q.get_tema_by_id(tema_id) + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") return templates.TemplateResponse( "fragments/tema/editor/title.html", { @@ -36,8 +38,8 @@ def title_editor(request: Request, logged_in: bool, tema_id: int): def lyric(request: Request, logged_in: bool, tema_id: int, lyric_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") lyric = tema.lyrics.get(lyric_id) return templates.TemplateResponse( @@ -54,8 +56,8 @@ def lyric(request: Request, logged_in: bool, tema_id: int, lyric_id: int): def lyric_editor(request: Request, logged_in: bool, tema_id: int, lyric_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") lyric = tema.lyrics.get(lyric_id) return templates.TemplateResponse( @@ -72,8 +74,8 @@ def lyric_editor(request: Request, logged_in: bool, tema_id: int, lyric_id: int) def link(request: Request, logged_in: bool, tema_id: int, link_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") link = tema.links.get(link_id) return templates.TemplateResponse( @@ -95,8 +97,8 @@ def link(request: Request, logged_in: bool, tema_id: int, link_id: int): def link_editor(request: Request, logged_in: bool, tema_id: int, link_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") link = tema.links.get(link_id) return templates.TemplateResponse( @@ -115,8 +117,8 @@ def link_editor(request: Request, logged_in: bool, tema_id: int, link_id: int): def link_editor_url(request: Request, logged_in: bool, tema_id: int, link_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") link = tema.links.get(link_id) return templates.TemplateResponse( "fragments/tema/editor/link_url.html", @@ -134,8 +136,8 @@ def link_editor_url(request: Request, logged_in: bool, tema_id: int, link_id: in def link_editor_file(request: Request, logged_in: bool, tema_id: int, link_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") link = tema.links.get(link_id) return templates.TemplateResponse( "fragments/tema/editor/link_file.html", @@ -175,8 +177,8 @@ def link_icon(request: Request, logged_in: bool, tema_id: int, link_id: int, url def score(request: Request, logged_in: bool, tema_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") return templates.TemplateResponse( "fragments/tema/score.html", { @@ -190,8 +192,8 @@ def score(request: Request, logged_in: bool, tema_id: int): def property_(request: Request, logged_in: bool, tema_id: int, property_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") prop = tema.properties.get(property_id) return templates.TemplateResponse( @@ -209,8 +211,8 @@ def property_(request: Request, logged_in: bool, tema_id: int, property_id: int) def property_editor(request: Request, logged_in: bool, tema_id: int, property_id: int): tema = temes_q.get_tema_by_id(tema_id) - if tema is None: - raise ValueError(f"No tune exists for tema_id: {tema_id}") + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") prop = tema.properties.get(property_id) return templates.TemplateResponse( diff --git a/folkugat_web/fragments/temes.py b/folkugat_web/fragments/temes.py index 59591f5..5f790a4 100644 --- a/folkugat_web/fragments/temes.py +++ b/folkugat_web/fragments/temes.py @@ -1,6 +1,7 @@ -from fastapi import Request +from fastapi import HTTPException, Request from folkugat_web.model import temes as model from folkugat_web.model.pagines import Pages +from folkugat_web.services import sessions as sessions_service from folkugat_web.services.temes import query as temes_q from folkugat_web.services.temes import search as temes_s from folkugat_web.templates import templates @@ -34,6 +35,8 @@ def temes_busca(request: Request, logged_in: bool, query: str, offset: int = 0, if offset > 0: prev_offset = max(offset - limit, 0) + temes = temes_q.temes_compute_stats(temes) + return templates.TemplateResponse( "fragments/temes/results.html", { @@ -51,6 +54,9 @@ def temes_busca(request: Request, logged_in: bool, query: str, offset: int = 0, def tema(request: Request, tema_id: int, logged_in: bool): tema = temes_q.get_tema_by_id(tema_id) + if not tema: + raise HTTPException(status_code=404, detail="Could not find tune") + tema = temes_q.tema_compute_stats(tema) return templates.TemplateResponse( "fragments/tema/pagina.html", { @@ -60,5 +66,6 @@ def tema(request: Request, tema_id: int, logged_in: bool): "LinkType": model.LinkType, "ContentType": model.ContentType, "tema": tema, + "date_names": sessions_service.get_date_names, } ) diff --git a/folkugat_web/model/temes.py b/folkugat_web/model/temes.py index 0a3c0fc..417bdd9 100644 --- a/folkugat_web/model/temes.py +++ b/folkugat_web/model/temes.py @@ -101,6 +101,12 @@ class Lyrics(WithId): ) +@dataclasses.dataclass +class Stats: + times_played: int + sessions_played: list[Session] + + @dataclasses.dataclass class Tema: id: int | None = None @@ -117,7 +123,7 @@ class Tema: modification_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now) creation_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now) # Stats - played: list[Session] = dataclasses.field(default_factory=list) + stats: Stats | None = None def compute_ngrams(self): self.ngrams = ngrams.get_text_ngrams(self.title, *self.alternatives) @@ -126,5 +132,13 @@ class Tema: self.compute_ngrams() return self + @staticmethod + def _is_score(link: Link) -> bool: + if link.content_type is not ContentType.PARTITURA: + return False + if link.link_type is LinkType.PDF: + return link.url.startswith("/") + return link.link_type is LinkType.IMAGE + def score(self) -> Link | None: - return next(filter(lambda l: l.content_type is ContentType.PARTITURA and l.url.startswith('/'), self.links), None) + return next(filter(self._is_score, self.links), None) diff --git a/folkugat_web/services/temes/query.py b/folkugat_web/services/temes/query.py index 890ae04..e165bf0 100644 --- a/folkugat_web/services/temes/query.py +++ b/folkugat_web/services/temes/query.py @@ -1,8 +1,29 @@ -from typing import Optional - +from folkugat_web.dal.sql.playlists import query as playlists_q from folkugat_web.dal.sql.temes import query as temes_q +from folkugat_web.model import sessions as sessions_model from folkugat_web.model import temes as model def get_tema_by_id(tema_id: int) -> model.Tema | None: return temes_q.get_tema_by_id(tema_id) + + +def tema_compute_stats( + tema: model.Tema, + tune_sessions_dict: dict[int, list[sessions_model.Session]] | None = None, +) -> model.Tema: + if tema.id: + if tune_sessions_dict is None: + tune_sessions_dict = playlists_q.get_tune_sessions(tema_ids=[tema.id]) + if tema.id and (tune_sessions := tune_sessions_dict.get(tema.id)): + tema.stats = model.Stats( + times_played=len(tune_sessions), + sessions_played=list(reversed(sorted(tune_sessions, key=lambda s: s.date))), + ) + return tema + + +def temes_compute_stats(temes: list[model.Tema]) -> list[model.Tema]: + tema_ids = [tema.id for tema in temes if tema.id is not None] + tune_sessions_dict = playlists_q.get_tune_sessions(tema_ids=tema_ids) + return [tema_compute_stats(tema=tema, tune_sessions_dict=tune_sessions_dict) for tema in temes] diff --git a/folkugat_web/utils.py b/folkugat_web/utils.py index b6ffc1d..36d5171 100644 --- a/folkugat_web/utils.py +++ b/folkugat_web/utils.py @@ -4,7 +4,7 @@ from typing import Protocol, Self, TypeVar class SupportsLessThan(Protocol): - def __lt__(self, other: Self) -> bool: + def __lt__(self, other: Self, /) -> bool: raise NotImplementedError()