Added indexed lists and link edition in tunes

This commit is contained in:
marc
2025-03-11 23:05:20 +01:00
parent efd26ce19d
commit a85efd0838
22 changed files with 498 additions and 137 deletions

View File

@@ -13,11 +13,31 @@ def title_editor(
return tema.title_editor(request=request, logged_in=logged_in, tema_id=tema_id)
@router.get("/api/tema/{tema_id}/editor/lyric/{lyric_idx}")
@router.get("/api/tema/{tema_id}/editor/lyric/{lyric_id}")
def lyric_editor(
request: Request,
logged_in: auth.RequireLogin,
tema_id: int,
lyric_idx: int,
lyric_id: int,
):
return tema.lyric_editor(request=request, logged_in=logged_in, tema_id=tema_id, lyric_idx=lyric_idx)
return tema.lyric_editor(
request=request,
logged_in=logged_in,
tema_id=tema_id,
lyric_id=lyric_id,
)
@router.get("/api/tema/{tema_id}/editor/link/{link_id}")
def link_editor(
request: Request,
logged_in: auth.RequireLogin,
tema_id: int,
link_id: int,
):
return tema.link_editor(
request=request,
logged_in=logged_in,
tema_id=tema_id,
link_id=link_id,
)

View File

@@ -1,13 +1,14 @@
from typing import Annotated
from fastapi import Request
from fastapi.params import Form
from fastapi.params import Form, Param
from fastapi.responses import HTMLResponse
from folkugat_web.api import router
from folkugat_web.fragments import tema, temes
from folkugat_web.model import temes as model
from folkugat_web.services import auth
from folkugat_web.services.temes import write as temes_w
from folkugat_web.services.temes.links import guess_link_type
from folkugat_web.templates import templates
@@ -45,23 +46,23 @@ def set_title(
return tema.title(request=request, tema=new_tema, logged_in=logged_in)
@router.get("/api/tema/{tema_id}/lyric/{lyric_idx}")
def lyric(request: Request, logged_in: auth.LoggedIn, tema_id: int, lyric_idx: int):
return tema.lyric(request=request, logged_in=logged_in, tema_id=tema_id, lyric_idx=lyric_idx)
@router.get("/api/tema/{tema_id}/lyric/{lyric_id}")
def lyric(request: Request, logged_in: auth.LoggedIn, tema_id: int, lyric_id: int):
return tema.lyric(request=request, logged_in=logged_in, tema_id=tema_id, lyric_id=lyric_id)
@router.put("/api/tema/{tema_id}/lyric/{lyric_idx}")
@router.put("/api/tema/{tema_id}/lyric/{lyric_id}")
def set_lyric(
request: Request,
logged_in: auth.RequireLogin,
tema_id: int,
lyric_idx: int,
lyric_id: int,
title: Annotated[str, Form()],
lyric: Annotated[str, Form()],
):
new_lyric = model.Lyrics(title=title, content=lyric.strip())
temes_w.update_lyric(tema_id=tema_id, lyric_idx=lyric_idx, lyric=new_lyric)
return tema.lyric(request=request, logged_in=logged_in, tema_id=tema_id, lyric_idx=lyric_idx)
new_lyric = model.Lyrics(id=lyric_id, title=title, content=lyric.strip())
temes_w.update_lyric(tema_id=tema_id, lyric_id=lyric_id, lyric=new_lyric)
return tema.lyric(request=request, logged_in=logged_in, tema_id=tema_id, lyric_id=lyric_id)
@router.post("/api/tema/{tema_id}/lyric")
@@ -71,14 +72,95 @@ def add_lyric(
tema_id: int,
):
new_tema = temes_w.add_lyric(tema_id=tema_id)
lyric_idx = len(new_tema.lyrics) - 1
return tema.lyric_editor(request=request, logged_in=logged_in, tema_id=tema_id, lyric_idx=lyric_idx)
lyric_id = new_tema.lyrics[-1].id
if lyric_id is None:
raise RuntimeError("Invalid lyric_id on newly created lyric!")
return tema.lyric_editor(
request=request,
logged_in=logged_in,
tema_id=tema_id,
lyric_id=lyric_id,
)
@router.delete("/api/tema/{tema_id}/lyric/{lyric_idx}")
@router.delete("/api/tema/{tema_id}/lyric/{lyric_id}")
def delete_lyric(
tema_id: int,
lyric_idx: int,
lyric_id: int,
):
temes_w.delete_lyric(tema_id=tema_id, lyric_idx=lyric_idx)
temes_w.delete_lyric(tema_id=tema_id, lyric_id=lyric_id)
return HTMLResponse()
@router.get("/api/tema/{tema_id}/link/{link_id}")
def link(request: Request, logged_in: auth.LoggedIn, tema_id: int, link_id: int):
return tema.link(request=request, logged_in=logged_in, tema_id=tema_id, link_id=link_id)
@router.put("/api/tema/{tema_id}/link/{link_id}")
def set_link(
request: Request,
logged_in: auth.RequireLogin,
tema_id: int,
link_id: int,
content_type: Annotated[model.ContentType, Form()],
url: Annotated[str, Form()] = "",
title: Annotated[str, Form()] = "",
):
link_type = guess_link_type(url)
new_link = model.Link(
id=link_id,
content_type=content_type,
link_type=link_type,
url=url,
title=title,
)
temes_w.update_link(tema_id=tema_id, link_id=link_id, link=new_link)
return tema.link(request=request, logged_in=logged_in, tema_id=tema_id, link_id=link_id)
@router.post("/api/tema/{tema_id}/link")
def add_link(
request: Request,
logged_in: auth.RequireLogin,
tema_id: int,
):
new_tema = temes_w.add_link(tema_id=tema_id)
link_id = new_tema.links[-1].id
if link_id is None:
raise RuntimeError("Invalid link_id on newly created link!")
return tema.link_editor(
request=request,
logged_in=logged_in,
tema_id=tema_id,
link_id=link_id,
)
@router.delete("/api/tema/{tema_id}/link/{link_id}")
def delete_link(
_: auth.RequireLogin,
tema_id: int,
link_id: int,
):
temes_w.delete_link(tema_id=tema_id, link_id=link_id)
return HTMLResponse()
@router.get("/api/tema/{tema_id}/link/{link_id}/icon")
def link_icon(
request: Request,
logged_in: auth.LoggedIn,
tema_id: int,
link_id: int,
content_type: Annotated[model.ContentType, Param()],
url: Annotated[str, Param()],
):
return tema.link_icon(
request=request,
logged_in=logged_in,
tema_id=tema_id,
link_id=link_id,
url=url,
content_type=content_type,
)

View File

@@ -0,0 +1,66 @@
<li id="tema-link-{{ link.id }}">
<form class="flex flex-row items-start">
{% include "fragments/tema/link_icon.html" %}
<div class="grow my-2">
<p class="text-sm text-beige">
<select id="tema-link-{{ link.id }}-content-type"
name="content_type"
value="{{ link.content_type.value.capitalize() }}"
class="border border-yellow-50 focus:outline-none
rounded
text-yellow-50 text-center
bg-brown p-0 m-0">
<option value="{{ link.content_type.value }}">
{{ link.content_type.value.capitalize() }}
</option>
{% for ct in ContentType %}
{% if ct != link.content_type %}
<option value="{{ ct.value }}">
{{ ct.value.capitalize() }}
</option>
{% endif %}
{% endfor %}
</select>
</p>
<p>
<input name="title"
placeholder="Títol de l'enllaç"
value="{{ link.title }}"
class="border border-beige focus:outline-none
rounded w-full
bg-brown p-1 my-1"
/>
</p>
<p>
<input name="url"
placeholder="URL de l'enllaç"
value="{{ link.url }}"
class="border border-beige focus:outline-none
rounded w-full
bg-brown p-1 my-1"
hx-get="/api/tema/{{ tema.id }}/link/{{ link_id }}/icon"
hx-trigger="keyup delay:500ms changed"
hx-target="#link-icon-{{ link.id }}"
hx-include="[name='content_type']"
hx-swap="outerHTML"
/>
</p>
</div>
<div class="m-2 text-sm text-beige">
<button title="Desa els canvis"
class="mx-1"
hx-put="/api/tema/{{ tema.id }}/link/{{ link.id }}"
hx-target="#tema-link-{{ link.id }}"
hx-swap="outerHTML">
<i class="fa fa-check" aria-hidden="true"></i>
</button>
<button title="Descarta els canvis"
class="mx-1"
hx-get="/api/tema/{{ tema.id }}/link/{{ link.id }}"
hx-target="#tema-link-{{ link.id }}"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
</form>
</li>

View File

@@ -1,4 +1,4 @@
<form id="tema-lyric-{{ lyric_idx }}">
<form id="tema-lyric-{{ lyric.id }}">
<h5 class="text-sm text-beige text-right">
<input name="title"
placeholder="Nom de la lletra"
@@ -9,15 +9,15 @@
/>
<button title="Desa els canvis"
class="mx-1"
hx-put="/api/tema/{{ tema.id }}/lyric/{{ lyric_idx }}"
hx-target="#tema-lyric-{{ lyric_idx }}"
hx-put="/api/tema/{{ tema.id }}/lyric/{{ lyric.id }}"
hx-target="#tema-lyric-{{ lyric.id }}"
hx-swap="outerHTML">
<i class="fa fa-check" aria-hidden="true"></i>
</button>
<button title="Descarta els canvis"
class="mx-1"
hx-get="/api/tema/{{ tema.id }}/lyric/{{ lyric_idx }}"
hx-target="#tema-lyric-{{ lyric_idx }}"
hx-get="/api/tema/{{ tema.id }}/lyric/{{ lyric.id }}"
hx-target="#tema-lyric-{{ lyric.id }}"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>
</button>

View File

@@ -1,21 +1,29 @@
<li class="flex flex-row items-start">
<div class="p-2 m-2 text-beige border border-beige rounded-md">
<a href="{{ link.url }}" target="_blank">
{% if link.subtype == LinkSubtype.SPOTIFY %}
{% include "icons/spotify.svg" %}
{% elif link.subtype == LinkSubtype.YOUTUBE %}
{% include "icons/youtube.svg" %}
{% elif link.subtype == LinkSubtype.PDF %}
{% include "icons/pdf.svg" %}
{% elif link.type == LinkType.AUDIO %}
{% include "icons/notes.svg" %}
{% else %}
{% include "icons/link.svg" %}
{% endif %}
</a>
</div>
<li id="tema-link-{{ link.id }}"
class="flex flex-row items-start">
{% include "fragments/tema/link_icon.html" %}
<div class="my-2">
<p class="text-sm text-beige">Partitura</p>
<p>Hola</p>
<p class="text-sm text-beige">
{{ link.content_type.value.capitalize() }}
</p>
{% if link.title %}
<p>{{ link.title }}</p>
{% endif %}
</div>
{% if logged_in %}
<div class="grow"></div>
<div class="m-2 text-sm text-beige">
<button title="Modifica l'enllaç"
hx-get="/api/tema/{{ tema.id }}/editor/link/{{ link.id }}"
hx-target="#tema-link-{{ link.id }}"
hx-swap="outerHTML">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
<button title="Esborra l'enllaç"
hx-delete="/api/tema/{{ tema.id }}/link/{{ link.id }}"
hx-target="#tema-link-{{ link.id }}"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
{% endif %}
</li>

View File

@@ -0,0 +1,16 @@
<div id="link-icon-{{ link.id }}"
class="p-2 m-2 text-beige border border-beige rounded-md">
<a href="{{ link.url }}" target="_blank">
{% if link.link_type == LinkType.SPOTIFY %}
{% include "icons/spotify.svg" %}
{% elif link.link_type == LinkType.YOUTUBE %}
{% include "icons/youtube.svg" %}
{% elif link.link_type == LinkType.PDF %}
{% include "icons/pdf.svg" %}
{% elif link.content_type == ContentType.AUDIO %}
{% include "icons/notes.svg" %}
{% else %}
{% include "icons/link.svg" %}
{% endif %}
</a>
</div>

View File

@@ -4,13 +4,12 @@
{% endif %}
{% if tema.links %}
{% for link in tema.links %}
<ul class="flex flex-col justify-center"
id="new-link-target">
{% set link_idx = loop.index0 %}
{% include "fragments/tema/link.html" %}
{% for link in tema.links %}
{% include "fragments/tema/link.html" %}
{% endfor %}
</ul>
{% endfor %}
{% endif %}
{% if logged_in %}
@@ -19,7 +18,7 @@
class="text-sm text-beige text-right"
hx-post="/api/tema/{{ tema.id }}/link"
hx-target="#new-link-target"
hx-swap="beforebegin">
hx-swap="beforeend transition:true">
<i class="fa fa-plus" aria-hidden="true"></i>
Afegeix un enllaç
</button>

View File

@@ -1,18 +1,18 @@
<div id="tema-lyric-{{ lyric_idx }}">
<div id="tema-lyric-{{ lyric.id }}">
<h5 class="text-sm text-beige text-right">
{{ lyric.title }}
{% if logged_in %}
<button title="Modifica la lletra"
class="mx-1"
hx-get="/api/tema/{{ tema.id }}/editor/lyric/{{ lyric_idx }}"
hx-target="#tema-lyric-{{ lyric_idx }}"
hx-get="/api/tema/{{ tema.id }}/editor/lyric/{{ lyric.id }}"
hx-target="#tema-lyric-{{ lyric.id }}"
hx-swap="outerHTML">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
<button title="Esborra la lletra"
class="mx-1"
hx-delete="/api/tema/{{ tema.id }}/lyric/{{ lyric_idx }}"
hx-target="#tema-lyric-{{ lyric_idx }}"
hx-delete="/api/tema/{{ tema.id }}/lyric/{{ lyric.id }}"
hx-target="#tema-lyric-{{ lyric.id }}"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>
</button>

View File

@@ -4,7 +4,6 @@
{% if tema.lyrics %}
{% for lyric in tema.lyrics %}
{% set lyric_idx = loop.index0 %}
{% include "fragments/tema/lyric.html" %}
{% endfor %}
{% endif %}

View File

@@ -4,18 +4,6 @@
{% include "fragments/tema/title.html" %}
<div class="text-left">
<!-- {% if tema.scores() %} -->
<!-- temes -->
<!-- <h4 class="text-xl text-beige">Tema</h4> -->
<!-- <hr class="h-px mt-1 mb-3 bg-beige border-0"> -->
<!-- {% for file in tema.files %} -->
<!-- {% if file.type == FileType.PDF %} -->
<!-- {% set pdf_url = file.path %} -->
<!-- {% include "fragments/pdf_viewer.html" %} -->
<!-- {% endif %} -->
<!-- {% endfor %} -->
<!-- {% endif %} -->
{% include "fragments/tema/lyrics.html" %}
{% include "fragments/tema/links.html" %}

View File

@@ -13,16 +13,16 @@
{% for link in tema.links %}
<li class="p-2 m-2 text-beige border border-beige rounded-md">
<a href="{{ link.url }}" target="_blank">
{% if link.type == LinkType.AUDIO %}
{% if link.subtype == LinkSubtype.SPOTIFY %}
{% if link.content_type == ContentType.AUDIO %}
{% if link.link_type == LinkType.SPOTIFY %}
{% include "icons/spotify.svg" %}
{% elif link.subtype == LinkSubtype.YOUTUBE %}
{% elif link.link_type == LinkType.YOUTUBE %}
{% include "icons/youtube.svg" %}
{% else %}
{% include "icons/notes.svg" %}
{% endif %}
{% elif link.type == LinkType.SCORE %}
{% if link.subtype == LinkSubtype.PDF %}
{% elif link.content_type == ContentType.PARTITURA %}
{% if link.link_type == LinkType.PDF %}
{% include "icons/pdf.svg" %}
{% else %}
{% include "icons/link.svg" %}

View File

@@ -1,6 +1,7 @@
import datetime
import json
from folkugat_web.model import IndexedList
from folkugat_web.model import temes as model
@@ -27,9 +28,9 @@ def row_to_tema(row: tuple) -> model.Tema:
return model.Tema(
id=row[0],
title=row[1],
properties=list(map(model.Property.from_dict, json.loads(row[2]))),
links=list(map(model.Link.from_dict, json.loads(row[3]))),
lyrics=list(map(model.Lyrics.from_dict, json.loads(row[4]))),
properties=IndexedList(map(model.Property.from_dict, json.loads(row[2]))),
links=IndexedList(map(model.Link.from_dict, json.loads(row[3]))),
lyrics=IndexedList(map(model.Lyrics.from_dict, json.loads(row[4]))),
alternatives=json.loads(row[5]),
ngrams=cell_to_ngrams(row[6]),
modification_date=datetime.datetime.fromisoformat(row[7]),

View File

@@ -1,3 +1,4 @@
from folkugat_web.model import IndexedList
from folkugat_web.model import temes as model
TEMES = [
@@ -9,26 +10,29 @@ TEMES = [
# ---
model.Tema(
title="Pasdoble de Muntanya (Cançó amb el nom molt llarg)",
links=[
links=IndexedList([
model.Link(
type=model.LinkType.AUDIO,
subtype=model.LinkSubtype.SPOTIFY,
id=0,
content_type=model.ContentType.AUDIO,
link_type=model.LinkType.SPOTIFY,
url="https://open.spotify.com/track/4j9Krf19c5USmMvVUCoeWa?si=3023d1d83f814886",
title="Versió de l'Orquestrina Trama",
),
],
]),
hidden=False,
).with_ngrams(),
# ---
model.Tema(
title="Astrid Waltz",
alternatives=["vals"],
links=[
links=IndexedList([
model.Link(
type=model.LinkType.OTHER,
subtype=None,
id=0,
content_type=model.ContentType.OTHER,
link_type=None,
url="https://marc.sastre.cat/folkugat",
)
],
]),
hidden=False,
).with_ngrams(),
# ---
@@ -45,13 +49,14 @@ TEMES = [
# ---
model.Tema(
title="El Gitano",
links=[
links=IndexedList([
model.Link(
type=model.LinkType.SCORE,
subtype=model.LinkSubtype.PDF,
id=0,
content_type=model.ContentType.PARTITURA,
link_type=model.LinkType.PDF,
url="/db/temes/1/tema.pdf",
)
],
]),
hidden=False,
).with_ngrams(),
# ---
@@ -68,8 +73,9 @@ TEMES = [
# ---
model.Tema(
title="Malaguenya de Barxeta",
lyrics=[
lyrics=IndexedList([
model.Lyrics(
id=0,
title="Malaguenya de Barxeta",
content="""
Mira si he corregut terres
@@ -93,13 +99,14 @@ d'allà on renaix de les cendres
el meu País Valencià.
""".strip(),
),
],
properties=[
]),
properties=IndexedList([
model.Property(
model.PropertyField.AUTOR,
"Pep Jimeno 'Botifarra'"
id=0,
field=model.PropertyField.AUTOR,
value="Pep Jimeno 'Botifarra'"
)
],
]),
hidden=False,
).with_ngrams(),
]

View File

@@ -25,11 +25,10 @@ def footer(request, value, logged_in):
def nota(request):
response = templates.TemplateResponse(
return templates.TemplateResponse(
"fragments/nota/nota.html",
{
"request": request,
}
},
headers={"HX-Refresh": "true"},
)
response.headers["HX-Refresh"] = "true"
return response

View File

@@ -3,6 +3,7 @@ from typing import Optional
from fastapi import 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
from folkugat_web.templates import templates
@@ -33,13 +34,11 @@ def title_editor(request: Request, logged_in: bool, tema_id: int):
)
def lyric(request: Request, logged_in: bool, tema_id: int, lyric_idx: 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 len(tema.lyrics) < lyric_idx:
raise ValueError(f'Lyric index out of bounds')
lyric = tema.lyrics[lyric_idx]
lyric = tema.lyrics.get(lyric_id)
return templates.TemplateResponse(
"fragments/tema/lyric.html",
@@ -47,19 +46,17 @@ def lyric(request: Request, logged_in: bool, tema_id: int, lyric_idx: int):
"request": request,
"logged_in": logged_in,
"tema": tema,
"lyric_idx": lyric_idx,
"lyric_id": lyric_id,
"lyric": lyric,
}
)
def lyric_editor(request: Request, logged_in: bool, tema_id: int, lyric_idx: 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 len(tema.lyrics) < lyric_idx:
raise ValueError(f'Lyric index out of bounds')
lyric = tema.lyrics[lyric_idx]
lyric = tema.lyrics.get(lyric_id)
return templates.TemplateResponse(
"fragments/tema/editor/lyric.html",
@@ -67,7 +64,75 @@ def lyric_editor(request: Request, logged_in: bool, tema_id: int, lyric_idx: int
"request": request,
"logged_in": logged_in,
"tema": tema,
"lyric_idx": lyric_idx,
"lyric_id": lyric_id,
"lyric": lyric,
}
)
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}")
link = tema.links.get(link_id)
return templates.TemplateResponse(
"fragments/tema/link.html",
{
"request": request,
"logged_in": logged_in,
"tema": tema,
"link_id": link_id,
"link": link,
"LinkType": model.LinkType,
"ContentType": model.ContentType,
}
)
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}")
link = tema.links.get(link_id)
return templates.TemplateResponse(
"fragments/tema/editor/link.html",
{
"request": request,
"logged_in": logged_in,
"tema": tema,
"link_id": link_id,
"link": link,
"LinkType": model.LinkType,
"ContentType": model.ContentType,
}
)
def link_icon(
request: Request,
logged_in: bool,
tema_id: int,
link_id: int,
url: str,
content_type: model.ContentType,
):
link = model.Link(
id=link_id,
content_type=content_type,
link_type=guess_link_type(url),
url=url,
)
return templates.TemplateResponse(
"fragments/tema/link_icon.html",
{
"request": request,
"logged_in": logged_in,
"tema_id": tema_id,
"link_id": link_id,
"link": link,
"LinkType": model.LinkType,
"ContentType": model.ContentType,
}
)

View File

@@ -25,8 +25,8 @@ def temes_busca_result(request: Request, tema: model.Tema, logged_in: bool):
"request": request,
"logged_in": logged_in,
"tema": tema,
"LinkSubtype": model.LinkSubtype,
"LinkType": model.LinkType,
"ContentType": model.ContentType,
}
).body.decode('utf-8')
@@ -47,8 +47,8 @@ def tema(request: Request, tema_id: int, logged_in: bool):
"request": request,
"logged_in": logged_in,
"Pages": Pages,
"LinkSubtype": model.LinkSubtype,
"LinkType": model.LinkType,
"ContentType": model.ContentType,
"tema": tema,
}
)

