Added stats
This commit is contained in:
@@ -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 }}
|
||||
{% 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 %}
|
||||
{% if session.venue.name %}
|
||||
a {{ session.venue.name }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -13,5 +13,6 @@
|
||||
{% include "fragments/tema/lyrics.html" %}
|
||||
{% include "fragments/tema/links.html" %}
|
||||
{% include "fragments/tema/properties.html" %}
|
||||
{% include "fragments/tema/stats.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
42
folkugat_web/assets/templates/fragments/tema/stats.html
Normal file
42
folkugat_web/assets/templates/fragments/tema/stats.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% if tema.stats %}
|
||||
<h4 class="pt-4 text-xl mt-3 text-beige">Estadístiques</h4>
|
||||
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
||||
<div>
|
||||
<p>
|
||||
Aquest tema ha sigut tocat en
|
||||
{% if tema.stats.times_played == 1%}
|
||||
una sessió.
|
||||
{% else %}
|
||||
{{ tema.stats.times_played }} sessions.
|
||||
{% endif %}
|
||||
</p>
|
||||
<p class="py-2">
|
||||
S'ha tocat a les sessions següents:
|
||||
<ol class="flex flex-col items-center justify-center">
|
||||
{% for session in tema.stats.sessions_played %}
|
||||
<li class="border rounded border-beige
|
||||
flex flex-row grow
|
||||
p-2 m-2 w-full max-w-xl
|
||||
relative">
|
||||
<a href="/session/{{session.id}}">
|
||||
<div class="flex flex-row grow items-center">
|
||||
<div class="flex-1">
|
||||
<a href="/sessio/{{session.id}}/">
|
||||
{% set dn = date_names(session.date) %}
|
||||
{{ dn.day_name }} {{ dn.day }} {{ dn.month_name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<a title="Més informació"
|
||||
class="text-beige mx-1"
|
||||
href="/sessio/{{session.id}}/">
|
||||
<i class="fa fa-chevron-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -4,6 +4,7 @@
|
||||
<tr class="border-b border-beige">
|
||||
<td class="font-bold py-2 px-4">Nom</td>
|
||||
<td class="font-bold py-2 px-4">Enllaços</td>
|
||||
<td class="font-bold py-2 px-4">Cops tocat</td>
|
||||
</tr>
|
||||
{% for tema in temes %}
|
||||
<tr class="border-b border-beige">
|
||||
@@ -15,6 +16,13 @@
|
||||
<td class="py-2 px-4">
|
||||
{% include "fragments/temes/result_links.html" %}
|
||||
</td>
|
||||
<td class="py-2 px-4">
|
||||
{% if tema.stats is none %}
|
||||
-
|
||||
{% else %}
|
||||
{{ tema.stats.times_played }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
@@ -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)
|
||||
))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user