Afegir temes coocurrents

This commit is contained in:
marc
2025-10-24 00:05:56 +02:00
parent 31aeb09dd9
commit 1909af9107
10 changed files with 171 additions and 43 deletions

View File

@@ -1,7 +1,7 @@
#+title: Readme
* Tasques
** TODO Ordenar els resultats de la cerca de temes
** DONE Ordenar els resultats de la cerca de temes
** TODO Suport per a diverses organitzacions (no només jam de Sant Cugat)
** TODO Usuaris i permisos granulars
** Lilypond support

View File

@@ -1,3 +1,4 @@
import dataclasses
from typing import Annotated
from fastapi import HTTPException, Request
@@ -5,6 +6,7 @@ from fastapi.params import Form
from fastapi.responses import HTMLResponse
from folkugat_web.api.router import get_router
from folkugat_web.fragments import tema, temes
from folkugat_web.model.temes import Tema
from folkugat_web.services import auth
from folkugat_web.services import files as files_service
from folkugat_web.services.temes import links as links_service
@@ -32,6 +34,21 @@ def page(request: Request, logged_in: auth.LoggedIn, tema_id: int):
)
def augment_played_with(tema: Tema) -> Tema:
if tema.played_with:
tema.played_with = [
dataclasses.replace(
co_tema,
tema=(
FnChain.transform(co_tema.tema) |
scores_service.add_scores_to_tema |
properties_service.add_properties_to_tema
).result()
) for co_tema in tema.played_with
]
return tema
@router.get("/api/tema/{tema_id}")
def contingut(request: Request, logged_in: auth.LoggedIn, tema_id: int):
tema = temes_q.get_tema_by_id(tema_id)
@@ -40,6 +57,8 @@ def contingut(request: Request, logged_in: auth.LoggedIn, tema_id: int):
tema = (
FnChain.transform(tema) |
temes_q.tema_compute_stats |
temes_q.tema_compute_played_with |
augment_played_with |
links_service.add_links_to_tema |
lyrics_service.add_lyrics_to_tema |
scores_service.add_scores_to_tema |

View File

@@ -657,6 +657,10 @@ video {
margin-left: 0.5rem;
}
.ml-4 {
margin-left: 1rem;
}
.ml-auto {
margin-left: auto;
}

View File

@@ -0,0 +1,40 @@
<li class="flex flex-col
items-center
m-4 rounded-lg
bg-white">
<div class="flex flex-col items-start py-4 text-left w-full max-w-[655px]">
<div class="px-4 text-black font-bold">
<a href="/tema/{{ tema.id }}">
{{ tema.title }}
</a>
</div>
{% if tema.properties %}
<ul class="flex flex-wrap text-sm px-3">
{% for property in tema.properties %}
<a class="bg-beige text-white rounded
m-1 px-2"
href="/temes?properties={{ property.value }}">
{{ property.value }}
</a>
{% endfor %}
</ul>
{% endif %}
{% if tema.main_score() and tema.main_score().preview_url %}
<a href="/tema/{{ tema.id }}">
<img class="px-4 pb-2"
src="{{ tema.main_score().preview_url }}" />
</a>
{% endif %}
<div class="flex flex-row w-full">
<div class="flex flex-row items-center
row-0 px-4 text-sm text-gray-400">
<i class="mx-1">{% include "icons/music-box.svg" %}</i>
{% if co_tema.count == 1 %}
1 cop
{% else %}
{{ co_tema.count }} cops
{% endif %}
</div>
</div>
</div>
</li>

View File

@@ -1,11 +1,12 @@
<div id="tema-property-{{ property.id }}"
class="flex flex-row">
class="flex flex-row items-center">
<div class="px-2">
<i>{{ property.field.value.capitalize() }}:</i>
</div>
<div>
<a class="bg-beige text-white rounded m-1 px-2"
href="/temes?query=&properties={{ property.value }}">
{{ property.value }}
</div>
</a>
{% if logged_in %}
<div class="grow"></div>

View File

@@ -1,42 +1,49 @@
<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">
{% 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>
<div class="flex flex-col">
{% if tema.played_with %}
<div class="my-2 flex flex-row items-center">
<i class="mx-2 flex-none">{% include "icons/notes-small.svg" %}</i>
<p>
Aquest tema ha sigut tocat en
S'ha tocat juntament amb:
</p>
</div>
<ol class="ml-4 flex flex-col justify-center">
{% for co_tema in tema.played_with %}
{% set tema = co_tema.tema %}
{% include "fragments/tema/played_with.html" %}
{% endfor %}
</ol>
{% endif %}
<div class="my-2 flex flex-row items-center">
<i class="mx-2 flex-none">{% include "icons/music-box.svg" %}</i>
<p>
S'ha tocat en
{% if tema.stats.times_played == 1%}
una sessió.
una sessió:
{% else %}
{{ tema.stats.times_played }} sessions.
{{ 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">
</div>
<ol class="ml-4 flex flex-col 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 }}
<li class="flex flex-row grow
my-1">
<a class="flex flex-row grow items-center"
href="/sessio/{{session.id}}">
<i class="mx-2 text-beige flex-none">{% include "icons/calendar.svg" %}</i>
<p>
{% set dn = date_names(session.date) %} {{ dn.day_name }} {{ dn.day }} {{ dn.month_name }}
</p>
</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>
{% else %}
<div>
<i>No s'ha tocat a cap jam (encara)</i>
</div>
{% endif %}

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-music-note-beamed" viewBox="0 0 16 16">
<path d="M6 13c0 1.105-1.12 2-2.5 2S1 14.105 1 13c0-1.104 1.12-2 2.5-2s2.5.896 2.5 2m9-2c0 1.105-1.12 2-2.5 2s-2.5-.895-2.5-2 1.12-2 2.5-2 2.5.895 2.5 2"/>
<path fill-rule="evenodd" d="M14 11V2h1v9zM6 3v10H5V3z"/>
<path d="M5 2.905a1 1 0 0 1 .9-.995l8-.8a1 1 0 0 1 1.1.995V3L5 4z"/>
</svg>

After

Width:  |  Height:  |  Size: 426 B

View File

@@ -3,7 +3,9 @@ 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.dal.sql.temes.conversion import row_to_tema
from folkugat_web.model import playlists as model
from folkugat_web.model import temes as temes_model
from folkugat_web.model.sessions import Session
from folkugat_web.utils import groupby
@@ -76,3 +78,36 @@ def get_tune_sessions(tema_ids: list[int], con: Connection | None = None) -> dic
key_fn=lambda row: row[0],
group_fn=lambda rows: list(sessions_conversion.row_to_session(row[1:]) for row in rows)
))
CommonlyPlayedTuneRow = tuple[int, str, str, str, str, int, int]
def get_commonly_played_tunes(
tema_id: int,
con: Connection | None = None,
) -> list[temes_model.CommonlyPlayedTema]:
query = """
SELECT
id, title, alternatives, creation_date, modification_date, hidden, count
FROM (
SELECT tema_id, count(*) count FROM playlists p JOIN (
SELECT session_id, set_id
FROM playlists
WHERE tema_id = ?
) s
ON p.session_id == s.session_id AND p.set_id == s.set_id
WHERE tema_id != ?
GROUP BY tema_id
) common JOIN temes t ON common.tema_id == t.id
"""
with get_connection(con) as con:
cur = con.cursor()
_ = cur.execute(query, [tema_id, tema_id])
result_rows: Iterable[CommonlyPlayedTuneRow] = cur.fetchall()
return [
temes_model.CommonlyPlayedTema(
tema=row_to_tema(row[:6]),
count=row[6],
) for row in result_rows
]

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import dataclasses
import datetime
import enum
@@ -110,6 +112,7 @@ class Tema:
creation_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
# Stats
stats: Stats | None = None
played_with: list[CommonlyPlayedTema] | None = None
def ngrams(self) -> NGrams:
return ngrams.get_text_ngrams(self.title, *self.alternatives)
@@ -149,6 +152,12 @@ class Tema:
return bool(self.lyrics)
@dataclasses.dataclass
class CommonlyPlayedTema:
tema: Tema
count: int
class TemaCols(enum.Enum):
NOM = "nom"
COPS_TOCAT = "cops_tocat"

View File

@@ -35,3 +35,11 @@ def temes_compute_stats(temes: Iterable[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]
def tema_compute_played_with(
tema: model.Tema,
) -> model.Tema:
if tema.id:
tema.played_with = playlists_q.get_commonly_played_tunes(tema_id=tema.id)
return tema