Afegir temes coocurrents
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
#+title: Readme
|
#+title: Readme
|
||||||
|
|
||||||
* Tasques
|
* 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 Suport per a diverses organitzacions (no només jam de Sant Cugat)
|
||||||
** TODO Usuaris i permisos granulars
|
** TODO Usuaris i permisos granulars
|
||||||
** Lilypond support
|
** Lilypond support
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import dataclasses
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import HTTPException, Request
|
from fastapi import HTTPException, Request
|
||||||
@@ -5,6 +6,7 @@ from fastapi.params import Form
|
|||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from folkugat_web.api.router import get_router
|
from folkugat_web.api.router import get_router
|
||||||
from folkugat_web.fragments import tema, temes
|
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 auth
|
||||||
from folkugat_web.services import files as files_service
|
from folkugat_web.services import files as files_service
|
||||||
from folkugat_web.services.temes import links as links_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}")
|
@router.get("/api/tema/{tema_id}")
|
||||||
def contingut(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
def contingut(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
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 = (
|
tema = (
|
||||||
FnChain.transform(tema) |
|
FnChain.transform(tema) |
|
||||||
temes_q.tema_compute_stats |
|
temes_q.tema_compute_stats |
|
||||||
|
temes_q.tema_compute_played_with |
|
||||||
|
augment_played_with |
|
||||||
links_service.add_links_to_tema |
|
links_service.add_links_to_tema |
|
||||||
lyrics_service.add_lyrics_to_tema |
|
lyrics_service.add_lyrics_to_tema |
|
||||||
scores_service.add_scores_to_tema |
|
scores_service.add_scores_to_tema |
|
||||||
|
|||||||
@@ -657,6 +657,10 @@ video {
|
|||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ml-4 {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-auto {
|
.ml-auto {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
<div id="tema-property-{{ property.id }}"
|
<div id="tema-property-{{ property.id }}"
|
||||||
class="flex flex-row">
|
class="flex flex-row items-center">
|
||||||
<div class="px-2">
|
<div class="px-2">
|
||||||
<i>{{ property.field.value.capitalize() }}:</i>
|
<i>{{ property.field.value.capitalize() }}:</i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<a class="bg-beige text-white rounded m-1 px-2"
|
||||||
|
href="/temes?query=&properties={{ property.value }}">
|
||||||
{{ property.value }}
|
{{ property.value }}
|
||||||
</div>
|
</a>
|
||||||
|
|
||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
|
|||||||
@@ -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 %}
|
{% if tema.stats %}
|
||||||
<h4 class="pt-4 text-xl mt-3 text-beige">Estadístiques</h4>
|
<div class="flex flex-col">
|
||||||
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
{% if tema.played_with %}
|
||||||
<div>
|
<div class="my-2 flex flex-row items-center">
|
||||||
<p>
|
<i class="mx-2 flex-none">{% include "icons/notes-small.svg" %}</i>
|
||||||
Aquest tema ha sigut tocat en
|
<p>
|
||||||
{% if tema.stats.times_played == 1%}
|
S'ha tocat juntament amb:
|
||||||
una sessió.
|
</p>
|
||||||
{% else %}
|
</div>
|
||||||
{{ tema.stats.times_played }} sessions.
|
<ol class="ml-4 flex flex-col justify-center">
|
||||||
{% endif %}
|
{% for co_tema in tema.played_with %}
|
||||||
</p>
|
{% set tema = co_tema.tema %}
|
||||||
<p class="py-2">
|
{% include "fragments/tema/played_with.html" %}
|
||||||
S'ha tocat a les sessions següents:
|
{% endfor %}
|
||||||
<ol class="flex flex-col items-center justify-center">
|
</ol>
|
||||||
{% for session in tema.stats.sessions_played %}
|
{% endif %}
|
||||||
<li class="border rounded border-beige
|
<div class="my-2 flex flex-row items-center">
|
||||||
flex flex-row grow
|
<i class="mx-2 flex-none">{% include "icons/music-box.svg" %}</i>
|
||||||
p-2 m-2 w-full max-w-xl
|
<p>
|
||||||
relative">
|
S'ha tocat en
|
||||||
<a href="/session/{{session.id}}">
|
{% if tema.stats.times_played == 1%}
|
||||||
<div class="flex flex-row grow items-center">
|
una sessió:
|
||||||
<div class="flex-1">
|
{% else %}
|
||||||
<a href="/sessio/{{session.id}}/">
|
{{ tema.stats.times_played }} sessions:
|
||||||
{% set dn = date_names(session.date) %}
|
{% endif %}
|
||||||
{{ dn.day_name }} {{ dn.day }} {{ dn.month_name }}
|
</p>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
<ol class="ml-4 flex flex-col justify-center">
|
||||||
<div class="ml-auto">
|
{% for session in tema.stats.sessions_played %}
|
||||||
<a title="Més informació"
|
<li class="flex flex-row grow
|
||||||
class="text-beige mx-1"
|
my-1">
|
||||||
href="/sessio/{{session.id}}/">
|
<a class="flex flex-row grow items-center"
|
||||||
<i class="fa fa-chevron-right" aria-hidden="true"></i>
|
href="/sessio/{{session.id}}">
|
||||||
</a>
|
<i class="mx-2 text-beige flex-none">{% include "icons/calendar.svg" %}</i>
|
||||||
</div>
|
<p>
|
||||||
</div>
|
{% set dn = date_names(session.date) %} {{ dn.day_name }} {{ dn.day }} {{ dn.month_name }}
|
||||||
</li>
|
</p>
|
||||||
{% endfor %}
|
</a>
|
||||||
</ol>
|
</li>
|
||||||
</p>
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div>
|
||||||
|
<i>No s'ha tocat a cap jam (encara)</i>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
5
folkugat_web/assets/templates/icons/notes-small.svg
Normal file
5
folkugat_web/assets/templates/icons/notes-small.svg
Normal 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 |
@@ -3,7 +3,9 @@ from typing import TypedDict
|
|||||||
|
|
||||||
from folkugat_web.dal.sql import Connection, get_connection
|
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.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 playlists as model
|
||||||
|
from folkugat_web.model import temes as temes_model
|
||||||
from folkugat_web.model.sessions import Session
|
from folkugat_web.model.sessions import Session
|
||||||
from folkugat_web.utils import groupby
|
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],
|
key_fn=lambda row: row[0],
|
||||||
group_fn=lambda rows: list(sessions_conversion.row_to_session(row[1:]) for row in rows)
|
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
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
@@ -110,6 +112,7 @@ class Tema:
|
|||||||
creation_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
|
creation_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
|
||||||
# Stats
|
# Stats
|
||||||
stats: Stats | None = None
|
stats: Stats | None = None
|
||||||
|
played_with: list[CommonlyPlayedTema] | None = None
|
||||||
|
|
||||||
def ngrams(self) -> NGrams:
|
def ngrams(self) -> NGrams:
|
||||||
return ngrams.get_text_ngrams(self.title, *self.alternatives)
|
return ngrams.get_text_ngrams(self.title, *self.alternatives)
|
||||||
@@ -149,6 +152,12 @@ class Tema:
|
|||||||
return bool(self.lyrics)
|
return bool(self.lyrics)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class CommonlyPlayedTema:
|
||||||
|
tema: Tema
|
||||||
|
count: int
|
||||||
|
|
||||||
|
|
||||||
class TemaCols(enum.Enum):
|
class TemaCols(enum.Enum):
|
||||||
NOM = "nom"
|
NOM = "nom"
|
||||||
COPS_TOCAT = "cops_tocat"
|
COPS_TOCAT = "cops_tocat"
|
||||||
|
|||||||
@@ -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]
|
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)
|
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]
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user