View File

@@ -0,0 +1 @@
from ._base import IndexedList

View File

@@ -0,0 +1,36 @@
import dataclasses
from typing import Optional, TypeVar
@dataclasses.dataclass
class WithId:
id: Optional[int]
T = TypeVar("T", bound=WithId)
class IndexedList(list[T]):
def append(self, _item: T) -> None:
if _item.id is None:
_item.id = max((i.id or 0 for i in self), default=0) + 1
return super().append(_item)
def find_idx(self, _id: int) -> int:
try:
i, _ = next(filter(lambda it: it[1].id == _id, enumerate(self)))
except StopIteration:
raise ValueError(f"Could not find item with id {_id}")
return i
def get(self, _id: int) -> T:
i = self.find_idx(_id)
return self.__getitem__(i)
def delete(self, _id: int) -> None:
i = self.find_idx(_id)
return super().__delitem__(i)
def replace(self, _id: int, _obj: T) -> None:
i = self.find_idx(_id)
super().__setitem__(i, _obj)

View File

@@ -5,16 +5,18 @@ from typing import Optional
from folkugat_web.services import ngrams
from ._base import IndexedList, WithId
NGrams = dict[int, list[str]]
class ContentType(enum.Enum):
PARTITURA = "partitura"
AUDIO = "àudio"
OTHER = "enllaç"
class LinkType(enum.Enum):
SCORE = "score"
AUDIO = "audio"
OTHER = "other"
class LinkSubtype(enum.Enum):
# Score
PDF = "pdf"
IMAGE = "image"
@@ -24,16 +26,17 @@ class LinkSubtype(enum.Enum):
@dataclasses.dataclass
class Link:
type: LinkType
subtype: Optional[LinkSubtype]
class Link(WithId):
content_type: ContentType
link_type: Optional[LinkType]
url: str
title: str = ""
def to_dict(self):
return dict(
type=self.type.value,
subtype=self.subtype.value if self.subtype else None,
id=self.id,
content_type=self.content_type.value,
link_type=self.link_type.value if self.link_type else None,
url=self.url,
title=self.title,
)
@@ -41,8 +44,9 @@ class Link:
@classmethod
def from_dict(cls, d):
return cls(
type=LinkType(d["type"]),
subtype=LinkSubtype(d["subtype"]) if d["subtype"] else None,
id=d["id"],
content_type=ContentType(d["content_type"]),
link_type=LinkType(d["link_type"]) if d["link_type"] else None,
url=d["url"],
title=d["title"],
)
@@ -56,12 +60,13 @@ class PropertyField(enum.Enum):
@dataclasses.dataclass
class Property:
class Property(WithId):
field: PropertyField
value: str
def to_dict(self):
return dict(
id=self.id,
field=self.field.value,
value=self.value,
)
@@ -69,18 +74,20 @@ class Property:
@classmethod
def from_dict(cls, d):
return cls(
id=d["id"],
field=PropertyField(d["field"]),
value=d["value"],
)
@dataclasses.dataclass
class Lyrics:
class Lyrics(WithId):
title: str
content: str
def to_dict(self):
return dict(
id=self.id,
title=self.title,
content=self.content,
)
@@ -88,6 +95,7 @@ class Lyrics:
@classmethod
def from_dict(cls, d):
return cls(
id=d["id"],
title=d["title"],
content=d["content"],
)
@@ -98,9 +106,9 @@ class Tema:
id: Optional[int] = None
# Info
title: str = ""
properties: list[Property] = dataclasses.field(default_factory=list)
links: list[Link] = dataclasses.field(default_factory=list)
lyrics: list[Lyrics] = dataclasses.field(default_factory=list)
properties: IndexedList[Property] = dataclasses.field(default_factory=IndexedList)
links: IndexedList[Link] = dataclasses.field(default_factory=IndexedList)
lyrics: IndexedList[Lyrics] = dataclasses.field(default_factory=IndexedList)
# Search related
alternatives: list[str] = dataclasses.field(default_factory=list)
ngrams: NGrams = dataclasses.field(default_factory=dict)
@@ -115,9 +123,3 @@ class Tema:
def with_ngrams(self):
self.compute_ngrams()
return self
def scores(self):
return [link for link in self.links if link.type is LinkType.SCORE]
def audios(self):
return [link for link in self.links if link.type is LinkType.AUDIO]

