Migrated links, lyrics and properties
This commit is contained in:
@@ -1 +1 @@
|
||||
from . import editor, index
|
||||
from . import editor, index, links, lyrics, properties
|
||||
|
||||
@@ -11,78 +11,3 @@ def title_editor(
|
||||
tema_id: int,
|
||||
):
|
||||
return tema.title_editor(request=request, logged_in=logged_in, tema_id=tema_id)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/editor/lyric/{lyric_id}")
|
||||
def lyric_editor(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
lyric_id: int,
|
||||
):
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/editor/link/{link_id}/url")
|
||||
def link_editor_url_input(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
link_id: int,
|
||||
):
|
||||
return tema.link_editor_url(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
tema_id=tema_id,
|
||||
link_id=link_id,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/editor/link/{link_id}/file")
|
||||
def link_editor_file_input(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
link_id: int,
|
||||
):
|
||||
return tema.link_editor_file(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
tema_id=tema_id,
|
||||
link_id=link_id,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/editor/property/{property_id}")
|
||||
def property_editor(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
property_id: int,
|
||||
):
|
||||
return tema.property_editor(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
tema_id=tema_id,
|
||||
property_id=property_id,
|
||||
)
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Request, UploadFile
|
||||
from fastapi.params import File, Form, Param
|
||||
from fastapi import HTTPException, Request
|
||||
from fastapi.params import Form
|
||||
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, files
|
||||
from folkugat_web.services import auth
|
||||
from folkugat_web.services.temes import links as links_service
|
||||
from folkugat_web.services.temes import lyrics as lyrics_service
|
||||
from folkugat_web.services.temes import properties as properties_service
|
||||
from folkugat_web.services.temes import query as temes_q
|
||||
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
|
||||
from folkugat_web.utils import FnChain
|
||||
|
||||
|
||||
@router.get("/tema/{tema_id}")
|
||||
@@ -27,7 +30,17 @@ def page(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
||||
|
||||
@router.get("/api/tema/{tema_id}")
|
||||
def contingut(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
||||
return temes.tema(request, tema_id, logged_in)
|
||||
tema = temes_q.get_tema_by_id(tema_id)
|
||||
if not tema:
|
||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
||||
tema = (
|
||||
FnChain.transform(tema) |
|
||||
temes_q.tema_compute_stats |
|
||||
links_service.add_links_to_tema |
|
||||
lyrics_service.add_lyrics_to_tema |
|
||||
properties_service.add_properties_to_tema
|
||||
).result()
|
||||
return temes.tema(request, logged_in, tema)
|
||||
|
||||
|
||||
@router.delete("/api/tema/{tema_id}")
|
||||
@@ -62,190 +75,6 @@ def set_title(
|
||||
return tema.title(request=request, tema=new_tema, logged_in=logged_in)
|
||||
|
||||
|
||||
@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_id}")
|
||||
def set_lyric(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
lyric_id: int,
|
||||
title: Annotated[str, Form()],
|
||||
lyric: Annotated[str, Form()],
|
||||
):
|
||||
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")
|
||||
def add_lyric(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
):
|
||||
new_tema = temes_w.add_lyric(tema_id=tema_id)
|
||||
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_id}")
|
||||
def delete_lyric(
|
||||
tema_id: int,
|
||||
lyric_id: int,
|
||||
):
|
||||
_ = 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}")
|
||||
async def set_link(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
link_id: int,
|
||||
content_type: Annotated[model.ContentType, Form()],
|
||||
title: Annotated[str | None, Form()] = None,
|
||||
url: Annotated[str | None, Form()] = None,
|
||||
upload_file: Annotated[UploadFile | None, File()] = None,
|
||||
):
|
||||
if upload_file:
|
||||
url = await files.store_file(tema_id=tema_id, upload_file=upload_file)
|
||||
|
||||
link_type = guess_link_type(url or '')
|
||||
new_link = model.Link(
|
||||
id=link_id,
|
||||
content_type=content_type,
|
||||
link_type=link_type,
|
||||
url=url or '',
|
||||
title=title or '',
|
||||
)
|
||||
_ = 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,
|
||||
):
|
||||
_tema = temes_w.delete_link(tema_id=tema_id, link_id=link_id)
|
||||
return HTMLResponse(
|
||||
headers={
|
||||
"HX-Trigger": f"reload-tema-{tema_id}-score"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@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,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/score")
|
||||
def get_score(
|
||||
request: Request,
|
||||
logged_in: auth.LoggedIn,
|
||||
tema_id: int,
|
||||
):
|
||||
return tema.score(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
tema_id=tema_id,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/property/{property_id}")
|
||||
def property_(request: Request, logged_in: auth.LoggedIn, tema_id: int, property_id: int):
|
||||
return tema.property_(request=request, logged_in=logged_in, tema_id=tema_id, property_id=property_id)
|
||||
|
||||
|
||||
@router.put("/api/tema/{tema_id}/property/{property_id}")
|
||||
def set_property(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
property_id: int,
|
||||
field: Annotated[model.PropertyField, Form()],
|
||||
value: Annotated[str, Form()],
|
||||
):
|
||||
new_property = model.Property(id=property_id, field=field, value=value.strip())
|
||||
_ = temes_w.update_property(tema_id=tema_id, property_id=property_id, prop=new_property)
|
||||
return tema.property_(request=request, logged_in=logged_in, tema_id=tema_id, property_id=property_id)
|
||||
|
||||
|
||||
@router.post("/api/tema/{tema_id}/property")
|
||||
def add_property(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
):
|
||||
new_tema = temes_w.add_property(tema_id=tema_id)
|
||||
property_id = new_tema.properties[-1].id
|
||||
if property_id is None:
|
||||
raise RuntimeError("Invalid property_id on newly created property!")
|
||||
return tema.property_editor(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
tema_id=tema_id,
|
||||
property_id=property_id,
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/api/tema/{tema_id}/property/{property_id}")
|
||||
def delete_property(_: auth.RequireLogin, tema_id: int, property_id: int):
|
||||
_tema = temes_w.delete_property(tema_id=tema_id, property_id=property_id)
|
||||
return HTMLResponse()
|
||||
|
||||
|
||||
@router.put("/api/tema/{tema_id}/visible")
|
||||
def set_visible(request: Request, logged_in: auth.RequireLogin, tema_id: int):
|
||||
new_tema = temes_w.set_visibility(tema_id=tema_id, hidden=False)
|
||||
|
||||
160
folkugat_web/api/tema/links.py
Normal file
160
folkugat_web/api/tema/links.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import HTTPException, Request, UploadFile
|
||||
from fastapi.params import File, Form, Param
|
||||
from fastapi.responses import HTMLResponse
|
||||
from folkugat_web.api import router
|
||||
from folkugat_web.fragments.tema import links as links_fragments
|
||||
from folkugat_web.model import temes as model
|
||||
from folkugat_web.services import auth, files
|
||||
from folkugat_web.services.temes import links as links_service
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/link/{link_id}")
|
||||
def link(request: Request, logged_in: auth.LoggedIn, tema_id: int, link_id: int):
|
||||
link = links_service.get_link_by_id(link_id=link_id, tema_id=tema_id)
|
||||
if not link:
|
||||
raise HTTPException(status_code=404, detail="Could not find link!")
|
||||
return links_fragments.link(request=request, logged_in=logged_in, link=link)
|
||||
|
||||
|
||||
@router.put("/api/tema/{tema_id}/link/{link_id}")
|
||||
async def set_link(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
link_id: int,
|
||||
content_type: Annotated[model.ContentType, Form()],
|
||||
title: Annotated[str | None, Form()] = None,
|
||||
url: Annotated[str | None, Form()] = None,
|
||||
upload_file: Annotated[UploadFile | None, File()] = None,
|
||||
):
|
||||
if upload_file:
|
||||
url = await files.store_file(tema_id=tema_id, upload_file=upload_file)
|
||||
|
||||
link_type = links_service.guess_link_type(url or '')
|
||||
new_link = model.Link(
|
||||
id=link_id,
|
||||
tema_id=tema_id,
|
||||
content_type=content_type,
|
||||
link_type=link_type,
|
||||
url=(url or '').strip(),
|
||||
title=(title or '').strip(),
|
||||
)
|
||||
links_service.update_link(link=new_link)
|
||||
return links_fragments.link(request=request, logged_in=logged_in, link=new_link)
|
||||
|
||||
|
||||
@router.post("/api/tema/{tema_id}/link")
|
||||
def create_link(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
):
|
||||
link = links_service.create_link(tema_id=tema_id)
|
||||
return links_fragments.link_editor(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
link=link,
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/api/tema/{tema_id}/link/{link_id}")
|
||||
def delete_link(
|
||||
_logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
link_id: int,
|
||||
):
|
||||
links_service.delete_link(link_id=link_id, tema_id=tema_id)
|
||||
return HTMLResponse(
|
||||
headers={
|
||||
"HX-Trigger": f"reload-tema-{tema_id}-score"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@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()],
|
||||
):
|
||||
link = model.Link(
|
||||
id=link_id,
|
||||
tema_id=tema_id,
|
||||
content_type=content_type,
|
||||
link_type=links_service.guess_link_type(url),
|
||||
url=url,
|
||||
)
|
||||
return links_fragments.link_icon(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
link=link,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/score")
|
||||
def get_score(
|
||||
request: Request,
|
||||
logged_in: auth.LoggedIn,
|
||||
tema_id: int,
|
||||
):
|
||||
return links_fragments.score(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
tema_id=tema_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,
|
||||
):
|
||||
link = links_service.get_link_by_id(link_id=link_id, tema_id=tema_id)
|
||||
if not link:
|
||||
raise HTTPException(status_code=404, detail="Could not find link!")
|
||||
return links_fragments.link_editor(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
link=link,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/editor/link/{link_id}/url")
|
||||
def link_editor_url_input(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
link_id: int,
|
||||
):
|
||||
link = links_service.get_link_by_id(link_id=link_id, tema_id=tema_id)
|
||||
if not link:
|
||||
raise HTTPException(status_code=404, detail="Could not find link!")
|
||||
return links_fragments.link_editor_url(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
link=link,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/editor/link/{link_id}/file")
|
||||
def link_editor_file_input(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
link_id: int,
|
||||
):
|
||||
link = links_service.get_link_by_id(link_id=link_id, tema_id=tema_id)
|
||||
if not link:
|
||||
raise HTTPException(status_code=404, detail="Could not find link!")
|
||||
return links_fragments.link_editor_file(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
link=link,
|
||||
)
|
||||
80
folkugat_web/api/tema/lyrics.py
Normal file
80
folkugat_web/api/tema/lyrics.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import dataclasses
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import HTTPException, Request
|
||||
from fastapi.params import Form
|
||||
from fastapi.responses import HTMLResponse
|
||||
from folkugat_web.api import router
|
||||
from folkugat_web.fragments.tema import lyrics as lyrics_fragments
|
||||
from folkugat_web.services import auth
|
||||
from folkugat_web.services.temes import lyrics as lyrics_service
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/lyric/{lyric_id}")
|
||||
def lyric(
|
||||
request: Request,
|
||||
logged_in: auth.LoggedIn,
|
||||
tema_id: int,
|
||||
lyric_id: int,
|
||||
):
|
||||
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
|
||||
if not lyric:
|
||||
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||
return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=lyric)
|
||||
|
||||
|
||||
@router.put("/api/tema/{tema_id}/lyric/{lyric_id}")
|
||||
def set_lyric(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
lyric_id: int,
|
||||
title: Annotated[str, Form()],
|
||||
content: Annotated[str, Form()],
|
||||
):
|
||||
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
|
||||
if not lyric:
|
||||
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||
new_lyric = dataclasses.replace(lyric, title=title, content=content.strip())
|
||||
lyrics_service.update_lyric(lyric=new_lyric)
|
||||
return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=new_lyric)
|
||||
|
||||
|
||||
@router.post("/api/tema/{tema_id}/lyric")
|
||||
def add_lyric(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
):
|
||||
lyric = lyrics_service.create_lyric(tema_id=tema_id)
|
||||
return lyrics_fragments.lyric_editor(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
lyric=lyric,
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/api/tema/{tema_id}/lyric/{lyric_id}")
|
||||
def delete_lyric(
|
||||
tema_id: int,
|
||||
lyric_id: int,
|
||||
):
|
||||
lyrics_service.delete_lyric(lyric_id=lyric_id, tema_id=tema_id)
|
||||
return HTMLResponse()
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/editor/lyric/{lyric_id}")
|
||||
def lyric_editor(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
lyric_id: int,
|
||||
):
|
||||
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
|
||||
if not lyric:
|
||||
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||
return lyrics_fragments.lyric_editor(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
lyric=lyric,
|
||||
)
|
||||
73
folkugat_web/api/tema/properties.py
Normal file
73
folkugat_web/api/tema/properties.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import dataclasses
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import HTTPException, Request
|
||||
from fastapi.params import Form
|
||||
from fastapi.responses import HTMLResponse
|
||||
from folkugat_web.api import router
|
||||
from folkugat_web.fragments.tema import properties as properties_fragments
|
||||
from folkugat_web.model import temes as model
|
||||
from folkugat_web.services import auth
|
||||
from folkugat_web.services.temes import properties as properties_service
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/property/{property_id}")
|
||||
def property_(request: Request, logged_in: auth.LoggedIn, tema_id: int, property_id: int):
|
||||
property = properties_service.get_property_by_id(property_id=property_id, tema_id=tema_id)
|
||||
if not property:
|
||||
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||
return properties_fragments.property_(request=request, logged_in=logged_in, property=property)
|
||||
|
||||
|
||||
@router.put("/api/tema/{tema_id}/property/{property_id}")
|
||||
def set_property(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
property_id: int,
|
||||
field: Annotated[model.PropertyField, Form()],
|
||||
value: Annotated[str, Form()],
|
||||
):
|
||||
property = properties_service.get_property_by_id(property_id=property_id, tema_id=tema_id)
|
||||
if not property:
|
||||
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||
new_property = dataclasses.replace(property, field=field, value=value.strip())
|
||||
properties_service.update_property(property=new_property)
|
||||
return properties_fragments.property_(request=request, logged_in=logged_in, property=new_property)
|
||||
|
||||
|
||||
@router.post("/api/tema/{tema_id}/property")
|
||||
def add_property(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
):
|
||||
property = properties_service.create_property(tema_id=tema_id)
|
||||
return properties_fragments.property_editor(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
property=property,
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/api/tema/{tema_id}/property/{property_id}")
|
||||
def delete_property(_: auth.RequireLogin, tema_id: int, property_id: int):
|
||||
properties_service.delete_property(property_id=property_id, tema_id=tema_id)
|
||||
return HTMLResponse()
|
||||
|
||||
|
||||
@router.get("/api/tema/{tema_id}/editor/property/{property_id}")
|
||||
def property_editor(
|
||||
request: Request,
|
||||
logged_in: auth.RequireLogin,
|
||||
tema_id: int,
|
||||
property_id: int,
|
||||
):
|
||||
property = properties_service.get_property_by_id(property_id=property_id, tema_id=tema_id)
|
||||
if not property:
|
||||
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||
return properties_fragments.property_editor(
|
||||
request=request,
|
||||
logged_in=logged_in,
|
||||
property=property,
|
||||
)
|
||||
@@ -37,4 +37,7 @@
|
||||
<div>
|
||||
<span><span id="page_num">?</span> / <span id="page_count">?</span></span>
|
||||
</div>
|
||||
<div class="text-beige border rounded border-beige m-2 p-1">
|
||||
<a href="{{ pdf_url }}" target="_blank">Obre el PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,14 +36,14 @@
|
||||
<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-put="/api/tema/{{ link.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-get="/api/tema/{{ link.tema_id }}/link/{{ link.id }}"
|
||||
hx-target="#tema-link-{{ link.id }}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="link-editor-{{ link_id }}-file"
|
||||
<div id="link-editor-{{ link.id }}-file"
|
||||
class="flex flex-row gap-2">
|
||||
<input type='file'
|
||||
class="border border-beige focus:outline-none
|
||||
@@ -7,8 +7,8 @@
|
||||
name='upload_file'>
|
||||
<button title="Afegeix un enllaç"
|
||||
class="border border-beige rounded px-2 py-1 my-1"
|
||||
hx-get="/api/tema/{{ tema.id }}/editor/link/{{ link_id }}/url"
|
||||
hx-target="#link-editor-{{ link_id }}-file"
|
||||
hx-get="/api/tema/{{ link.tema_id }}/editor/link/{{ link.id }}/url"
|
||||
hx-target="#link-editor-{{ link.id }}-file"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<i class="fa fa-link" aria-hidden="true"></i>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="link-editor-{{ link_id }}-url"
|
||||
<div id="link-editor-{{ link.id }}-url"
|
||||
class="flex flex-row gap-2">
|
||||
<input name="url"
|
||||
placeholder="URL de l'enllaç"
|
||||
@@ -6,7 +6,7 @@
|
||||
class="border border-beige focus:outline-none
|
||||
rounded grow
|
||||
bg-brown p-1 my-1"
|
||||
hx-get="/api/tema/{{ tema.id }}/link/{{ link_id }}/icon"
|
||||
hx-get="/api/tema/{{ link.tema_id }}/link/{{ link.id }}/icon"
|
||||
hx-trigger="keyup delay:500ms changed"
|
||||
hx-target="#link-icon-{{ link.id }}"
|
||||
hx-include="[name='content_type']"
|
||||
@@ -14,8 +14,8 @@
|
||||
/>
|
||||
<button title="Puja un fitxer"
|
||||
class="border border-beige rounded py-1 px-2 my-1"
|
||||
hx-get="/api/tema/{{ tema.id }}/editor/link/{{ link_id }}/file"
|
||||
hx-target="#link-editor-{{ link_id }}-url"
|
||||
hx-get="/api/tema/{{ link.tema_id }}/editor/link/{{ link.id }}/file"
|
||||
hx-target="#link-editor-{{ link.id }}-url"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<i class="fa fa-upload" aria-hidden="true"></i>
|
||||
|
||||
@@ -9,21 +9,21 @@
|
||||
/>
|
||||
<button title="Desa els canvis"
|
||||
class="mx-1"
|
||||
hx-put="/api/tema/{{ tema.id }}/lyric/{{ lyric.id }}"
|
||||
hx-put="/api/tema/{{ lyric.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.id }}"
|
||||
hx-get="/api/tema/{{ lyric.tema_id }}/lyric/{{ lyric.id }}"
|
||||
hx-target="#tema-lyric-{{ lyric.id }}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</h5>
|
||||
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
||||
<textarea name="lyric"
|
||||
<textarea name="content"
|
||||
placeholder="Lletra"
|
||||
rows="{{ lyric.content.count('\n') + 1 }}"
|
||||
class="border border-beige focus:outline-none
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
</div>
|
||||
<button title="Desa els canvis"
|
||||
class="text-sm text-beige mx-1"
|
||||
hx-put="/api/tema/{{ tema.id }}/property/{{ property.id }}"
|
||||
hx-put="/api/tema/{{ property.tema_id }}/property/{{ property.id }}"
|
||||
hx-target="#tema-property-{{ property.id }}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fa fa-check" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button title="Descarta els canvis"
|
||||
class="text-sm text-beige mx-1"
|
||||
hx-get="/api/tema/{{ tema.id }}/property/{{ property.id }}"
|
||||
hx-get="/api/tema/{{ property.tema_id }}/property/{{ property.id }}"
|
||||
hx-target="#tema-property-{{ property.id }}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
<div class="m-2 text-sm text-beige">
|
||||
<button title="Modifica l'enllaç"
|
||||
class="mx-1"
|
||||
hx-get="/api/tema/{{ tema.id }}/editor/link/{{ link.id }}"
|
||||
hx-get="/api/tema/{{ link.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ç"
|
||||
class="mx-1"
|
||||
hx-delete="/api/tema/{{ tema.id }}/link/{{ link.id }}"
|
||||
hx-delete="/api/tema/{{ link.tema_id }}/link/{{ link.id }}"
|
||||
hx-target="#tema-link-{{ link.id }}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
{% if logged_in %}
|
||||
<div class="flex flex-row my-2 justify-end">
|
||||
<button title="Afegeix una lletra"
|
||||
<button title="Afegeix un enllaç"
|
||||
class="text-sm text-beige text-right"
|
||||
hx-post="/api/tema/{{ tema.id }}/link"
|
||||
hx-target="#new-link-target"
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
{% if logged_in %}
|
||||
<button title="Modifica la lletra"
|
||||
class="mx-1"
|
||||
hx-get="/api/tema/{{ tema.id }}/editor/lyric/{{ lyric.id }}"
|
||||
hx-get="/api/tema/{{ lyric.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.id }}"
|
||||
hx-delete="/api/tema/{{ lyric.tema_id }}/lyric/{{ lyric.id }}"
|
||||
hx-target="#tema-lyric-{{ lyric.id }}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
<div class="grow"></div>
|
||||
<button title="Modifica la informació"
|
||||
class="text-sm text-beige mx-1"
|
||||
hx-get="/api/tema/{{ tema.id }}/editor/property/{{ property.id }}"
|
||||
hx-get="/api/tema/{{ property.tema_id }}/editor/property/{{ property.id }}"
|
||||
hx-target="#tema-property-{{ property.id }}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button title="Esborra la informació"
|
||||
class="text-sm text-beige mx-1"
|
||||
hx-delete="/api/tema/{{ tema.id }}/property/{{ property.id }}"
|
||||
hx-delete="/api/tema/{{ property.tema_id }}/property/{{ property.id }}"
|
||||
hx-target="#tema-property-{{ property.id }}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import sqlite3
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import Iterator, Optional
|
||||
|
||||
from folkugat_web.config.db import DB_FILE
|
||||
|
||||
@@ -13,6 +13,7 @@ def get_connection(con: Connection | None = None) -> Iterator[Connection]:
|
||||
yield con
|
||||
else:
|
||||
con = sqlite3.connect(DB_FILE)
|
||||
_ = con.execute('PRAGMA foreign_keys = ON;')
|
||||
try:
|
||||
yield con
|
||||
con.commit()
|
||||
|
||||
@@ -6,12 +6,6 @@ def create_db(con: Connection | None = None):
|
||||
create_playlists_table(con)
|
||||
|
||||
|
||||
def drop_playlists_table(con: Connection):
|
||||
query = "DROP TABLE IF EXISTS playlists"
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query)
|
||||
|
||||
|
||||
def create_playlists_table(con: Connection):
|
||||
query = """
|
||||
CREATE TABLE IF NOT EXISTS playlists (
|
||||
|
||||
@@ -6,12 +6,6 @@ def create_db(con: Connection | None = None):
|
||||
create_sessions_table(con)
|
||||
|
||||
|
||||
def drop_sessions_table(con: Connection):
|
||||
query = "DROP TABLE IF EXISTS sessions"
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query)
|
||||
|
||||
|
||||
def create_sessions_table(con: Connection):
|
||||
query = """
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
|
||||
@@ -2,21 +2,16 @@ import datetime
|
||||
import json
|
||||
from typing import TypedDict
|
||||
|
||||
from folkugat_web.model import IndexedList
|
||||
from folkugat_web.model import search as search_model
|
||||
from folkugat_web.model import temes as model
|
||||
|
||||
TemaRowTuple = tuple[int, str, str, str, str, str, str, str, str, int]
|
||||
TemaRowTuple = tuple[int, str, str, str, str, int]
|
||||
|
||||
|
||||
class TemaRowDict(TypedDict):
|
||||
id: int | None
|
||||
title: str
|
||||
properties: str
|
||||
links: str
|
||||
lyrics: str
|
||||
alternatives: str
|
||||
ngrams: str
|
||||
modification_date: str
|
||||
creation_date: str
|
||||
hidden: int
|
||||
@@ -26,31 +21,24 @@ def tema_to_row(tema: model.Tema) -> TemaRowDict:
|
||||
return {
|
||||
'id': tema.id,
|
||||
'title': tema.title,
|
||||
'properties': json.dumps(list(map(lambda p: p.to_dict(), tema.properties))),
|
||||
'links': json.dumps(list(map(lambda l: l.to_dict(), tema.links))),
|
||||
'lyrics': json.dumps(list(map(lambda l: l.to_dict(), tema.lyrics))),
|
||||
'alternatives': json.dumps(tema.alternatives),
|
||||
'ngrams': json.dumps(tema.ngrams),
|
||||
'modification_date': tema.modification_date.isoformat(),
|
||||
'creation_date': tema.creation_date.isoformat(),
|
||||
'hidden': 1 if tema.hidden else 0,
|
||||
}
|
||||
|
||||
|
||||
def cell_to_ngrams(cell: str) -> search_model.NGrams:
|
||||
return {int(n): ngrams_ for n, ngrams_ in json.loads(cell).items()}
|
||||
def cell_to_ngrams(cell_str: str) -> search_model.NGrams:
|
||||
cell: dict[str, list[str]] = json.loads(cell_str)
|
||||
return {int(n): ngrams_ for n, ngrams_ in cell.items()}
|
||||
|
||||
|
||||
def row_to_tema(row: TemaRowTuple) -> model.Tema:
|
||||
return model.Tema(
|
||||
id=row[0],
|
||||
title=row[1],
|
||||
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]),
|
||||
creation_date=datetime.datetime.fromisoformat(row[8]),
|
||||
hidden=bool(row[9]),
|
||||
alternatives=json.loads(row[2]),
|
||||
modification_date=datetime.datetime.fromisoformat(row[3]),
|
||||
creation_date=datetime.datetime.fromisoformat(row[4]),
|
||||
hidden=bool(row[5]),
|
||||
)
|
||||
|
||||
@@ -4,12 +4,9 @@ from folkugat_web.dal.sql import Connection, get_connection
|
||||
def create_db(con: Connection | None = None):
|
||||
with get_connection(con) as con:
|
||||
create_temes_table(con)
|
||||
|
||||
|
||||
def drop_temes_table(con: Connection):
|
||||
query = "DROP TABLE IF EXISTS temes"
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query)
|
||||
create_links_table(con)
|
||||
create_lyrics_table(con)
|
||||
create_properties_table(con)
|
||||
|
||||
|
||||
def create_temes_table(con: Connection):
|
||||
@@ -17,11 +14,7 @@ def create_temes_table(con: Connection):
|
||||
CREATE TABLE IF NOT EXISTS temes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
properties TEXT,
|
||||
links TEXT,
|
||||
lyrics TEXT,
|
||||
alternatives TEXT,
|
||||
ngrams TEXT,
|
||||
creation_date TEXT NOT NULL,
|
||||
modification_date TEXT NOT NULL,
|
||||
hidden INTEGER NOT NULL
|
||||
@@ -29,3 +22,47 @@ def create_temes_table(con: Connection):
|
||||
"""
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query)
|
||||
|
||||
|
||||
def create_links_table(con: Connection):
|
||||
query = """
|
||||
CREATE TABLE IF NOT EXISTS tema_links (
|
||||
id INTEGER PRIMARY KEY,
|
||||
tema_id INTEGER,
|
||||
content_type TEXT NOT NULL,
|
||||
link_type TEXT,
|
||||
title TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
FOREIGN KEY(tema_id) REFERENCES temes(id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query)
|
||||
|
||||
|
||||
def create_lyrics_table(con: Connection):
|
||||
query = """
|
||||
CREATE TABLE IF NOT EXISTS tema_lyrics (
|
||||
id INTEGER PRIMARY KEY,
|
||||
tema_id INTEGER,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
FOREIGN KEY(tema_id) REFERENCES temes(id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query)
|
||||
|
||||
|
||||
def create_properties_table(con: Connection):
|
||||
query = """
|
||||
CREATE TABLE IF NOT EXISTS tema_properties (
|
||||
id INTEGER PRIMARY KEY,
|
||||
tema_id INTEGER,
|
||||
field TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
FOREIGN KEY(tema_id) REFERENCES temes(id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query)
|
||||
|
||||
137
folkugat_web/dal/sql/temes/links.py
Normal file
137
folkugat_web/dal/sql/temes/links.py
Normal file
@@ -0,0 +1,137 @@
|
||||
from collections.abc import Iterable
|
||||
from typing import TypedDict
|
||||
|
||||
from folkugat_web.dal.sql import Connection, get_connection
|
||||
from folkugat_web.model import temes as model
|
||||
from folkugat_web.utils import map_none
|
||||
|
||||
LinkRowTuple = tuple[int, int, str, str | None, str, str]
|
||||
|
||||
|
||||
class LinkRowDict(TypedDict):
|
||||
id: int | None
|
||||
tema_id: int
|
||||
content_type: str
|
||||
link_type: str | None
|
||||
title: str
|
||||
url: str
|
||||
|
||||
|
||||
def link_to_row(link: model.Link) -> LinkRowDict:
|
||||
return {
|
||||
"id": link.id,
|
||||
"tema_id": link.tema_id,
|
||||
"content_type": link.content_type.value,
|
||||
"link_type": map_none(lambda lt: lt.value, link.link_type),
|
||||
"title": link.title,
|
||||
"url": link.url,
|
||||
}
|
||||
|
||||
|
||||
def row_to_link(row: LinkRowTuple) -> model.Link:
|
||||
return model.Link(
|
||||
id=row[0],
|
||||
tema_id=row[1],
|
||||
content_type=model.ContentType(row[2]),
|
||||
link_type=map_none(model.LinkType, row[3]),
|
||||
title=row[4],
|
||||
url=row[5],
|
||||
)
|
||||
|
||||
|
||||
class QueryData(TypedDict, total=False):
|
||||
id: int
|
||||
tema_id: int
|
||||
|
||||
|
||||
def _filter_clause(
|
||||
link_id: int | None,
|
||||
tema_id: int | None,
|
||||
) -> tuple[str, QueryData]:
|
||||
filter_clauses: list[str] = []
|
||||
filter_data: QueryData = {}
|
||||
|
||||
if link_id is not None:
|
||||
filter_clauses.append("id = :id")
|
||||
filter_data["id"] = link_id
|
||||
|
||||
if tema_id is not None:
|
||||
filter_clauses.append("tema_id = :tema_id")
|
||||
filter_data["tema_id"] = tema_id
|
||||
|
||||
filter_clause = " AND ".join(filter_clauses)
|
||||
return filter_clause, filter_data
|
||||
|
||||
|
||||
def get_links(link_id: int | None = None, tema_id: int | None = None, con: Connection | None = None) -> Iterable[model.Link]:
|
||||
filter_clause, data = _filter_clause(link_id=link_id, tema_id=tema_id)
|
||||
if filter_clause:
|
||||
filter_clause = f"WHERE {filter_clause}"
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
id, tema_id, content_type, link_type, title, url
|
||||
FROM tema_links
|
||||
{filter_clause}
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
return map(row_to_link, cur.fetchall())
|
||||
|
||||
|
||||
def insert_link(link: model.Link, con: Connection | None = None) -> model.Link:
|
||||
data = link_to_row(link)
|
||||
query = f"""
|
||||
INSERT INTO tema_links
|
||||
(id, tema_id, content_type, link_type, title, url)
|
||||
VALUES
|
||||
(:id, :tema_id, :content_type, :link_type, :title, :url)
|
||||
RETURNING *
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
row: LinkRowTuple = cur.fetchone()
|
||||
return row_to_link(row)
|
||||
|
||||
|
||||
def create_link(tema_id: int, con: Connection | None = None) -> model.Link:
|
||||
new_link = model.Link(
|
||||
id=None,
|
||||
tema_id=tema_id,
|
||||
content_type=model.ContentType.PARTITURA,
|
||||
link_type=None,
|
||||
url="",
|
||||
title="",
|
||||
)
|
||||
return insert_link(new_link, con=con)
|
||||
|
||||
|
||||
def update_link(link: model.Link, con: Connection | None = None):
|
||||
data = link_to_row(link)
|
||||
query = """
|
||||
UPDATE tema_links
|
||||
SET
|
||||
tema_id = :tema_id, content_type = :content_type, link_type = :link_type,
|
||||
title = :title, url = :url
|
||||
WHERE
|
||||
id = :id
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
|
||||
|
||||
def delete_link(link_id: int, tema_id: int | None = None, con: Connection | None = None):
|
||||
filter_clause, data = _filter_clause(link_id=link_id, tema_id=tema_id)
|
||||
if filter_clause:
|
||||
filter_clause = f"WHERE {filter_clause}"
|
||||
|
||||
query = f"""
|
||||
DELETE FROM tema_links
|
||||
{filter_clause}
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
127
folkugat_web/dal/sql/temes/lyrics.py
Normal file
127
folkugat_web/dal/sql/temes/lyrics.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from collections.abc import Iterable
|
||||
from typing import TypedDict
|
||||
|
||||
from folkugat_web.dal.sql import Connection, get_connection
|
||||
from folkugat_web.model import temes as model
|
||||
|
||||
LyricRowTuple = tuple[int, int, str, str]
|
||||
|
||||
|
||||
class LyricRowDict(TypedDict):
|
||||
id: int | None
|
||||
tema_id: int
|
||||
title: str
|
||||
content: str
|
||||
|
||||
|
||||
def lyric_to_row(lyric: model.Lyrics) -> LyricRowDict:
|
||||
return {
|
||||
"id": lyric.id,
|
||||
"tema_id": lyric.tema_id,
|
||||
"title": lyric.title,
|
||||
"content": lyric.content,
|
||||
}
|
||||
|
||||
|
||||
def row_to_lyric(row: LyricRowTuple) -> model.Lyrics:
|
||||
return model.Lyrics(
|
||||
id=row[0],
|
||||
tema_id=row[1],
|
||||
title=row[2],
|
||||
content=row[3],
|
||||
)
|
||||
|
||||
|
||||
class QueryData(TypedDict, total=False):
|
||||
id: int
|
||||
tema_id: int
|
||||
|
||||
|
||||
def _filter_clause(
|
||||
lyric_id: int | None,
|
||||
tema_id: int | None,
|
||||
) -> tuple[str, QueryData]:
|
||||
filter_clauses: list[str] = []
|
||||
filter_data: QueryData = {}
|
||||
|
||||
if lyric_id is not None:
|
||||
filter_clauses.append("id = :id")
|
||||
filter_data["id"] = lyric_id
|
||||
|
||||
if tema_id is not None:
|
||||
filter_clauses.append("tema_id = :tema_id")
|
||||
filter_data["tema_id"] = tema_id
|
||||
|
||||
filter_clause = " AND ".join(filter_clauses)
|
||||
return filter_clause, filter_data
|
||||
|
||||
|
||||
def get_lyrics(lyric_id: int | None = None, tema_id: int | None = None, con: Connection | None = None) -> Iterable[model.Lyrics]:
|
||||
filter_clause, data = _filter_clause(lyric_id=lyric_id, tema_id=tema_id)
|
||||
if filter_clause:
|
||||
filter_clause = f"WHERE {filter_clause}"
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
id, tema_id, title, content
|
||||
FROM tema_lyrics
|
||||
{filter_clause}
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
return map(row_to_lyric, cur.fetchall())
|
||||
|
||||
|
||||
def insert_lyric(lyric: model.Lyrics, con: Connection | None = None) -> model.Lyrics:
|
||||
data = lyric_to_row(lyric)
|
||||
query = f"""
|
||||
INSERT INTO tema_lyrics
|
||||
(id, tema_id, title, content)
|
||||
VALUES
|
||||
(:id, :tema_id, :title, :content)
|
||||
RETURNING *
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
row: LyricRowTuple = cur.fetchone()
|
||||
return row_to_lyric(row)
|
||||
|
||||
|
||||
def create_lyric(tema_id: int, title: str | None = None, con: Connection | None = None) -> model.Lyrics:
|
||||
new_lyric = model.Lyrics(
|
||||
id=None,
|
||||
tema_id=tema_id,
|
||||
title=title or "",
|
||||
content="",
|
||||
)
|
||||
return insert_lyric(new_lyric, con=con)
|
||||
|
||||
|
||||
def update_lyric(lyric: model.Lyrics, con: Connection | None = None):
|
||||
data = lyric_to_row(lyric)
|
||||
query = """
|
||||
UPDATE tema_lyrics
|
||||
SET
|
||||
tema_id = :tema_id, title = :title, content = :content
|
||||
WHERE
|
||||
id = :id
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
|
||||
|
||||
def delete_lyric(lyric_id: int, tema_id: int | None = None, con: Connection | None = None):
|
||||
filter_clause, data = _filter_clause(lyric_id=lyric_id, tema_id=tema_id)
|
||||
if filter_clause:
|
||||
filter_clause = f"WHERE {filter_clause}"
|
||||
|
||||
query = f"""
|
||||
DELETE FROM tema_lyrics
|
||||
{filter_clause}
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
127
folkugat_web/dal/sql/temes/properties.py
Normal file
127
folkugat_web/dal/sql/temes/properties.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from collections.abc import Iterable
|
||||
from typing import TypedDict
|
||||
|
||||
from folkugat_web.dal.sql import Connection, get_connection
|
||||
from folkugat_web.model import temes as model
|
||||
|
||||
PropertyRowTuple = tuple[int, int, str, str]
|
||||
|
||||
|
||||
class PropertyRowDict(TypedDict):
|
||||
id: int | None
|
||||
tema_id: int
|
||||
field: str
|
||||
value: str
|
||||
|
||||
|
||||
def property_to_row(property: model.Property) -> PropertyRowDict:
|
||||
return {
|
||||
"id": property.id,
|
||||
"tema_id": property.tema_id,
|
||||
"field": property.field.value,
|
||||
"value": property.value,
|
||||
}
|
||||
|
||||
|
||||
def row_to_property(row: PropertyRowTuple) -> model.Property:
|
||||
return model.Property(
|
||||
id=row[0],
|
||||
tema_id=row[1],
|
||||
field=model.PropertyField(row[2]),
|
||||
value=row[3],
|
||||
)
|
||||
|
||||
|
||||
class QueryData(TypedDict, total=False):
|
||||
id: int
|
||||
tema_id: int
|
||||
|
||||
|
||||
def _filter_clause(
|
||||
property_id: int | None,
|
||||
tema_id: int | None,
|
||||
) -> tuple[str, QueryData]:
|
||||
filter_clauses: list[str] = []
|
||||
filter_data: QueryData = {}
|
||||
|
||||
if property_id is not None:
|
||||
filter_clauses.append("id = :id")
|
||||
filter_data["id"] = property_id
|
||||
|
||||
if tema_id is not None:
|
||||
filter_clauses.append("tema_id = :tema_id")
|
||||
filter_data["tema_id"] = tema_id
|
||||
|
||||
filter_clause = " AND ".join(filter_clauses)
|
||||
return filter_clause, filter_data
|
||||
|
||||
|
||||
def get_properties(property_id: int | None = None, tema_id: int | None = None, con: Connection | None = None) -> Iterable[model.Property]:
|
||||
filter_clause, data = _filter_clause(property_id=property_id, tema_id=tema_id)
|
||||
if filter_clause:
|
||||
filter_clause = f"WHERE {filter_clause}"
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
id, tema_id, field, value
|
||||
FROM tema_properties
|
||||
{filter_clause}
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
return map(row_to_property, cur.fetchall())
|
||||
|
||||
|
||||
def insert_property(property: model.Property, con: Connection | None = None) -> model.Property:
|
||||
data = property_to_row(property)
|
||||
query = f"""
|
||||
INSERT INTO tema_properties
|
||||
(id, tema_id, field, value)
|
||||
VALUES
|
||||
(:id, :tema_id, :field, :value)
|
||||
RETURNING *
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
row: PropertyRowTuple = cur.fetchone()
|
||||
return row_to_property(row)
|
||||
|
||||
|
||||
def create_property(tema_id: int, field: model.PropertyField | None = None, con: Connection | None = None) -> model.Property:
|
||||
new_property = model.Property(
|
||||
id=None,
|
||||
tema_id=tema_id,
|
||||
field=field or model.PropertyField.AUTOR,
|
||||
value="",
|
||||
)
|
||||
return insert_property(new_property, con=con)
|
||||
|
||||
|
||||
def update_property(property: model.Property, con: Connection | None = None):
|
||||
data = property_to_row(property)
|
||||
query = """
|
||||
UPDATE tema_properties
|
||||
SET
|
||||
tema_id = :tema_id, field = :field, value = :value
|
||||
WHERE
|
||||
id = :id
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
|
||||
|
||||
def delete_property(property_id: int, tema_id: int | None = None, con: Connection | None = None):
|
||||
filter_clause, data = _filter_clause(property_id=property_id, tema_id=tema_id)
|
||||
if filter_clause:
|
||||
filter_clause = f"WHERE {filter_clause}"
|
||||
|
||||
query = f"""
|
||||
DELETE FROM tema_properties
|
||||
{filter_clause}
|
||||
"""
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
@@ -10,8 +10,7 @@ _tema_id_to_ngrams_cache: dict[int, search_model.NGrams] | None = None
|
||||
def get_tema_by_id(tema_id: int, con: Connection | None = None) -> model.Tema | None:
|
||||
query = """
|
||||
SELECT
|
||||
id, title, properties, links, lyrics, alternatives, ngrams,
|
||||
creation_date, modification_date, hidden
|
||||
id, title, alternatives, creation_date, modification_date, hidden
|
||||
FROM temes
|
||||
WHERE id = :id
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import json
|
||||
|
||||
from folkugat_web.dal.sql import Connection, get_connection
|
||||
from folkugat_web.model import search as search_model
|
||||
from folkugat_web.model import temes as model
|
||||
|
||||
from . import conversion
|
||||
@@ -8,12 +11,10 @@ from .query import evict_tema_id_to_ngrams_cache
|
||||
def insert_tema(tema: model.Tema, con: Connection | None = None) -> model.Tema:
|
||||
query = """
|
||||
INSERT INTO temes
|
||||
(id, title, properties, links, lyrics, alternatives, ngrams,
|
||||
creation_date, modification_date, hidden)
|
||||
(id, title, alternatives, creation_date, modification_date, hidden)
|
||||
VALUES
|
||||
(:id, :title, :properties, :links, :lyrics, :alternatives, :ngrams,
|
||||
:creation_date, :modification_date, :hidden)
|
||||
RETURNING *
|
||||
(:id, :title, :alternatives, :creation_date, :modification_date, :hidden)
|
||||
RETURNING id, title, alternatives, creation_date, modification_date, hidden
|
||||
"""
|
||||
data = conversion.tema_to_row(tema)
|
||||
with get_connection(con) as con:
|
||||
@@ -28,13 +29,27 @@ def update_tema(tema: model.Tema, con: Connection | None = None):
|
||||
query = """
|
||||
UPDATE temes
|
||||
SET
|
||||
title = :title, properties = :properties, links = :links, lyrics = :lyrics,
|
||||
alternatives = :alternatives, ngrams = :ngrams, creation_date = :creation_date,
|
||||
title = :title, alternatives = :alternatives, creation_date = :creation_date,
|
||||
modification_date = :modification_date, hidden = :hidden
|
||||
WHERE
|
||||
id = :id
|
||||
"""
|
||||
data = conversion.tema_to_row(tema)
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
return
|
||||
|
||||
|
||||
def update_ngrams(tema: model.Tema, con: Connection | None = None):
|
||||
query = """
|
||||
UPDATE temes
|
||||
SET
|
||||
ngrams = :ngrams
|
||||
WHERE
|
||||
id = :id
|
||||
"""
|
||||
data = dict(id=tema.id, ngrams=json.dumps(tema.ngrams()))
|
||||
with get_connection(con) as con:
|
||||
cur = con.cursor()
|
||||
_ = cur.execute(query, data)
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
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
|
||||
from folkugat_web.templates import templates
|
||||
|
||||
|
||||
def title(request: Request, logged_in: bool, tema: model.Tema | None = None, tema_id: int | None = None):
|
||||
if tema is None:
|
||||
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",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def lyric(request: Request, logged_in: bool, tema_id: int, lyric_id: int):
|
||||
tema = temes_q.get_tema_by_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(
|
||||
"fragments/tema/lyric.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"lyric_id": lyric_id,
|
||||
"lyric": lyric,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def lyric_editor(request: Request, logged_in: bool, tema_id: int, lyric_id: int):
|
||||
tema = temes_q.get_tema_by_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(
|
||||
"fragments/tema/editor/lyric.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"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 not tema:
|
||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
||||
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,
|
||||
},
|
||||
headers={
|
||||
"HX-Trigger": f"reload-tema-{tema_id}-score"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def link_editor(request: Request, logged_in: bool, tema_id: int, link_id: int):
|
||||
tema = temes_q.get_tema_by_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.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"link_id": link_id,
|
||||
"link": link,
|
||||
"LinkType": model.LinkType,
|
||||
"ContentType": model.ContentType,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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 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",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"link_id": link_id,
|
||||
"link": link,
|
||||
"LinkType": model.LinkType,
|
||||
"ContentType": model.ContentType,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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 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",
|
||||
{
|
||||
"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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def score(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/score.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"LinkType": model.LinkType,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def property_(request: Request, logged_in: bool, tema_id: int, property_id: int):
|
||||
tema = temes_q.get_tema_by_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(
|
||||
"fragments/tema/property.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"property_id": property_id,
|
||||
"property": prop,
|
||||
"PropertyField": model.PropertyField,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def property_editor(request: Request, logged_in: bool, tema_id: int, property_id: int):
|
||||
tema = temes_q.get_tema_by_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(
|
||||
"fragments/tema/editor/property.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"property_id": property_id,
|
||||
"property": prop,
|
||||
"PropertyField": model.PropertyField,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def visibility(request: Request, logged_in: bool, tema: model.Tema):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/visibility.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
}
|
||||
)
|
||||
84
folkugat_web/fragments/tema/__init__.py
Normal file
84
folkugat_web/fragments/tema/__init__.py
Normal file
@@ -0,0 +1,84 @@
|
||||
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.templates import templates
|
||||
|
||||
|
||||
def title(request: Request, logged_in: bool, tema: model.Tema | None = None, tema_id: int | None = None):
|
||||
if tema is None:
|
||||
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",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def property_(request: Request, logged_in: bool, tema_id: int, property_id: int):
|
||||
tema = temes_q.get_tema_by_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(
|
||||
"fragments/tema/property.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"property_id": property_id,
|
||||
"property": prop,
|
||||
"PropertyField": model.PropertyField,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def property_editor(request: Request, logged_in: bool, tema_id: int, property_id: int):
|
||||
tema = temes_q.get_tema_by_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(
|
||||
"fragments/tema/editor/property.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"property_id": property_id,
|
||||
"property": prop,
|
||||
"PropertyField": model.PropertyField,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def visibility(request: Request, logged_in: bool, tema: model.Tema):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/visibility.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
}
|
||||
)
|
||||
88
folkugat_web/fragments/tema/links.py
Normal file
88
folkugat_web/fragments/tema/links.py
Normal file
@@ -0,0 +1,88 @@
|
||||
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
|
||||
from folkugat_web.templates import templates
|
||||
|
||||
|
||||
def link(request: Request, logged_in: bool, link: model.Link):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/link.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"link": link,
|
||||
"LinkType": model.LinkType,
|
||||
"ContentType": model.ContentType,
|
||||
},
|
||||
headers={
|
||||
"HX-Trigger": f"reload-tema-{link.tema_id}-score"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def link_editor(request: Request, logged_in: bool, link: model.Link):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/editor/link.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"link": link,
|
||||
"LinkType": model.LinkType,
|
||||
"ContentType": model.ContentType,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def link_editor_url(request: Request, logged_in: bool, link: model.Link):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/editor/link_url.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"link": link,
|
||||
"LinkType": model.LinkType,
|
||||
"ContentType": model.ContentType,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def link_editor_file(request: Request, logged_in: bool, link: model.Link):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/editor/link_file.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"link": link,
|
||||
"LinkType": model.LinkType,
|
||||
"ContentType": model.ContentType,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def link_icon(request: Request, logged_in: bool, link: model.Link):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/link_icon.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"link": link,
|
||||
"LinkType": model.LinkType,
|
||||
"ContentType": model.ContentType,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def score(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/score.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"tema": tema,
|
||||
"LinkType": model.LinkType,
|
||||
}
|
||||
)
|
||||
25
folkugat_web/fragments/tema/lyrics.py
Normal file
25
folkugat_web/fragments/tema/lyrics.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from fastapi import Request
|
||||
from folkugat_web.model import temes as model
|
||||
from folkugat_web.templates import templates
|
||||
|
||||
|
||||
def lyric(request: Request, logged_in: bool, lyric: model.Lyrics):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/lyric.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"lyric": lyric,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def lyric_editor(request: Request, logged_in: bool, lyric: model.Lyrics):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/editor/lyric.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"lyric": lyric,
|
||||
}
|
||||
)
|
||||
27
folkugat_web/fragments/tema/properties.py
Normal file
27
folkugat_web/fragments/tema/properties.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from fastapi import Request
|
||||
from folkugat_web.model import temes as model
|
||||
from folkugat_web.templates import templates
|
||||
|
||||
|
||||
def property_(request: Request, logged_in: bool, property: model.Property):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/property.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"property": property,
|
||||
"PropertyField": model.PropertyField,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def property_editor(request: Request, logged_in: bool, property: model.Property):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/editor/property.html",
|
||||
{
|
||||
"request": request,
|
||||
"logged_in": logged_in,
|
||||
"property": property,
|
||||
"PropertyField": model.PropertyField,
|
||||
}
|
||||
)
|
||||
@@ -1,10 +1,12 @@
|
||||
from fastapi import HTTPException, Request
|
||||
from fastapi import 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 links as links_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
|
||||
from folkugat_web.utils import FnChain
|
||||
|
||||
|
||||
def temes_pagina(request: Request, logged_in: bool, query: str):
|
||||
@@ -35,7 +37,12 @@ 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)
|
||||
temes = (
|
||||
FnChain.transform(temes) |
|
||||
temes_q.temes_compute_stats |
|
||||
links_service.add_links_to_temes |
|
||||
list
|
||||
).result()
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"fragments/temes/results.html",
|
||||
@@ -52,11 +59,7 @@ 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)
|
||||
def tema(request: Request, logged_in: bool, tema: model.Tema):
|
||||
return templates.TemplateResponse(
|
||||
"fragments/tema/pagina.html",
|
||||
{
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from ._base import IndexedList
|
||||
|
||||
__all__ = ["IndexedList"]
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import dataclasses
|
||||
from typing import TypeVar
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class WithId:
|
||||
id: int | None
|
||||
|
||||
|
||||
T = TypeVar("T", bound=WithId)
|
||||
|
||||
|
||||
class IndexedList(list[T]):
|
||||
@override
|
||||
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)
|
||||
@@ -1,14 +1,11 @@
|
||||
import dataclasses
|
||||
import datetime
|
||||
import enum
|
||||
from typing import Self
|
||||
|
||||
from folkugat_web.model.search import NGrams
|
||||
from folkugat_web.model.sessions import Session
|
||||
from folkugat_web.services import ngrams
|
||||
|
||||
from ._base import IndexedList, WithId
|
||||
|
||||
|
||||
class ContentType(enum.Enum):
|
||||
PARTITURA = "partitura"
|
||||
@@ -26,31 +23,14 @@ class LinkType(enum.Enum):
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Link(WithId):
|
||||
class Link:
|
||||
id: int | None
|
||||
tema_id: int
|
||||
content_type: ContentType
|
||||
link_type: LinkType | None
|
||||
url: str
|
||||
title: str = ""
|
||||
|
||||
def to_dict(self):
|
||||
return dict(
|
||||
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,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d):
|
||||
return cls(
|
||||
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"],
|
||||
)
|
||||
|
||||
|
||||
class PropertyField(enum.Enum):
|
||||
AUTOR = "autor"
|
||||
@@ -60,46 +40,20 @@ class PropertyField(enum.Enum):
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Property(WithId):
|
||||
class Property:
|
||||
id: int | None
|
||||
tema_id: int
|
||||
field: PropertyField
|
||||
value: str
|
||||
|
||||
def to_dict(self):
|
||||
return dict(
|
||||
id=self.id,
|
||||
field=self.field.value,
|
||||
value=self.value,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d):
|
||||
return cls(
|
||||
id=d["id"],
|
||||
field=PropertyField(d["field"]),
|
||||
value=d["value"],
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Lyrics(WithId):
|
||||
class Lyrics:
|
||||
id: int | None
|
||||
tema_id: int
|
||||
title: str
|
||||
content: str
|
||||
|
||||
def to_dict(self):
|
||||
return dict(
|
||||
id=self.id,
|
||||
title=self.title,
|
||||
content=self.content,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d) -> Self:
|
||||
return cls(
|
||||
id=d["id"],
|
||||
title=d["title"],
|
||||
content=d["content"],
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Stats:
|
||||
@@ -112,12 +66,11 @@ class Tema:
|
||||
id: int | None = None
|
||||
# Info
|
||||
title: str = ""
|
||||
properties: IndexedList[Property] = dataclasses.field(default_factory=IndexedList)
|
||||
links: IndexedList[Link] = dataclasses.field(default_factory=IndexedList)
|
||||
lyrics: IndexedList[Lyrics] = dataclasses.field(default_factory=IndexedList)
|
||||
properties: list[Property] = dataclasses.field(default_factory=list)
|
||||
links: list[Link] = dataclasses.field(default_factory=list)
|
||||
lyrics: list[Lyrics] = dataclasses.field(default_factory=list)
|
||||
# Search related
|
||||
alternatives: list[str] = dataclasses.field(default_factory=list)
|
||||
ngrams: NGrams = dataclasses.field(default_factory=dict)
|
||||
hidden: bool = True
|
||||
# Other info
|
||||
modification_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
|
||||
@@ -125,12 +78,8 @@ class Tema:
|
||||
# Stats
|
||||
stats: Stats | None = None
|
||||
|
||||
def compute_ngrams(self):
|
||||
self.ngrams = ngrams.get_text_ngrams(self.title, *self.alternatives)
|
||||
|
||||
def with_ngrams(self):
|
||||
self.compute_ngrams()
|
||||
return self
|
||||
def ngrams(self) -> NGrams:
|
||||
return ngrams.get_text_ngrams(self.title, *self.alternatives)
|
||||
|
||||
@staticmethod
|
||||
def _is_score(link: Link) -> bool:
|
||||
|
||||
@@ -48,7 +48,7 @@ async def store_file(tema_id: int, upload_file: UploadFile) -> str:
|
||||
filepath = filedir / filename
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(await upload_file.read())
|
||||
_ = f.write(await upload_file.read())
|
||||
|
||||
return get_db_file_path(filepath)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import re
|
||||
from typing import Optional
|
||||
from collections.abc import Iterable, Iterator
|
||||
|
||||
from folkugat_web.dal.sql.temes import links as links_dal
|
||||
from folkugat_web.model import temes as model
|
||||
|
||||
IMAGE_FORMATS_RE = "|".join([
|
||||
@@ -30,3 +31,29 @@ def guess_link_type(url: str) -> model.LinkType | None:
|
||||
if regex.match(url):
|
||||
return link_type
|
||||
return None
|
||||
|
||||
|
||||
def add_links_to_tema(tema: model.Tema) -> model.Tema:
|
||||
if tema.id is not None:
|
||||
tema.links = list(links_dal.get_links(tema_id=tema.id))
|
||||
return tema
|
||||
|
||||
|
||||
def add_links_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
|
||||
return map(add_links_to_tema, temes)
|
||||
|
||||
|
||||
def get_link_by_id(link_id: int, tema_id: int | None = None) -> model.Link | None:
|
||||
return next(iter(links_dal.get_links(link_id=link_id, tema_id=tema_id)), None)
|
||||
|
||||
|
||||
def update_link(link: model.Link):
|
||||
links_dal.update_link(link=link)
|
||||
|
||||
|
||||
def delete_link(link_id: int, tema_id: int | None = None):
|
||||
links_dal.delete_link(link_id=link_id, tema_id=tema_id)
|
||||
|
||||
|
||||
def create_link(tema_id: int) -> model.Link:
|
||||
return links_dal.create_link(tema_id=tema_id)
|
||||
|
||||
30
folkugat_web/services/temes/lyrics.py
Normal file
30
folkugat_web/services/temes/lyrics.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from collections.abc import Iterable, Iterator
|
||||
|
||||
from folkugat_web.dal.sql.temes import lyrics as lyrics_dal
|
||||
from folkugat_web.model import temes as model
|
||||
|
||||
|
||||
def add_lyrics_to_tema(tema: model.Tema) -> model.Tema:
|
||||
if tema.id is not None:
|
||||
tema.lyrics = list(lyrics_dal.get_lyrics(tema_id=tema.id))
|
||||
return tema
|
||||
|
||||
|
||||
def add_lyrics_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
|
||||
return map(add_lyrics_to_tema, temes)
|
||||
|
||||
|
||||
def get_lyric_by_id(lyric_id: int, tema_id: int | None = None) -> model.Lyrics | None:
|
||||
return next(iter(lyrics_dal.get_lyrics(lyric_id=lyric_id, tema_id=tema_id)), None)
|
||||
|
||||
|
||||
def update_lyric(lyric: model.Lyrics):
|
||||
lyrics_dal.update_lyric(lyric=lyric)
|
||||
|
||||
|
||||
def delete_lyric(lyric_id: int, tema_id: int | None = None):
|
||||
lyrics_dal.delete_lyric(lyric_id=lyric_id, tema_id=tema_id)
|
||||
|
||||
|
||||
def create_lyric(tema_id: int) -> model.Lyrics:
|
||||
return lyrics_dal.create_lyric(tema_id=tema_id)
|
||||
30
folkugat_web/services/temes/properties.py
Normal file
30
folkugat_web/services/temes/properties.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from collections.abc import Iterable, Iterator
|
||||
|
||||
from folkugat_web.dal.sql.temes import properties as properties_dal
|
||||
from folkugat_web.model import temes as model
|
||||
|
||||
|
||||
def add_properties_to_tema(tema: model.Tema) -> model.Tema:
|
||||
if tema.id is not None:
|
||||
tema.properties = list(properties_dal.get_properties(tema_id=tema.id))
|
||||
return tema
|
||||
|
||||
|
||||
def add_properties_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
|
||||
return map(add_properties_to_tema, temes)
|
||||
|
||||
|
||||
def get_property_by_id(property_id: int, tema_id: int | None = None) -> model.Property | None:
|
||||
return next(iter(properties_dal.get_properties(property_id=property_id, tema_id=tema_id)), None)
|
||||
|
||||
|
||||
def update_property(property: model.Property):
|
||||
properties_dal.update_property(property=property)
|
||||
|
||||
|
||||
def delete_property(property_id: int, tema_id: int | None = None):
|
||||
properties_dal.delete_property(property_id=property_id, tema_id=tema_id)
|
||||
|
||||
|
||||
def create_property(tema_id: int) -> model.Property:
|
||||
return properties_dal.create_property(tema_id=tema_id)
|
||||
@@ -1,7 +1,7 @@
|
||||
import functools
|
||||
import time
|
||||
import typing
|
||||
from collections.abc import Callable, Iterable
|
||||
from collections.abc import Iterable, Iterator
|
||||
from sqlite3 import Connection
|
||||
from typing import Callable
|
||||
|
||||
import Levenshtein
|
||||
from folkugat_web.config import search as config
|
||||
@@ -10,6 +10,7 @@ from folkugat_web.dal.sql.temes import query as temes_q
|
||||
from folkugat_web.log import logger
|
||||
from folkugat_web.model import search as search_model
|
||||
from folkugat_web.model import temes as model
|
||||
from folkugat_web.utils import FnChain
|
||||
|
||||
|
||||
def get_query_word_similarity(query_word: str, text_ngrams: search_model.NGrams) -> search_model.SearchMatch:
|
||||
@@ -33,40 +34,66 @@ def get_query_similarity(query: str, ngrams: search_model.NGrams) -> search_mode
|
||||
return search_model.SearchMatch.combine_matches(word_matches)
|
||||
|
||||
|
||||
def build_result(query: str, entry: tuple[int, search_model.NGrams]) -> search_model.QueryResult:
|
||||
if len(query) == 0:
|
||||
def _build_results_fn(query: str) -> Callable[[Iterable[tuple[int, search_model.NGrams]]],
|
||||
Iterator[search_model.QueryResult]]:
|
||||
def build_result(entry: tuple[int, search_model.NGrams]) -> search_model.QueryResult:
|
||||
if len(query) == 0:
|
||||
return search_model.QueryResult(
|
||||
id=entry[0],
|
||||
distance=0,
|
||||
ngram="",
|
||||
)
|
||||
match = get_query_similarity(query, entry[1])
|
||||
return search_model.QueryResult(
|
||||
id=entry[0],
|
||||
distance=0,
|
||||
ngram="",
|
||||
distance=match.distance,
|
||||
ngram=match.ngram,
|
||||
)
|
||||
match = get_query_similarity(query, entry[1])
|
||||
return search_model.QueryResult(
|
||||
id=entry[0],
|
||||
distance=match.distance,
|
||||
ngram=match.ngram,
|
||||
)
|
||||
|
||||
def build_results(entries: Iterable[tuple[int, search_model.NGrams]]) -> Iterator[search_model.QueryResult]:
|
||||
return map(build_result, entries)
|
||||
|
||||
return build_results
|
||||
|
||||
|
||||
T = typing.TypeVar("T")
|
||||
def _filter_distance(qrs: Iterable[search_model.QueryResult]) -> Iterator[search_model.QueryResult]:
|
||||
return filter(lambda qr: qr.distance <= config.SEARCH_DISTANCE_THRESHOLD, qrs)
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _thread(it: Iterable[T], *funcs: Callable[[Iterable], Iterable]) -> Iterable:
|
||||
return functools.reduce(lambda i, fn: fn(i), funcs, it)
|
||||
def _sort_by_distance(qrs: Iterable[search_model.QueryResult]) -> list[search_model.QueryResult]:
|
||||
return sorted(qrs, key=lambda qr: qr.distance)
|
||||
|
||||
|
||||
def _query_results_to_temes(con: Connection) -> Callable[[Iterable[search_model.QueryResult]], Iterator[model.Tema]]:
|
||||
def fetch_temes(qrs: Iterable[search_model.QueryResult]) -> Iterator[model.Tema]:
|
||||
return filter(None, map(lambda qr: temes_q.get_tema_by_id(tema_id=qr.id, con=con), qrs))
|
||||
return fetch_temes
|
||||
|
||||
|
||||
def _filter_hidden(hidden: bool) -> Callable[[Iterable[model.Tema]], Iterator[model.Tema]]:
|
||||
def filter_hidden(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
|
||||
return filter(lambda t: hidden or not t.hidden, temes)
|
||||
return filter_hidden
|
||||
|
||||
|
||||
def _apply_limit_offset(limit: int, offset: int) -> Callable[[Iterable[model.Tema]], list[model.Tema]]:
|
||||
def apply_limit_offset(temes: Iterable[model.Tema]) -> list[model.Tema]:
|
||||
return list(temes)[offset:offset + limit]
|
||||
return apply_limit_offset
|
||||
|
||||
|
||||
def busca_temes(query: str, hidden: bool = False, limit: int = 10, offset: int = 0) -> list[model.Tema]:
|
||||
t0 = time.time()
|
||||
|
||||
with get_connection() as con:
|
||||
result = _thread(
|
||||
temes_q.get_tema_id_to_ngrams(con).items(),
|
||||
lambda tema_id_to_ngrams: (build_result(query, entry) for entry in tema_id_to_ngrams),
|
||||
lambda results: filter(lambda qr: qr.distance <= config.SEARCH_DISTANCE_THRESHOLD, results),
|
||||
lambda results: sorted(results, key=lambda qr: qr.distance),
|
||||
lambda results: filter(None, map(lambda qr: temes_q.get_tema_by_id(qr.id, con), results)),
|
||||
lambda results: filter(lambda t: hidden or not t.hidden, results),
|
||||
)
|
||||
result = list(result)[offset:offset + limit]
|
||||
result = (
|
||||
FnChain.transform(temes_q.get_tema_id_to_ngrams(con).items()) |
|
||||
_build_results_fn(query) |
|
||||
_filter_distance |
|
||||
_sort_by_distance |
|
||||
_query_results_to_temes(con) |
|
||||
_filter_hidden(hidden) |
|
||||
_apply_limit_offset(limit=limit, offset=offset)
|
||||
).result()
|
||||
logger.info(f"Search time: { int((time.time() - t0) * 1000) } ms")
|
||||
return result
|
||||
|
||||
@@ -7,8 +7,10 @@ from folkugat_web.model import temes as model
|
||||
|
||||
|
||||
def create_tema(title: str = "") -> model.Tema:
|
||||
new_tema = model.Tema(title=title, hidden=False).with_ngrams()
|
||||
return temes_w.insert_tema(tema=new_tema)
|
||||
with get_connection() as con:
|
||||
new_tema = temes_w.insert_tema(tema=model.Tema(title=title, hidden=False), con=con)
|
||||
temes_w.update_ngrams(tema=new_tema, con=con)
|
||||
return new_tema
|
||||
|
||||
|
||||
def delete_tema(tema_id: int) -> None:
|
||||
@@ -21,131 +23,13 @@ def update_title(tema_id: int, title: str) -> model.Tema:
|
||||
if tema is None:
|
||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
||||
|
||||
new_tema = dataclasses.replace(tema, title=title).with_ngrams()
|
||||
new_tema = dataclasses.replace(tema, title=title)
|
||||
temes_w.update_tema(tema=new_tema, con=con)
|
||||
temes_w.update_ngrams(tema=new_tema, con=con)
|
||||
|
||||
return new_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}!")
|
||||
|
||||
tema.lyrics.replace(lyric_id, lyric)
|
||||
temes_w.update_tema(tema=tema, con=con)
|
||||
|
||||
return 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}!")
|
||||
|
||||
tema.lyrics.delete(lyric_id)
|
||||
temes_w.update_tema(tema=tema, con=con)
|
||||
|
||||
return tema
|
||||
|
||||
|
||||
def add_lyric(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.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
|
||||
|
||||
|
||||
def update_property(tema_id: int, property_id: int, prop: model.Property) -> 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.properties.replace(property_id, prop)
|
||||
temes_w.update_tema(tema=tema, con=con)
|
||||
|
||||
return tema
|
||||
|
||||
|
||||
def delete_property(tema_id: int, property_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.properties.delete(property_id)
|
||||
temes_w.update_tema(tema=tema, con=con)
|
||||
|
||||
return tema
|
||||
|
||||
|
||||
def add_property(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.properties.append(model.Property(
|
||||
id=None,
|
||||
field=model.PropertyField.AUTOR,
|
||||
value="",
|
||||
))
|
||||
temes_w.update_tema(tema=tema, con=con)
|
||||
|
||||
return tema
|
||||
|
||||
|
||||
def set_visibility(tema_id: int, hidden: bool) -> model.Tema:
|
||||
with get_connection() as con:
|
||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
||||
|
||||
@@ -3,4 +3,4 @@ from typing import TypeVar
|
||||
T = TypeVar("T")
|
||||
|
||||
ListOrValue = T | list[T]
|
||||
OptionalListOrValue = ListOrValue[T] | None
|
||||
OptionalListOrValue = T | list[T] | None
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from collections.abc import Callable, Iterable, Iterator
|
||||
from typing import Protocol, Self, TypeVar
|
||||
from typing import Generic, Protocol, Self, TypeVar
|
||||
|
||||
|
||||
class SupportsLessThan(Protocol):
|
||||
@@ -20,3 +22,28 @@ def groupby(
|
||||
) -> Iterator[tuple[KeyT, GroupT]]:
|
||||
for k, g in itertools.groupby(sorted(it, key=key_fn), key=key_fn):
|
||||
yield k, group_fn(g)
|
||||
|
||||
|
||||
U = TypeVar("U")
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
def map_none(fn: Callable[[U], V], value: U | None) -> V | None:
|
||||
if value is None:
|
||||
return None
|
||||
return fn(value)
|
||||
|
||||
|
||||
class FnChain(Generic[U]):
|
||||
def __init__(self, fn: Callable[[], U], /):
|
||||
self._fn: Callable[[], U] = fn
|
||||
|
||||
def __or__(self, next_fn: Callable[[U], V], /) -> FnChain[V]:
|
||||
return FnChain(lambda: next_fn(self._fn()))
|
||||
|
||||
def result(self) -> U:
|
||||
return self._fn()
|
||||
|
||||
@classmethod
|
||||
def transform(cls, x: V, /) -> FnChain[V]:
|
||||
return FnChain(lambda: x)
|
||||
|
||||
Reference in New Issue
Block a user