View File

@@ -0,0 +1,32 @@
import re
from typing import Optional
from folkugat_web.model import temes as model
IMAGE_FORMATS_RE = "|".join([
'jpg', 'jpeg', 'png'
])
LINK_RES = {
model.LinkType.IMAGE: [
re.compile(rf"^.*\.({IMAGE_FORMATS_RE})$")
],
model.LinkType.PDF: [
re.compile(r"^.*\.pdf$")
],
model.LinkType.SPOTIFY: [
re.compile(r"^.*spotify\.com.*$")
],
model.LinkType.YOUTUBE: [
re.compile(r"^.*youtube\.com.*$"),
re.compile(r"^.*youtu\.be.*$"),
]
}
def guess_link_type(url: str) -> Optional[model.LinkType]:
for link_type, regexes in LINK_RES.items():
for regex in regexes:
if regex.match(url):
return link_type
return None

View File

@@ -20,29 +20,25 @@ def update_title(tema_id: int, title: str) -> model.Tema:
return new_tema
def update_lyric(tema_id: int, lyric_idx: int, lyric: model.Lyrics) -> model.Tema:
def update_lyric(tema_id: int, lyric_id: int, lyric: model.Lyrics) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
if lyric_idx > len(tema.lyrics):
raise ValueError(f'Lyric index out of bounds')
tema.lyrics[lyric_idx] = lyric
tema.lyrics.replace(lyric_id, lyric)
temes_w.update_tema(tema=tema, con=con)
return tema
def delete_lyric(tema_id: int, lyric_idx: int) -> model.Tema:
def delete_lyric(tema_id: int, lyric_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
if lyric_idx > len(tema.lyrics):
raise ValueError(f'Lyric index out of bounds')
del tema.lyrics[lyric_idx]
tema.lyrics.delete(lyric_id)
temes_w.update_tema(tema=tema, con=con)
return tema
@@ -54,9 +50,51 @@ def add_lyric(tema_id: int) -> model.Tema:
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.lyrics.append(model.Lyrics(
id=None,
title=tema.title,
content="",
))
temes_w.update_tema(tema=tema, con=con)
return tema
def update_link(tema_id: int, link_id: int, link: model.Link) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.links.replace(link_id, link)
temes_w.update_tema(tema=tema, con=con)
return tema
def delete_link(tema_id: int, link_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.links.delete(link_id)
temes_w.update_tema(tema=tema, con=con)
return tema
def add_link(tema_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.links.append(model.Link(
id=None,
title="",
url="",
content_type=model.ContentType.OTHER,
link_type=None,
))
temes_w.update_tema(tema=tema, con=con)
return tema