diff --git a/flake.nix b/flake.nix
index ea63275..ebe10c8 100644
--- a/flake.nix
+++ b/flake.nix
@@ -60,6 +60,7 @@
lilypond-with-fonts
# Project tools
sqlite
+ lazysql
opencode
];
diff --git a/folkugat_web/api/routes/__init__.py b/folkugat_web/api/routes/__init__.py
index 3f0be81..aa69368 100644
--- a/folkugat_web/api/routes/__init__.py
+++ b/folkugat_web/api/routes/__init__.py
@@ -1,14 +1,15 @@
from folkugat_web.api.router import get_router
-from . import auth, index, sessio, sessions, tema, temes
+from . import auth, index, playlist, sessio, sessions, tema, temes
router = get_router()
+router.include_router(auth.router)
+router.include_router(index.router)
+router.include_router(playlist.router)
router.include_router(sessio.router)
router.include_router(sessions.router)
router.include_router(tema.router)
router.include_router(temes.router)
-router.include_router(auth.router)
-router.include_router(index.router)
__all__ = ["router"]
diff --git a/folkugat_web/api/routes/playlist/__init__.py b/folkugat_web/api/routes/playlist/__init__.py
new file mode 100644
index 0000000..d87f649
--- /dev/null
+++ b/folkugat_web/api/routes/playlist/__init__.py
@@ -0,0 +1,9 @@
+from folkugat_web.api.router import get_router
+
+from . import playlist, set_page
+
+router = get_router()
+router.include_router(set_page.router)
+router.include_router(playlist.router)
+
+__all__ = ["router"]
diff --git a/folkugat_web/api/routes/sessio/playlist.py b/folkugat_web/api/routes/playlist/playlist.py
similarity index 65%
rename from folkugat_web/api/routes/sessio/playlist.py
rename to folkugat_web/api/routes/playlist/playlist.py
index 468fb8b..b268cc2 100644
--- a/folkugat_web/api/routes/sessio/playlist.py
+++ b/folkugat_web/api/routes/playlist/playlist.py
@@ -2,141 +2,139 @@ from typing import Annotated
from fastapi import Form, Request
from folkugat_web.api.router import get_router
-from folkugat_web.fragments import live
-from folkugat_web.fragments.sessio import playlist
+from folkugat_web.fragments import playlist
from folkugat_web.services import auth
from folkugat_web.services.temes import write as temes_service
-from folkugat_web.templates import templates
router = get_router()
-@router.post("/api/sessio/{session_id}/playlist/set")
+@router.post("/api/playlist/{playlist_id}/set")
def add_set(
request: Request,
logged_in: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
):
return playlist.add_set(
request=request,
- session_id=session_id,
+ playlist_id=playlist_id,
logged_in=logged_in,
)
-@router.get("/api/sessio/{session_id}/playlist/set/{set_id}")
+@router.get("/api/playlist/{playlist_id}/set/{set_id}")
def get_set(
request: Request,
logged_in: auth.LoggedIn,
- session_id: int,
+ playlist_id: int,
set_id: int,
):
return playlist.get_set(
request=request,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
logged_in=logged_in,
)
-@router.delete("/api/sessio/{session_id}/playlist/set/{set_id}")
+@router.delete("/api/playlist/{playlist_id}/set/{set_id}")
def delete_set(
_: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
):
return playlist.delete_set(
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
)
-@router.post("/api/sessio/{session_id}/playlist/set/{set_id}")
+@router.post("/api/playlist/{playlist_id}/set/{set_id}")
def add_tema(
request: Request,
logged_in: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
):
return playlist.add_tema(
request=request,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
logged_in=logged_in,
)
-@router.get("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}")
+@router.get("/api/playlist/{playlist_id}/set/{set_id}/tema/{entry_id}")
def get_tema(
request: Request,
logged_in: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
entry_id: int,
):
return playlist.get_tema(
request=request,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
entry_id=entry_id,
logged_in=logged_in,
)
-@router.get("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}/editor")
+@router.get("/api/playlist/{playlist_id}/set/{set_id}/tema/{entry_id}/editor")
def get_tema_editor(
request: Request,
logged_in: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
entry_id: int,
):
return playlist.get_tema_editor(
request=request,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
entry_id=entry_id,
logged_in=logged_in,
)
-@router.delete("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}")
+@router.delete("/api/playlist/{playlist_id}/set/{set_id}/tema/{entry_id}")
def delete_tema(
_: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
entry_id: int,
):
return playlist.delete_tema(
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
entry_id=entry_id,
)
-@router.get("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}/busca")
+@router.get("/api/playlist/{playlist_id}/set/{set_id}/tema/{entry_id}/busca")
def busca_tema(
request: Request,
_: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
entry_id: int,
query: str,
):
return playlist.busca_tema(
request=request,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
entry_id=entry_id,
query=query,
)
-@router.put("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}")
+@router.put("/api/playlist/{playlist_id}/set/{set_id}/tema/{entry_id}")
def set_tema(
request: Request,
logged_in: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
entry_id: int,
tema_id: Annotated[int, Form()],
@@ -144,36 +142,36 @@ def set_tema(
return playlist.set_tema(
request=request,
logged_in=logged_in,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
entry_id=entry_id,
tema_id=tema_id,
)
-@router.put("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}/unknown")
+@router.put("/api/playlist/{playlist_id}/set/{set_id}/tema/{entry_id}/unknown")
def set_tema_unknown(
request: Request,
logged_in: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
entry_id: int,
):
return playlist.set_tema(
request=request,
logged_in=logged_in,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
entry_id=entry_id,
tema_id=None,
)
-@router.post("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}")
+@router.post("/api/playlist/{playlist_id}/set/{set_id}/tema/{entry_id}")
def set_tema_new(
request: Request,
logged_in: auth.RequireLogin,
- session_id: int,
+ playlist_id: int,
set_id: int,
entry_id: int,
title: Annotated[str, Form()],
@@ -182,7 +180,7 @@ def set_tema_new(
return playlist.set_tema(
request=request,
logged_in=logged_in,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
entry_id=entry_id,
tema_id=new_tema.id,
diff --git a/folkugat_web/api/routes/sessio/set_page.py b/folkugat_web/api/routes/playlist/set_page.py
similarity index 60%
rename from folkugat_web/api/routes/sessio/set_page.py
rename to folkugat_web/api/routes/playlist/set_page.py
index bdc80fe..92c4442 100644
--- a/folkugat_web/api/routes/sessio/set_page.py
+++ b/folkugat_web/api/routes/playlist/set_page.py
@@ -7,11 +7,11 @@ from folkugat_web.templates import templates
router = get_router()
-@router.get("/sessio/{session_id}/set/{set_id}")
+@router.get("/playlist/{playlist_id}/set/{set_id}")
def page(
request: Request,
logged_in: auth.LoggedIn,
- session_id: int,
+ playlist_id: int,
set_id: int,
):
return templates.TemplateResponse(
@@ -19,17 +19,22 @@ def page(
{
"request": request,
"page_title": "Folkugat",
- "content": f"/api/content/sessio/{session_id}/set/{set_id}",
+ "content": f"/api/content/playlist/{playlist_id}/set/{set_id}",
"logged_in": logged_in,
}
)
-@router.get("/api/content/sessio/{session_id}/set/{set_id}")
+@router.get("/api/content/playlist/{playlist_id}/set/{set_id}")
async def contingut(
request: Request,
logged_in: auth.LoggedIn,
- session_id: int,
+ playlist_id: int,
set_id: int,
):
- return await set_page.pagina(request, session_id, set_id, logged_in)
+ return await set_page.pagina(
+ request=request,
+ playlist_id=playlist_id,
+ set_id=set_id,
+ logged_in=logged_in,
+ )
diff --git a/folkugat_web/api/routes/sessio/__init__.py b/folkugat_web/api/routes/sessio/__init__.py
index ebb6045..e3bf067 100644
--- a/folkugat_web/api/routes/sessio/__init__.py
+++ b/folkugat_web/api/routes/sessio/__init__.py
@@ -1,12 +1,11 @@
from folkugat_web.api.router import get_router
-from . import cartell, index, live, playlist, set_page
+from . import cartell, index, live, notes
router = get_router()
router.include_router(cartell.router)
router.include_router(index.router)
router.include_router(live.router)
-router.include_router(set_page.router)
-router.include_router(playlist.router)
+router.include_router(notes.router)
__all__ = ["router"]
diff --git a/folkugat_web/api/routes/sessio/notes.py b/folkugat_web/api/routes/sessio/notes.py
new file mode 100644
index 0000000..7e0af11
--- /dev/null
+++ b/folkugat_web/api/routes/sessio/notes.py
@@ -0,0 +1,47 @@
+import dataclasses
+from typing import Annotated
+
+from fastapi import HTTPException, Request
+from fastapi.params import Form
+from folkugat_web.api.router import get_router
+from folkugat_web.fragments.sessio import notes
+from folkugat_web.services import auth
+from folkugat_web.services import sessions as sessions_service
+
+router = get_router()
+
+
+@router.get("/api/sessio/{session_id}/notes")
+def get_notes(
+ request: Request,
+ logged_in: auth.LoggedIn,
+ session_id: int,
+):
+ return notes.notes(request, session_id, logged_in)
+
+
+@router.get("/api/sessio/{session_id}/notes/editor")
+def get_notes_editor(
+ request: Request,
+ _: auth.RequireLogin,
+ session_id: int,
+):
+ return notes.notes_editor(request, session_id)
+
+
+@router.put("/api/sessio/{session_id}/notes")
+async def set_notes(
+ request: Request,
+ logged_in: auth.RequireLogin,
+ session_id: int,
+ content: Annotated[str, Form()],
+):
+ session = sessions_service.get_session(session_id=session_id)
+ if not session:
+ raise HTTPException(status_code=404, detail="Could not find session")
+ new_session = dataclasses.replace(
+ session,
+ notes=content.strip(),
+ )
+ sessions_service.set_session(new_session)
+ return notes.notes(request, session_id, logged_in)
diff --git a/folkugat_web/assets/static/css/main.css b/folkugat_web/assets/static/css/main.css
index 4a6c748..c6c2ae4 100644
--- a/folkugat_web/assets/static/css/main.css
+++ b/folkugat_web/assets/static/css/main.css
@@ -1,1154 +1 @@
-*, ::before, ::after {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
-}
-
-::backdrop {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
-}
-
-/*
-! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
-*/
-
-/*
-1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
-2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
-*/
-
-*,
-::before,
-::after {
- box-sizing: border-box;
- /* 1 */
- border-width: 0;
- /* 2 */
- border-style: solid;
- /* 2 */
- border-color: #e5e7eb;
- /* 2 */
-}
-
-::before,
-::after {
- --tw-content: '';
-}
-
-/*
-1. Use a consistent sensible line-height in all browsers.
-2. Prevent adjustments of font size after orientation changes in iOS.
-3. Use a more readable tab size.
-4. Use the user's configured `sans` font-family by default.
-5. Use the user's configured `sans` font-feature-settings by default.
-6. Use the user's configured `sans` font-variation-settings by default.
-7. Disable tap highlights on iOS
-*/
-
-html,
-:host {
- line-height: 1.5;
- /* 1 */
- -webkit-text-size-adjust: 100%;
- /* 2 */
- -moz-tab-size: 4;
- /* 3 */
- -o-tab-size: 4;
- tab-size: 4;
- /* 3 */
- font-family: Inter, sans-serif;
- /* 4 */
- font-feature-settings: normal;
- /* 5 */
- font-variation-settings: normal;
- /* 6 */
- -webkit-tap-highlight-color: transparent;
- /* 7 */
-}
-
-/*
-1. Remove the margin in all browsers.
-2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
-*/
-
-body {
- margin: 0;
- /* 1 */
- line-height: inherit;
- /* 2 */
-}
-
-/*
-1. Add the correct height in Firefox.
-2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
-3. Ensure horizontal rules are visible by default.
-*/
-
-hr {
- height: 0;
- /* 1 */
- color: inherit;
- /* 2 */
- border-top-width: 1px;
- /* 3 */
-}
-
-/*
-Add the correct text decoration in Chrome, Edge, and Safari.
-*/
-
-abbr:where([title]) {
- -webkit-text-decoration: underline dotted;
- text-decoration: underline dotted;
-}
-
-/*
-Remove the default font size and weight for headings.
-*/
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- font-size: inherit;
- font-weight: inherit;
-}
-
-/*
-Reset links to optimize for opt-in styling instead of opt-out.
-*/
-
-a {
- color: inherit;
- text-decoration: inherit;
-}
-
-/*
-Add the correct font weight in Edge and Safari.
-*/
-
-b,
-strong {
- font-weight: bolder;
-}
-
-/*
-1. Use the user's configured `mono` font-family by default.
-2. Use the user's configured `mono` font-feature-settings by default.
-3. Use the user's configured `mono` font-variation-settings by default.
-4. Correct the odd `em` font sizing in all browsers.
-*/
-
-code,
-kbd,
-samp,
-pre {
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- /* 1 */
- font-feature-settings: normal;
- /* 2 */
- font-variation-settings: normal;
- /* 3 */
- font-size: 1em;
- /* 4 */
-}
-
-/*
-Add the correct font size in all browsers.
-*/
-
-small {
- font-size: 80%;
-}
-
-/*
-Prevent `sub` and `sup` elements from affecting the line height in all browsers.
-*/
-
-sub,
-sup {
- font-size: 75%;
- line-height: 0;
- position: relative;
- vertical-align: baseline;
-}
-
-sub {
- bottom: -0.25em;
-}
-
-sup {
- top: -0.5em;
-}
-
-/*
-1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
-2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
-3. Remove gaps between table borders by default.
-*/
-
-table {
- text-indent: 0;
- /* 1 */
- border-color: inherit;
- /* 2 */
- border-collapse: collapse;
- /* 3 */
-}
-
-/*
-1. Change the font styles in all browsers.
-2. Remove the margin in Firefox and Safari.
-3. Remove default padding in all browsers.
-*/
-
-button,
-input,
-optgroup,
-select,
-textarea {
- font-family: inherit;
- /* 1 */
- font-feature-settings: inherit;
- /* 1 */
- font-variation-settings: inherit;
- /* 1 */
- font-size: 100%;
- /* 1 */
- font-weight: inherit;
- /* 1 */
- line-height: inherit;
- /* 1 */
- letter-spacing: inherit;
- /* 1 */
- color: inherit;
- /* 1 */
- margin: 0;
- /* 2 */
- padding: 0;
- /* 3 */
-}
-
-/*
-Remove the inheritance of text transform in Edge and Firefox.
-*/
-
-button,
-select {
- text-transform: none;
-}
-
-/*
-1. Correct the inability to style clickable types in iOS and Safari.
-2. Remove default button styles.
-*/
-
-button,
-input:where([type='button']),
-input:where([type='reset']),
-input:where([type='submit']) {
- -webkit-appearance: button;
- /* 1 */
- background-color: transparent;
- /* 2 */
- background-image: none;
- /* 2 */
-}
-
-/*
-Use the modern Firefox focus style for all focusable elements.
-*/
-
-:-moz-focusring {
- outline: auto;
-}
-
-/*
-Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
-*/
-
-:-moz-ui-invalid {
- box-shadow: none;
-}
-
-/*
-Add the correct vertical alignment in Chrome and Firefox.
-*/
-
-progress {
- vertical-align: baseline;
-}
-
-/*
-Correct the cursor style of increment and decrement buttons in Safari.
-*/
-
-::-webkit-inner-spin-button,
-::-webkit-outer-spin-button {
- height: auto;
-}
-
-/*
-1. Correct the odd appearance in Chrome and Safari.
-2. Correct the outline style in Safari.
-*/
-
-[type='search'] {
- -webkit-appearance: textfield;
- /* 1 */
- outline-offset: -2px;
- /* 2 */
-}
-
-/*
-Remove the inner padding in Chrome and Safari on macOS.
-*/
-
-::-webkit-search-decoration {
- -webkit-appearance: none;
-}
-
-/*
-1. Correct the inability to style clickable types in iOS and Safari.
-2. Change font properties to `inherit` in Safari.
-*/
-
-::-webkit-file-upload-button {
- -webkit-appearance: button;
- /* 1 */
- font: inherit;
- /* 2 */
-}
-
-/*
-Add the correct display in Chrome and Safari.
-*/
-
-summary {
- display: list-item;
-}
-
-/*
-Removes the default spacing and border for appropriate elements.
-*/
-
-blockquote,
-dl,
-dd,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-hr,
-figure,
-p,
-pre {
- margin: 0;
-}
-
-fieldset {
- margin: 0;
- padding: 0;
-}
-
-legend {
- padding: 0;
-}
-
-ol,
-ul,
-menu {
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-/*
-Reset default styling for dialogs.
-*/
-
-dialog {
- padding: 0;
-}
-
-/*
-Prevent resizing textareas horizontally by default.
-*/
-
-textarea {
- resize: vertical;
-}
-
-/*
-1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
-2. Set the default placeholder color to the user's configured gray 400 color.
-*/
-
-input::-moz-placeholder, textarea::-moz-placeholder {
- opacity: 1;
- /* 1 */
- color: #9ca3af;
- /* 2 */
-}
-
-input::placeholder,
-textarea::placeholder {
- opacity: 1;
- /* 1 */
- color: #9ca3af;
- /* 2 */
-}
-
-/*
-Set the default cursor for buttons.
-*/
-
-button,
-[role="button"] {
- cursor: pointer;
-}
-
-/*
-Make sure disabled buttons don't get the pointer cursor.
-*/
-
-:disabled {
- cursor: default;
-}
-
-/*
-1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
-2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
- This can trigger a poorly considered lint error in some tools but is included by design.
-*/
-
-img,
-svg,
-video,
-canvas,
-audio,
-iframe,
-embed,
-object {
- display: block;
- /* 1 */
- vertical-align: middle;
- /* 2 */
-}
-
-/*
-Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
-*/
-
-img,
-video {
- max-width: 100%;
- height: auto;
-}
-
-/* Make elements with the HTML hidden attribute stay hidden by default */
-
-[hidden]:where(:not([hidden="until-found"])) {
- display: none;
-}
-
-.visible {
- visibility: visible;
-}
-
-.invisible {
- visibility: hidden;
-}
-
-.static {
- position: static;
-}
-
-.absolute {
- position: absolute;
-}
-
-.relative {
- position: relative;
-}
-
-.-left-6 {
- left: -1.5rem;
-}
-
-.-top-5 {
- top: -1.25rem;
-}
-
-.m-0 {
- margin: 0px;
-}
-
-.m-1 {
- margin: 0.25rem;
-}
-
-.m-12 {
- margin: 3rem;
-}
-
-.m-2 {
- margin: 0.5rem;
-}
-
-.m-3 {
- margin: 0.75rem;
-}
-
-.m-4 {
- margin: 1rem;
-}
-
-.m-6 {
- margin: 1.5rem;
-}
-
-.m-8 {
- margin: 2rem;
-}
-
-.mx-1 {
- margin-left: 0.25rem;
- margin-right: 0.25rem;
-}
-
-.mx-12 {
- margin-left: 3rem;
- margin-right: 3rem;
-}
-
-.mx-2 {
- margin-left: 0.5rem;
- margin-right: 0.5rem;
-}
-
-.mx-4 {
- margin-left: 1rem;
- margin-right: 1rem;
-}
-
-.my-1 {
- margin-top: 0.25rem;
- margin-bottom: 0.25rem;
-}
-
-.my-2 {
- margin-top: 0.5rem;
- margin-bottom: 0.5rem;
-}
-
-.my-3 {
- margin-top: 0.75rem;
- margin-bottom: 0.75rem;
-}
-
-.mb-3 {
- margin-bottom: 0.75rem;
-}
-
-.ml-2 {
- margin-left: 0.5rem;
-}
-
-.ml-4 {
- margin-left: 1rem;
-}
-
-.ml-auto {
- margin-left: auto;
-}
-
-.mr-1 {
- margin-right: 0.25rem;
-}
-
-.mr-2 {
- margin-right: 0.5rem;
-}
-
-.mt-1 {
- margin-top: 0.25rem;
-}
-
-.mt-2 {
- margin-top: 0.5rem;
-}
-
-.mt-3 {
- margin-top: 0.75rem;
-}
-
-.mt-4 {
- margin-top: 1rem;
-}
-
-.mt-8 {
- margin-top: 2rem;
-}
-
-.block {
- display: block;
-}
-
-.inline-block {
- display: inline-block;
-}
-
-.flex {
- display: flex;
-}
-
-.hidden {
- display: none;
-}
-
-.h-4\/5 {
- height: 80%;
-}
-
-.h-auto {
- height: auto;
-}
-
-.h-px {
- height: 1px;
-}
-
-.min-h-\[400px\] {
- min-height: 400px;
-}
-
-.w-0 {
- width: 0px;
-}
-
-.w-1\/2 {
- width: 50%;
-}
-
-.w-full {
- width: 100%;
-}
-
-.min-w-0 {
- min-width: 0px;
-}
-
-.min-w-full {
- min-width: 100%;
-}
-
-.max-w-3xl {
- max-width: 48rem;
-}
-
-.max-w-4xl {
- max-width: 56rem;
-}
-
-.max-w-\[655px\] {
- max-width: 655px;
-}
-
-.max-w-full {
- max-width: 100%;
-}
-
-.max-w-xl {
- max-width: 36rem;
-}
-
-.max-w-xs {
- max-width: 20rem;
-}
-
-.flex-1 {
- flex: 1 1 0%;
-}
-
-.flex-auto {
- flex: 1 1 auto;
-}
-
-.flex-none {
- flex: none;
-}
-
-.shrink-0 {
- flex-shrink: 0;
-}
-
-.grow {
- flex-grow: 1;
-}
-
-@keyframes fadeIn {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-}
-
-.animate-fade-in-one {
- animation: fadeIn 1s ease-in 0.3s 1 normal forwards;
-}
-
-@keyframes fadeIn {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-}
-
-.animate-fade-in-three {
- animation: fadeIn 1s ease-in 0.9s 1 normal forwards;
-}
-
-@keyframes fadeIn {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-}
-
-.animate-fade-in-two {
- animation: fadeIn 1s ease-in 0.6s 1 normal forwards;
-}
-
-@keyframes grow {
- 0% {
- width: 0%;
- }
-
- 100% {
- width: 100%;
- }
-}
-
-.animate-grow {
- animation: grow 0.3s ease-in 0s 1 normal forwards;
-}
-
-@keyframes marquee {
- 0% {
- transform: translateX(0%);
- }
-
- 100% {
- transform: translateX(-100%);
- }
-}
-
-.animate-marquee {
- animation: marquee 25s linear infinite;
-}
-
-.cursor-pointer {
- cursor: pointer;
-}
-
-.flex-row {
- flex-direction: row;
-}
-
-.flex-col {
- flex-direction: column;
-}
-
-.flex-wrap {
- flex-wrap: wrap;
-}
-
-.flex-nowrap {
- flex-wrap: nowrap;
-}
-
-.items-start {
- align-items: flex-start;
-}
-
-.items-center {
- align-items: center;
-}
-
-.justify-end {
- justify-content: flex-end;
-}
-
-.justify-center {
- justify-content: center;
-}
-
-.gap-2 {
- gap: 0.5rem;
-}
-
-.overflow-hidden {
- overflow: hidden;
-}
-
-.whitespace-nowrap {
- white-space: nowrap;
-}
-
-.break-words {
- overflow-wrap: break-word;
-}
-
-.rounded {
- border-radius: 0.25rem;
-}
-
-.rounded-lg {
- border-radius: 0.5rem;
-}
-
-.rounded-md {
- border-radius: 0.375rem;
-}
-
-.border {
- border-width: 1px;
-}
-
-.border-0 {
- border-width: 0px;
-}
-
-.border-none {
- border-style: none;
-}
-
-.border-beige {
- --tw-border-opacity: 1;
- border-color: rgb(178 124 9 / var(--tw-border-opacity, 1));
-}
-
-.border-yellow-50 {
- --tw-border-opacity: 1;
- border-color: rgb(254 252 232 / var(--tw-border-opacity, 1));
-}
-
-.bg-beige {
- --tw-bg-opacity: 1;
- background-color: rgb(178 124 9 / var(--tw-bg-opacity, 1));
-}
-
-.bg-brown {
- --tw-bg-opacity: 1;
- background-color: rgb(76 16 30 / var(--tw-bg-opacity, 1));
-}
-
-.bg-white {
- --tw-bg-opacity: 1;
- background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
-}
-
-.p-0 {
- padding: 0px;
-}
-
-.p-1 {
- padding: 0.25rem;
-}
-
-.p-12 {
- padding: 3rem;
-}
-
-.p-2 {
- padding: 0.5rem;
-}
-
-.p-4 {
- padding: 1rem;
-}
-
-.px-10 {
- padding-left: 2.5rem;
- padding-right: 2.5rem;
-}
-
-.px-2 {
- padding-left: 0.5rem;
- padding-right: 0.5rem;
-}
-
-.px-3 {
- padding-left: 0.75rem;
- padding-right: 0.75rem;
-}
-
-.px-4 {
- padding-left: 1rem;
- padding-right: 1rem;
-}
-
-.py-1 {
- padding-top: 0.25rem;
- padding-bottom: 0.25rem;
-}
-
-.py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
-}
-
-.py-4 {
- padding-top: 1rem;
- padding-bottom: 1rem;
-}
-
-.pb-2 {
- padding-bottom: 0.5rem;
-}
-
-.pl-5 {
- padding-left: 1.25rem;
-}
-
-.pr-2 {
- padding-right: 0.5rem;
-}
-
-.pt-4 {
- padding-top: 1rem;
-}
-
-.text-left {
- text-align: left;
-}
-
-.text-center {
- text-align: center;
-}
-
-.text-right {
- text-align: right;
-}
-
-.text-2xl {
- font-size: 1.5rem;
- line-height: 2rem;
-}
-
-.text-3xl {
- font-size: 1.875rem;
- line-height: 2.25rem;
-}
-
-.text-sm {
- font-size: 0.875rem;
- line-height: 1.25rem;
-}
-
-.text-xl {
- font-size: 1.25rem;
- line-height: 1.75rem;
-}
-
-.text-xs {
- font-size: 0.75rem;
- line-height: 1rem;
-}
-
-.font-bold {
- font-weight: 700;
-}
-
-.capitalize {
- text-transform: capitalize;
-}
-
-.text-beige {
- --tw-text-opacity: 1;
- color: rgb(178 124 9 / var(--tw-text-opacity, 1));
-}
-
-.text-black {
- --tw-text-opacity: 1;
- color: rgb(0 0 0 / var(--tw-text-opacity, 1));
-}
-
-.text-brown {
- --tw-text-opacity: 1;
- color: rgb(76 16 30 / var(--tw-text-opacity, 1));
-}
-
-.text-gray-400 {
- --tw-text-opacity: 1;
- color: rgb(156 163 175 / var(--tw-text-opacity, 1));
-}
-
-.text-red-400 {
- --tw-text-opacity: 1;
- color: rgb(248 113 113 / var(--tw-text-opacity, 1));
-}
-
-.text-red-500 {
- --tw-text-opacity: 1;
- color: rgb(239 68 68 / var(--tw-text-opacity, 1));
-}
-
-.text-white {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity, 1));
-}
-
-.text-yellow-50 {
- --tw-text-opacity: 1;
- color: rgb(254 252 232 / var(--tw-text-opacity, 1));
-}
-
-.opacity-0 {
- opacity: 0;
-}
-
-.transition-opacity {
- transition-property: opacity;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
-}
-
-.duration-200 {
- transition-duration: 200ms;
-}
-
-.focus\:outline-none:focus {
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-@media (min-width: 450px) {
- .min-\[450px\]\:flex-row {
- flex-direction: row;
- }
-}
-
-@media (min-width: 640px) {
- .sm\:text-3xl {
- font-size: 1.875rem;
- line-height: 2.25rem;
- }
-
- .sm\:text-8xl {
- font-size: 6rem;
- line-height: 1;
- }
-}
+*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:Inter,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.absolute{position:absolute}.relative{position:relative}.-left-6{left:-1.5rem}.-top-5{top:-1.25rem}.m-0{margin:0}.m-1{margin:.25rem}.m-12{margin:3rem}.m-2{margin:.5rem}.m-3{margin:.75rem}.m-4{margin:1rem}.m-6{margin:1.5rem}.m-8{margin:2rem}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-12{margin-left:3rem;margin-right:3rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-bottom:.25rem;margin-top:.25rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-3{margin-top:.75rem}.mb-3,.my-3{margin-bottom:.75rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.hidden{display:none}.h-4\/5{height:80%}.h-auto{height:auto}.h-px{height:1px}.min-h-\[400px\]{min-height:400px}.w-0{width:0}.w-1\/2{width:50%}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-\[655px\]{max-width:655px}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.animate-fade-in-one{animation:fadeIn 1s ease-in .3s 1 normal forwards}.animate-fade-in-three{animation:fadeIn 1s ease-in .9s 1 normal forwards}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.animate-fade-in-two{animation:fadeIn 1s ease-in .6s 1 normal forwards}@keyframes grow{0%{width:0}to{width:100%}}.animate-grow{animation:grow .3s ease-in 0s 1 normal forwards}@keyframes marquee{0%{transform:translateX(0)}to{transform:translateX(-100%)}}.animate-marquee{animation:marquee 25s linear infinite}.cursor-pointer{cursor:pointer}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.gap-2{gap:.5rem}.overflow-hidden{overflow:hidden}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-0{border-width:0}.border-none{border-style:none}.border-beige{--tw-border-opacity:1;border-color:rgb(178 124 9/var(--tw-border-opacity,1))}.border-yellow-50{--tw-border-opacity:1;border-color:rgb(254 252 232/var(--tw-border-opacity,1))}.bg-beige{--tw-bg-opacity:1;background-color:rgb(178 124 9/var(--tw-bg-opacity,1))}.bg-brown{--tw-bg-opacity:1;background-color:rgb(76 16 30/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-0{padding:0}.p-1{padding:.25rem}.p-12{padding:3rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-4{padding-bottom:1rem;padding-top:1rem}.pb-2{padding-bottom:.5rem}.pl-5{padding-left:1.25rem}.pr-2{padding-right:.5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.capitalize{text-transform:capitalize}.text-beige{--tw-text-opacity:1;color:rgb(178 124 9/var(--tw-text-opacity,1))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-brown{--tw-text-opacity:1;color:rgb(76 16 30/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-50{--tw-text-opacity:1;color:rgb(254 252 232/var(--tw-text-opacity,1))}.opacity-0{opacity:0}.transition-opacity{transition-duration:.15s;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-200{transition-duration:.2s}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}@media (min-width:450px){.min-\[450px\]\:flex-row{flex-direction:row}}@media (min-width:640px){.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-8xl{font-size:6rem;line-height:1}}
\ No newline at end of file
diff --git a/folkugat_web/assets/templates/fragments/sessio/playlist.html b/folkugat_web/assets/templates/fragments/playlist/playlist.html
similarity index 62%
rename from folkugat_web/assets/templates/fragments/sessio/playlist.html
rename to folkugat_web/assets/templates/fragments/playlist/playlist.html
index a28d012..3efba36 100644
--- a/folkugat_web/assets/templates/fragments/sessio/playlist.html
+++ b/folkugat_web/assets/templates/fragments/playlist/playlist.html
@@ -1,14 +1,14 @@
-
+
{% for set_entry in playlist.sets %}
{% set set_id = set_entry.id %}
- {% include "fragments/sessio/set_entry.html" %}
+ {% include "fragments/playlist/set_entry.html" %}
{% endfor %}
{% if logged_in %}
Afegeix set
diff --git a/folkugat_web/assets/templates/fragments/playlist/set/pagina.html b/folkugat_web/assets/templates/fragments/playlist/set/pagina.html
new file mode 100644
index 0000000..7b28688
--- /dev/null
+++ b/folkugat_web/assets/templates/fragments/playlist/set/pagina.html
@@ -0,0 +1,2 @@
+{% include "fragments/menu.html" %}
+{% include "fragments/playlist/set/set_page.html" %}
diff --git a/folkugat_web/assets/templates/fragments/sessio/set/set_page.html b/folkugat_web/assets/templates/fragments/playlist/set/set_page.html
similarity index 87%
rename from folkugat_web/assets/templates/fragments/sessio/set/set_page.html
rename to folkugat_web/assets/templates/fragments/playlist/set/set_page.html
index b3d3827..1e05d00 100644
--- a/folkugat_web/assets/templates/fragments/sessio/set/set_page.html
+++ b/folkugat_web/assets/templates/fragments/playlist/set/set_page.html
@@ -13,14 +13,14 @@
{% for tema_in_set in set.temes %}
{% if tema_in_set.tema is not none %}
{% set tema = tema_in_set.tema %}
- {% include "fragments/sessio/set/tema_title.html" %}
+ {% include "fragments/playlist/set/tema_title.html" %}
{% else %}
Desconegut
{% endif %}
{% endfor %}
- {% include "fragments/sessio/set/set_score.html"%}
+ {% include "fragments/playlist/set/set_score.html"%}
{% else %}
{% for tema_in_set in set.temes %}
@@ -31,7 +31,7 @@
{% endif %}
{% if tema_in_set.tema is not none %}
{% set tema = tema_in_set.tema %}
- {% include "fragments/sessio/set/tema.html"%}
+ {% include "fragments/playlist/set/tema.html"%}
{% else %}
Desconegut
diff --git a/folkugat_web/assets/templates/fragments/sessio/set/set_score.html b/folkugat_web/assets/templates/fragments/playlist/set/set_score.html
similarity index 100%
rename from folkugat_web/assets/templates/fragments/sessio/set/set_score.html
rename to folkugat_web/assets/templates/fragments/playlist/set/set_score.html
diff --git a/folkugat_web/assets/templates/fragments/sessio/set/tema.html b/folkugat_web/assets/templates/fragments/playlist/set/tema.html
similarity index 89%
rename from folkugat_web/assets/templates/fragments/sessio/set/tema.html
rename to folkugat_web/assets/templates/fragments/playlist/set/tema.html
index f9660ef..bc71a99 100644
--- a/folkugat_web/assets/templates/fragments/sessio/set/tema.html
+++ b/folkugat_web/assets/templates/fragments/playlist/set/tema.html
@@ -1,4 +1,4 @@
-{% include "fragments/sessio/set/tema_title.html" %}
+{% include "fragments/playlist/set/tema_title.html" %}
{% if tema.main_score() is not none %}
diff --git a/folkugat_web/assets/templates/fragments/sessio/set/tema_title.html b/folkugat_web/assets/templates/fragments/playlist/set/tema_title.html
similarity index 100%
rename from folkugat_web/assets/templates/fragments/sessio/set/tema_title.html
rename to folkugat_web/assets/templates/fragments/playlist/set/tema_title.html
diff --git a/folkugat_web/assets/templates/fragments/sessio/set_entry.html b/folkugat_web/assets/templates/fragments/playlist/set_entry.html
similarity index 81%
rename from folkugat_web/assets/templates/fragments/sessio/set_entry.html
rename to folkugat_web/assets/templates/fragments/playlist/set_entry.html
index 8ad58fd..0772b9a 100644
--- a/folkugat_web/assets/templates/fragments/sessio/set_entry.html
+++ b/folkugat_web/assets/templates/fragments/playlist/set_entry.html
@@ -2,12 +2,12 @@
m-4 rounded-lg bg-white
px-2 py-1 my-1"
id="set-entry-{{ set_id }}"
- hx-get="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}"
+ hx-get="/api/playlist/{{ playlist_id }}/set/{{ set_id }}"
hx-target="#set-entry-{{ set_id }}"
hx-swap="outerHTML"
hx-trigger="reload-set-{{ set_id }}">
{% if set_entry.temes | length > 1 or not set_entry.temes[0].tema %}
- {% set set_url = "/sessio/%d/set/%d" | format(session_id, set_id) %}
+ {% set set_url = "/playlist/%d/set/%d" | format(playlist_id, set_id) %}
{% else %}
{% set set_url = "/tema/%d" | format(set_entry.temes[0].tema_id) %}
{% endif %}
@@ -25,7 +25,7 @@
w-full max-w-[655px]">
{% if logged_in %}
@@ -36,15 +36,15 @@
class="flex flex-col items-start w-full">
{% for tema_entry in set_entry.temes %}
{% if new_entry %}
- {% include "fragments/sessio/tema_editor.html" %}
+ {% include "fragments/playlist/tema_editor.html" %}
{% else %}
- {% include "fragments/sessio/tema_entry.html" %}
+ {% include "fragments/playlist/tema_entry.html" %}
{% endif %}
{% endfor %}
{% if logged_in %}
diff --git a/folkugat_web/assets/templates/fragments/sessio/tema_editor.html b/folkugat_web/assets/templates/fragments/playlist/tema_editor.html
similarity index 81%
rename from folkugat_web/assets/templates/fragments/sessio/tema_editor.html
rename to folkugat_web/assets/templates/fragments/playlist/tema_editor.html
index 9d56ec9..329341e 100644
--- a/folkugat_web/assets/templates/fragments/sessio/tema_editor.html
+++ b/folkugat_web/assets/templates/fragments/playlist/tema_editor.html
@@ -12,13 +12,13 @@
class="border border-beige focus:outline-none
rounded text-center text-black
p-1 m-1"
- hx-get="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}/tema/{{ tema_entry.id }}/busca"
+ hx-get="/api/playlist/{{ playlist_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}/busca"
hx-trigger="revealed, keyup delay:500ms changed"
hx-target="#tune-entry-{{ tema_entry.id }}-search-results"
hx-swap="outerHTML"/>
diff --git a/folkugat_web/assets/templates/fragments/sessio/tema_entry.html b/folkugat_web/assets/templates/fragments/playlist/tema_entry.html
similarity index 85%
rename from folkugat_web/assets/templates/fragments/sessio/tema_entry.html
rename to folkugat_web/assets/templates/fragments/playlist/tema_entry.html
index 95217be..6a9a407 100644
--- a/folkugat_web/assets/templates/fragments/sessio/tema_entry.html
+++ b/folkugat_web/assets/templates/fragments/playlist/tema_entry.html
@@ -15,14 +15,14 @@
diff --git a/folkugat_web/assets/templates/fragments/sessio/tema_results.html b/folkugat_web/assets/templates/fragments/playlist/tema_results.html
similarity index 77%
rename from folkugat_web/assets/templates/fragments/sessio/tema_results.html
rename to folkugat_web/assets/templates/fragments/playlist/tema_results.html
index 9ecaecc..8b8f041 100644
--- a/folkugat_web/assets/templates/fragments/sessio/tema_results.html
+++ b/folkugat_web/assets/templates/fragments/playlist/tema_results.html
@@ -4,7 +4,7 @@
@@ -15,7 +15,7 @@
@@ -25,7 +25,7 @@
diff --git a/folkugat_web/assets/templates/fragments/sessio/notes.html b/folkugat_web/assets/templates/fragments/sessio/notes.html
new file mode 100644
index 0000000..3e40720
--- /dev/null
+++ b/folkugat_web/assets/templates/fragments/sessio/notes.html
@@ -0,0 +1,13 @@
+
+ Notes
+{% if logged_in %}
+
+
+
+{% endif %}
+
+{{ notes | safe }}
diff --git a/folkugat_web/assets/templates/fragments/sessio/notes_editor.html b/folkugat_web/assets/templates/fragments/sessio/notes_editor.html
new file mode 100644
index 0000000..4f60e50
--- /dev/null
+++ b/folkugat_web/assets/templates/fragments/sessio/notes_editor.html
@@ -0,0 +1,28 @@
+Notes
+
diff --git a/folkugat_web/assets/templates/fragments/sessio/pagina.html b/folkugat_web/assets/templates/fragments/sessio/pagina.html
index 2271d6a..4ce1aef 100644
--- a/folkugat_web/assets/templates/fragments/sessio/pagina.html
+++ b/folkugat_web/assets/templates/fragments/sessio/pagina.html
@@ -31,20 +31,26 @@
{% endif %}
{% if logged_in or session.notes %}
-
-
Notes
- {% if session.notes %}{{ session.notes }}{% endif %}
+
+ {% set notes = session.notes if session.notes else "" %}
+ {% include "fragments/sessio/notes.html" %}
{% endif %}
- {% if logged_in or False %}
+ {% if logged_in or (session.slowjam and session.slowjam.sets) %}
-
Slow Jam
+ Slow Jam
+ {% set playlist_id = session.slowjam.id %}
+ {% set playlist = session.slowjam %}
+ {% include "fragments/playlist/playlist.html" %}
{% endif %}
- {% if logged_in or playlist.sets %}
+ {% if logged_in or (session.setlist and session.setlist.sets) %}
Temes tocats
- {% include "fragments/sessio/playlist.html" %}
+ {% set playlist_id = session.setlist.id %}
+ {% set playlist = session.setlist %}
+ {% include "fragments/playlist/playlist.html" %}
{% endif %}
diff --git a/folkugat_web/assets/templates/fragments/sessio/set/pagina.html b/folkugat_web/assets/templates/fragments/sessio/set/pagina.html
deleted file mode 100644
index e062eba..0000000
--- a/folkugat_web/assets/templates/fragments/sessio/set/pagina.html
+++ /dev/null
@@ -1,2 +0,0 @@
-{% include "fragments/menu.html" %}
-{% include "fragments/sessio/set/set_page.html" %}
diff --git a/folkugat_web/dal/sql/playlists/conversion.py b/folkugat_web/dal/sql/playlists/conversion.py
index 663c6af..a5a5f4b 100644
--- a/folkugat_web/dal/sql/playlists/conversion.py
+++ b/folkugat_web/dal/sql/playlists/conversion.py
@@ -2,12 +2,13 @@ from typing import TypedDict
from folkugat_web.model import playlists as model
-PlaylistRowTuple = tuple[int, int, int, int | None]
+PlaylistRowTuple = tuple[int, str | None]
+PlaylistEntryRowTuple = tuple[int, int, int, int | None]
class PlaylistRowDict(TypedDict):
id: int | None
- session_id: int
+ playlist_id: int
set_id: int
tema_id: int | None
@@ -15,16 +16,16 @@ class PlaylistRowDict(TypedDict):
def playlist_entry_to_row(tema_in_set: model.PlaylistEntry) -> PlaylistRowDict:
return {
'id': tema_in_set.id,
- 'session_id': tema_in_set.session_id,
+ 'playlist_id': tema_in_set.playlist_id,
'set_id': tema_in_set.set_id,
'tema_id': tema_in_set.tema_id,
}
-def row_to_playlist_entry(row: PlaylistRowTuple) -> model.PlaylistEntry:
+def row_to_playlist_entry(row: PlaylistEntryRowTuple) -> model.PlaylistEntry:
return model.PlaylistEntry(
id=row[0],
- session_id=row[1],
+ playlist_id=row[1],
set_id=row[2],
tema_id=row[3],
)
diff --git a/folkugat_web/dal/sql/playlists/ddl.py b/folkugat_web/dal/sql/playlists/ddl.py
index 609b67c..2eff45a 100644
--- a/folkugat_web/dal/sql/playlists/ddl.py
+++ b/folkugat_web/dal/sql/playlists/ddl.py
@@ -4,15 +4,29 @@ from folkugat_web.dal.sql import Connection, get_connection
def create_db(con: Connection | None = None):
with get_connection(con) as con:
create_playlists_table(con)
+ create_playlist_entries_table(con)
def create_playlists_table(con: Connection):
query = """
CREATE TABLE IF NOT EXISTS playlists (
id INTEGER PRIMARY KEY,
- session_id INTEGER NOT NULL,
- set_id INTEGER NOT NULL,
- tema_id INTEGER
+ name TEXT
+ )
+ """
+ cur = con.cursor()
+ _ = cur.execute(query)
+
+
+def create_playlist_entries_table(con: Connection):
+ query = """
+ CREATE TABLE IF NOT EXISTS playlist_entries (
+ id INTEGER PRIMARY KEY,
+ playlist_id INTEGER NOT NULL,
+ set_id INTEGER NOT NULL,
+ tema_id INTEGER,
+ FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
+ FOREIGN KEY (tema_id) REFERENCES temes(id) ON DELETE CASCADE
)
"""
cur = con.cursor()
diff --git a/folkugat_web/dal/sql/playlists/query.py b/folkugat_web/dal/sql/playlists/query.py
index a0aabc1..738c5cc 100644
--- a/folkugat_web/dal/sql/playlists/query.py
+++ b/folkugat_web/dal/sql/playlists/query.py
@@ -1,11 +1,9 @@
-from collections.abc import Iterable, Iterator
+from collections.abc import Iterator
from typing import TypedDict
from folkugat_web.dal.sql import Connection, get_connection
from folkugat_web.dal.sql.sessions import conversion as sessions_conversion
-from folkugat_web.dal.sql.temes.conversion import row_to_tema
from folkugat_web.model import playlists as model
-from folkugat_web.model import temes as temes_model
from folkugat_web.model.sessions import Session
from folkugat_web.utils import groupby
@@ -15,13 +13,13 @@ from . import conversion
class QueryData(TypedDict, total=False):
id: int
set_id: int
- session_id: int
+ playlist_id: int
def _filter_clause(
entry_id: int | None = None,
set_id: int | None = None,
- session_id: int | None = None,
+ playlist_id: int | None = None,
) -> tuple[str, QueryData]:
filter_clauses: list[str] = []
query_data: QueryData = {}
@@ -32,9 +30,9 @@ def _filter_clause(
if set_id is not None:
filter_clauses.append("set_id = :set_id")
query_data["set_id"] = set_id
- if session_id is not None:
- filter_clauses.append("session_id = :session_id")
- query_data["session_id"] = session_id
+ if playlist_id is not None:
+ filter_clauses.append("playlist_id = :playlist_id")
+ query_data["playlist_id"] = playlist_id
return " AND ".join(filter_clauses), query_data
@@ -42,14 +40,14 @@ def _filter_clause(
def get_playlist_entries(
entry_id: int | None = None,
set_id: int | None = None,
- session_id: int | None = None,
+ playlist_id: int | None = None,
con: Connection | None = None,
) -> Iterator[model.PlaylistEntry]:
- filter_clause, data = _filter_clause(entry_id=entry_id, set_id=set_id, session_id=session_id)
+ filter_clause, data = _filter_clause(entry_id=entry_id, set_id=set_id, playlist_id=playlist_id)
query = f"""
SELECT
- id, session_id, set_id, tema_id
- FROM playlists
+ id, playlist_id, set_id, tema_id
+ FROM playlist_entries
WHERE {filter_clause}
ORDER BY id ASC
"""
@@ -57,59 +55,3 @@ def get_playlist_entries(
cur = con.cursor()
_ = cur.execute(query, data)
return map(conversion.row_to_playlist_entry, cur.fetchall())
-
-
-GetTuneSessionsRow = tuple[int, int, str, str, str, str | None, str | None, str | None, str | None, bool]
-
-
-def get_tune_sessions(tema_ids: list[int], con: Connection | None = None) -> dict[int, list[Session]]:
- placeholders = ", ".join(["?" for _ in tema_ids])
- query = f"""
- SELECT
- p.tema_id, s.id, s.date, s.start_time, s.end_time, s.venue_name,
- s.venue_url, s.notes, s.cartell_url, s.is_live
- FROM playlists p JOIN sessions s ON p.session_id = s.id
- WHERE p.tema_id IN ({placeholders})
- """
- with get_connection(con) as con:
- cur = con.cursor()
- _ = cur.execute(query, tema_ids)
- result_rows: Iterable[GetTuneSessionsRow] = cur.fetchall()
- return dict(groupby(
- result_rows,
- key_fn=lambda row: row[0],
- group_fn=lambda rows: list(sessions_conversion.row_to_session(row[1:]) for row in rows)
- ))
-
-
-CommonlyPlayedTuneRow = tuple[int, str, str, str, str, int, int]
-
-
-def get_commonly_played_tunes(
- tema_id: int,
- con: Connection | None = None,
-) -> list[temes_model.CommonlyPlayedTema]:
- query = """
- SELECT
- id, title, alternatives, creation_date, modification_date, hidden, count
- FROM (
- SELECT tema_id, count(*) count FROM playlists p JOIN (
- SELECT session_id, set_id
- FROM playlists
- WHERE tema_id = ?
- ) s
- ON p.session_id == s.session_id AND p.set_id == s.set_id
- WHERE tema_id != ?
- GROUP BY tema_id
- ) common JOIN temes t ON common.tema_id == t.id
- """
- with get_connection(con) as con:
- cur = con.cursor()
- _ = cur.execute(query, [tema_id, tema_id])
- result_rows: Iterable[CommonlyPlayedTuneRow] = cur.fetchall()
- return [
- temes_model.CommonlyPlayedTema(
- tema=row_to_tema(row[:6]),
- count=row[6],
- ) for row in result_rows
- ]
diff --git a/folkugat_web/dal/sql/playlists/write.py b/folkugat_web/dal/sql/playlists/write.py
index e8135a6..5d7f55e 100644
--- a/folkugat_web/dal/sql/playlists/write.py
+++ b/folkugat_web/dal/sql/playlists/write.py
@@ -4,27 +4,66 @@ from folkugat_web.model import playlists as model
from . import conversion
-def insert_playlist_entry(pl_entry: model.PlaylistEntry, con: Connection | None = None) -> model.PlaylistEntry:
+def create_playlist(
+ name: str | None = None,
+ con: Connection | None = None,
+) -> int:
query = """
INSERT INTO playlists
- (id, session_id, set_id, tema_id)
+ (name)
VALUES
- (:id, :session_id, :set_id, :tema_id)
+ (:name)
+ RETURNING *
+ """
+ data = dict(name=name)
+ with get_connection(con) as con:
+ cur = con.cursor()
+ _ = cur.execute(query, data)
+ row: conversion.PlaylistRowTuple = cur.fetchone()
+ return row[0]
+
+
+def delete_playlist(
+ playlist_id: int,
+ con: Connection | None = None,
+) -> None:
+ query = """
+ DELETE FROM playlists
+ WHERE id = :id
+ """
+ data = dict(playlist_id=playlist_id)
+ with get_connection(con) as con:
+ cur = con.cursor()
+ _ = cur.execute(query, data)
+
+
+def insert_playlist_entry(
+ pl_entry: model.PlaylistEntry,
+ con: Connection | None = None,
+) -> model.PlaylistEntry:
+ query = """
+ INSERT INTO playlist_entries
+ (id, playlist_id, set_id, tema_id)
+ VALUES
+ (:id, :playlist_id, :set_id, :tema_id)
RETURNING *
"""
data = conversion.playlist_entry_to_row(pl_entry)
with get_connection(con) as con:
cur = con.cursor()
_ = cur.execute(query, data)
- row: conversion.PlaylistRowTuple = cur.fetchone()
+ row: conversion.PlaylistEntryRowTuple = cur.fetchone()
return conversion.row_to_playlist_entry(row)
-def update_playlist_entry(entry: model.PlaylistEntry, con: Connection | None = None):
+def update_playlist_entry(
+ entry: model.PlaylistEntry,
+ con: Connection | None = None,
+):
query = """
- UPDATE playlists
+ UPDATE playlist_entries
SET
- id = :id, session_id = :session_id, set_id = :set_id, tema_id = :tema_id
+ id = :id, playlist_id = :playlist_id, set_id = :set_id, tema_id = :tema_id
WHERE
id = :id
"""
@@ -35,9 +74,12 @@ def update_playlist_entry(entry: model.PlaylistEntry, con: Connection | None = N
return
-def delete_playlist_entry(entry_id: int, con: Connection | None = None):
+def delete_playlist_entry(
+ entry_id: int,
+ con: Connection | None = None,
+):
query = """
- DELETE FROM playlists
+ DELETE FROM playlist_entries
WHERE id = :id
"""
data = dict(id=entry_id)
@@ -47,12 +89,16 @@ def delete_playlist_entry(entry_id: int, con: Connection | None = None):
return
-def delete_playlist_set(session_id: int, set_id: int, con: Connection | None = None):
+def delete_playlist_set(
+ playlist_id: int,
+ set_id: int,
+ con: Connection | None = None,
+):
query = """
- DELETE FROM playlists
- WHERE session_id = :session_id AND set_id = :set_id
+ DELETE FROM playlist_entries
+ WHERE playlist_id = :playlist_id AND set_id = :set_id
"""
- data = dict(session_id=session_id, set_id=set_id)
+ data = dict(playlist_id=playlist_id, set_id=set_id)
with get_connection(con) as con:
cur = con.cursor()
_ = cur.execute(query, data)
diff --git a/folkugat_web/dal/sql/sessions/ddl.py b/folkugat_web/dal/sql/sessions/ddl.py
index 5e99c0b..ee430c3 100644
--- a/folkugat_web/dal/sql/sessions/ddl.py
+++ b/folkugat_web/dal/sql/sessions/ddl.py
@@ -4,6 +4,7 @@ from folkugat_web.dal.sql import Connection, get_connection
def create_db(con: Connection | None = None):
with get_connection(con) as con:
create_sessions_table(con)
+ create_session_playlists_table(con)
def create_sessions_table(con: Connection):
@@ -22,3 +23,18 @@ def create_sessions_table(con: Connection):
"""
cur = con.cursor()
_ = cur.execute(query)
+
+
+def create_session_playlists_table(con: Connection):
+ query = """
+ CREATE TABLE IF NOT EXISTS session_playlists (
+ session_id INTEGER,
+ playlist_type TEXT,
+ playlist_id INTEGER NOT NULL,
+ PRIMARY KEY (session_id, playlist_type),
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
+ FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE
+ )
+ """
+ cur = con.cursor()
+ _ = cur.execute(query)
diff --git a/folkugat_web/dal/sql/sessions/playlists.py b/folkugat_web/dal/sql/sessions/playlists.py
new file mode 100644
index 0000000..b483b32
--- /dev/null
+++ b/folkugat_web/dal/sql/sessions/playlists.py
@@ -0,0 +1,138 @@
+from folkugat_web.dal.sql import Connection, get_connection
+from folkugat_web.dal.sql.temes.conversion import row_to_tema
+from folkugat_web.model import sessions as sessions_model
+from folkugat_web.model import temes as temes_model
+from folkugat_web.model.playlists import PlaylistType
+from folkugat_web.utils import groupby
+
+from . import conversion
+
+
+def get_playlist_id(
+ session_id: int,
+ playlist_type: PlaylistType,
+ con: Connection | None = None,
+) -> int | None:
+ query = f"""
+ SELECT playlist_id
+ FROM session_playlists
+ WHERE
+ session_id = :session_id AND
+ playlist_type = :playlist_type
+ """
+ data = dict(
+ session_id=session_id,
+ playlist_type=playlist_type.value,
+ )
+ with get_connection(con) as con:
+ cur = con.cursor()
+ _ = cur.execute(query, data)
+ if row := cur.fetchone():
+ return row[0]
+ return None
+
+
+def insert_playlist(
+ session_id: int,
+ playlist_type: PlaylistType,
+ playlist_id: int,
+ con: Connection | None = None,
+):
+ query = f"""
+ INSERT INTO session_playlists
+ (session_id, playlist_type, playlist_id)
+ VALUES
+ (:session_id, :playlist_type, :playlist_id)
+ """
+ data = dict(
+ session_id=session_id,
+ playlist_type=playlist_type.value,
+ playlist_id=playlist_id,
+ )
+ with get_connection(con) as con:
+ cur = con.cursor()
+ _ = cur.execute(query, data)
+
+
+def delete_playlist(
+ session_id: int,
+ playlist_type: PlaylistType,
+ con: Connection | None = None,
+):
+ query = f"""
+ DELETE FROM session_playlists
+ WHERE
+ session_id = :session_id AND
+ playlist_type = :playlist_type
+ """
+ data = dict(
+ session_id=session_id,
+ playlist_type=playlist_type.value,
+ )
+ with get_connection(con) as con:
+ cur = con.cursor()
+ _ = cur.execute(query, data)
+
+
+GetTuneSessionsRow = tuple[int, int, str, str, str, str | None, str | None, str | None, str | None, bool]
+
+
+def get_tune_sessions(
+ tema_ids: list[int],
+ con: Connection | None = None,
+) -> dict[int, list[sessions_model.Session]]:
+ placeholders = ", ".join(["?" for _ in tema_ids])
+ query = f"""
+ SELECT
+ p.tema_id, s.id, s.date, s.start_time, s.end_time, s.venue_name,
+ s.venue_url, s.notes, s.cartell_url, s.is_live
+ FROM playlist_entries p
+ JOIN session_playlists sp ON p.playlist_id = sp.playlist_id
+ JOIN sessions s ON sp.session_id = s.id
+ WHERE
+ p.tema_id IN ({placeholders}) AND
+ sp.playlist_type = "{PlaylistType.SESSION_SETLIST.value}"
+ """
+ with get_connection(con) as con:
+ cur = con.cursor()
+ _ = cur.execute(query, tema_ids)
+ result_rows: list[GetTuneSessionsRow] = cur.fetchall()
+ return dict(groupby(
+ result_rows,
+ key_fn=lambda row: row[0],
+ group_fn=lambda rows: list(conversion.row_to_session(row[1:]) for row in rows)
+ ))
+
+
+CommonlyPlayedTuneRow = tuple[int, str, str, str, str, int, int]
+
+
+def get_commonly_played_tunes(
+ tema_id: int,
+ con: Connection | None = None,
+) -> list[temes_model.CommonlyPlayedTema]:
+ query = f"""
+ SELECT
+ id, title, alternatives, creation_date, modification_date, hidden, count
+ FROM (
+ SELECT tema_id, count(*) count FROM playlist_entries p JOIN (
+ SELECT pe.playlist_id, pe.set_id
+ FROM playlist_entries pe JOIN session_playlists sp USING (playlist_id)
+ WHERE tema_id = :tema_id AND playlist_type = :playlist_type
+ ) s
+ ON p.playlist_id == s.playlist_id AND p.set_id == s.set_id
+ WHERE tema_id != :tema_id
+ GROUP BY tema_id
+ ) common JOIN temes t ON common.tema_id == t.id
+ """
+ data = dict(tema_id=tema_id, playlist_type=PlaylistType.SESSION_SETLIST.value)
+ with get_connection(con) as con:
+ cur = con.cursor()
+ _ = cur.execute(query, data)
+ result_rows: list[CommonlyPlayedTuneRow] = cur.fetchall()
+ return [
+ temes_model.CommonlyPlayedTema(
+ tema=row_to_tema(row[:6]),
+ count=row[6],
+ ) for row in result_rows
+ ]
diff --git a/folkugat_web/fragments/live.py b/folkugat_web/fragments/live.py
index 9c9f95c..fd6653e 100644
--- a/folkugat_web/fragments/live.py
+++ b/folkugat_web/fragments/live.py
@@ -14,7 +14,7 @@ def sessio_en_directe(request: Request):
raise RuntimeError("Got a session without id!")
current_set = None
- if playlist := playlists_service.get_playlist(session_id=session.id):
+ if playlist := service.get_session_setlist(session_id=session.id):
if playlist.sets:
current_set = playlists_service.add_temes_to_set(playlist.sets[-1])
diff --git a/folkugat_web/fragments/sessio/playlist.py b/folkugat_web/fragments/playlist.py
similarity index 61%
rename from folkugat_web/fragments/sessio/playlist.py
rename to folkugat_web/fragments/playlist.py
index 05467cb..87c700a 100644
--- a/folkugat_web/fragments/sessio/playlist.py
+++ b/folkugat_web/fragments/playlist.py
@@ -1,6 +1,5 @@
from fastapi import Request
from fastapi.responses import HTMLResponse
-from folkugat_web.model.pagines import Pages
from folkugat_web.services import playlists as playlists_service
from folkugat_web.services import sessions as sessions_service
from folkugat_web.services.temes import query as query_service
@@ -8,31 +7,35 @@ from folkugat_web.services.temes import search as search_service
from folkugat_web.templates import templates
-def add_set(request: Request, session_id: int, logged_in: bool):
- new_set = playlists_service.add_set(session_id=session_id)
+def add_set(
+ request: Request,
+ playlist_id: int,
+ logged_in: bool,
+):
+ new_set = playlists_service.add_set(playlist_id=playlist_id)
return templates.TemplateResponse(
- "fragments/sessio/set_entry.html",
+ "fragments/playlist/set_entry.html",
{
"request": request,
"logged_in": logged_in,
"new_entry": True,
- "session_id": session_id,
+ "playlist_id": playlist_id,
"set_id": new_set.id,
"set_entry": new_set,
}
)
-def get_set(request: Request, session_id: int, set_id: int, logged_in: bool):
- set_entry = playlists_service.get_set(session_id=session_id, set_id=set_id)
+def get_set(request: Request, playlist_id: int, set_id: int, logged_in: bool):
+ set_entry = playlists_service.get_set(playlist_id=playlist_id, set_id=set_id)
if set_entry:
return templates.TemplateResponse(
- "fragments/sessio/set_entry.html",
+ "fragments/playlist/set_entry.html",
{
"request": request,
"logged_in": logged_in,
"new_entry": True,
- "session_id": session_id,
+ "playlist_id": playlist_id,
"set_id": set_id,
"set_entry": set_entry,
}
@@ -41,59 +44,59 @@ def get_set(request: Request, session_id: int, set_id: int, logged_in: bool):
return HTMLResponse()
-def delete_set(session_id: int, set_id: int):
- playlists_service.delete_set(session_id=session_id, set_id=set_id)
+def delete_set(playlist_id: int, set_id: int):
+ playlists_service.delete_set(playlist_id=playlist_id, set_id=set_id)
return HTMLResponse()
-def add_tema(request: Request, session_id: int, set_id: int, logged_in: bool):
- new_tema = playlists_service.add_tema(session_id=session_id, set_id=set_id)
- playlists_service.add_tema_to_tema_in_set(new_tema)
+def add_tema(request: Request, playlist_id: int, set_id: int, logged_in: bool):
+ new_tema = playlists_service.add_tema(playlist_id=playlist_id, set_id=set_id)
+ new_tema = playlists_service.add_tema_to_tema_in_set(new_tema)
return templates.TemplateResponse(
- "fragments/sessio/tema_editor.html",
+ "fragments/playlist/tema_editor.html",
{
"request": request,
"logged_in": logged_in,
- "session_id": session_id,
+ "playlist_id": playlist_id,
"set_id": set_id,
"tema_entry": new_tema,
}
)
-def get_tema(request: Request, session_id: int, set_id: int, entry_id: int, logged_in: bool):
+def get_tema(request: Request, playlist_id: int, set_id: int, entry_id: int, logged_in: bool):
tema_entry = playlists_service.get_tema(entry_id=entry_id)
- playlists_service.add_tema_to_tema_in_set(tema_entry)
+ tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
return templates.TemplateResponse(
- "fragments/sessio/tema_entry.html",
+ "fragments/playlist/tema_entry.html",
{
"request": request,
"logged_in": logged_in,
- "session_id": session_id,
+ "playlist_id": playlist_id,
"set_id": set_id,
"tema_entry": tema_entry,
}
)
-def get_tema_editor(request: Request, session_id: int, set_id: int, entry_id: int, logged_in: bool):
+def get_tema_editor(request: Request, playlist_id: int, set_id: int, entry_id: int, logged_in: bool):
tema_entry = playlists_service.get_tema(entry_id=entry_id)
- playlists_service.add_tema_to_tema_in_set(tema_entry)
+ tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
return templates.TemplateResponse(
- "fragments/sessio/tema_editor.html",
+ "fragments/playlist/tema_editor.html",
{
"request": request,
"logged_in": logged_in,
- "session_id": session_id,
+ "playlist_id": playlist_id,
"set_id": set_id,
"tema_entry": tema_entry,
}
)
-def delete_tema(session_id: int, set_id: int, entry_id: int):
+def delete_tema(playlist_id: int, set_id: int, entry_id: int):
playlists_service.delete_tema(entry_id=entry_id)
- if not playlists_service.get_set(session_id=session_id, set_id=set_id):
+ if not playlists_service.get_set(playlist_id=playlist_id, set_id=set_id):
headers = {
"HX-Trigger": f"reload-set-{set_id}"
}
@@ -104,7 +107,7 @@ def delete_tema(session_id: int, set_id: int, entry_id: int):
def busca_tema(
request: Request,
- session_id: int,
+ playlist_id: int,
set_id: int,
entry_id: int,
query: str,
@@ -114,7 +117,7 @@ def busca_tema(
if not query:
# If there is no query, suggest tunes commonly played together
set_entry = playlists_service.get_set(
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
)
if set_entry:
@@ -124,7 +127,7 @@ def busca_tema(
commonly_played_tema_ids = {
cpt.tema.id
for tema_id in tema_ids
- for cpt in playlists_service.get_commonly_played_temes(tema_id)
+ for cpt in sessions_service.get_commonly_played_temes(tema_id)
if cpt.tema.id is not None
} - tema_ids
suggestions = query_service.get_temes_by_ids(
@@ -141,10 +144,10 @@ def busca_tema(
offset=0,
)
return templates.TemplateResponse(
- "fragments/sessio/tema_results.html",
+ "fragments/playlist/tema_results.html",
{
"request": request,
- "session_id": session_id,
+ "playlist_id": playlist_id,
"set_id": set_id,
"entry_id": entry_id,
"results": suggestions,
@@ -153,16 +156,16 @@ def busca_tema(
)
-def set_tema(request: Request, logged_in: bool, session_id: int, set_id: int, entry_id: int, tema_id: int | None):
- playlists_service.set_tema(session_id=session_id, set_id=set_id, entry_id=entry_id, tema_id=tema_id)
+def set_tema(request: Request, logged_in: bool, playlist_id: int, set_id: int, entry_id: int, tema_id: int | None):
+ playlists_service.set_tema(playlist_id=playlist_id, set_id=set_id, entry_id=entry_id, tema_id=tema_id)
tema_entry = playlists_service.get_tema(entry_id=entry_id)
- playlists_service.add_tema_to_tema_in_set(tema_entry)
+ tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
return templates.TemplateResponse(
- "fragments/sessio/tema_entry.html",
+ "fragments/playlist/tema_entry.html",
{
"request": request,
"logged_in": logged_in,
- "session_id": session_id,
+ "playlist_id": playlist_id,
"set_id": set_id,
"tema_entry": tema_entry,
}
diff --git a/folkugat_web/fragments/sessio/cartell.py b/folkugat_web/fragments/sessio/cartell.py
index b10c1b6..bd9b329 100644
--- a/folkugat_web/fragments/sessio/cartell.py
+++ b/folkugat_web/fragments/sessio/cartell.py
@@ -1,10 +1,5 @@
from fastapi import Request
-from fastapi.responses import HTMLResponse
-from folkugat_web.model.pagines import Pages
-from folkugat_web.services import playlists as playlists_service
from folkugat_web.services import sessions as sessions_service
-from folkugat_web.services.temes import query as query_service
-from folkugat_web.services.temes import search as search_service
from folkugat_web.templates import templates
diff --git a/folkugat_web/fragments/sessio/notes.py b/folkugat_web/fragments/sessio/notes.py
new file mode 100644
index 0000000..2f1167e
--- /dev/null
+++ b/folkugat_web/fragments/sessio/notes.py
@@ -0,0 +1,30 @@
+from fastapi import Request
+from folkugat_web.services import sessions as sessions_service
+from folkugat_web.templates import templates
+
+
+def notes(request: Request, session_id: int, logged_in: bool):
+ session = sessions_service.get_session(session_id=session_id)
+ return templates.TemplateResponse(
+ "fragments/sessio/notes.html",
+ {
+ "request": request,
+ "logged_in": logged_in,
+ "session_id": session_id,
+ "session": session,
+ "notes": session.notes if session and session.notes else "",
+ }
+ )
+
+
+def notes_editor(request: Request, session_id: int):
+ session = sessions_service.get_session(session_id=session_id)
+ return templates.TemplateResponse(
+ "fragments/sessio/notes_editor.html",
+ {
+ "request": request,
+ "session_id": session_id,
+ "notes": session.notes if session and session.notes else "",
+ "max": max,
+ }
+ )
diff --git a/folkugat_web/fragments/sessio/page.py b/folkugat_web/fragments/sessio/page.py
index 82686e6..0ed7521 100644
--- a/folkugat_web/fragments/sessio/page.py
+++ b/folkugat_web/fragments/sessio/page.py
@@ -9,8 +9,7 @@ def pagina(request: Request, session_id: int, logged_in: bool):
session = sessions_service.get_session(session_id=session_id)
if not session:
raise HTTPException(status_code=404, detail="Could not find session")
- playlist = playlists_service.get_playlist(session_id=session_id)
- playlist = playlists_service.add_temes_to_playlist(playlist)
+ session = sessions_service.add_playlists_to_session(session=session)
return templates.TemplateResponse(
"fragments/sessio/pagina.html",
{
@@ -19,7 +18,6 @@ def pagina(request: Request, session_id: int, logged_in: bool):
"Pages": Pages,
"session_id": session_id,
"session": session,
- "playlist": playlist,
"date_names": sessions_service.get_date_names,
}
)
diff --git a/folkugat_web/fragments/set_page.py b/folkugat_web/fragments/set_page.py
index 5c06629..ad570eb 100644
--- a/folkugat_web/fragments/set_page.py
+++ b/folkugat_web/fragments/set_page.py
@@ -6,22 +6,20 @@ from folkugat_web.services import sessions as sessions_service
from folkugat_web.templates import templates
-async def pagina(request: Request, session_id: int, set_id: int, logged_in: bool):
- session = sessions_service.get_session(session_id=session_id)
- set_ = playlists_service.get_set(session_id=session_id, set_id=set_id)
+async def pagina(request: Request, playlist_id: int, set_id: int, logged_in: bool):
+ set_ = playlists_service.get_set(playlist_id=playlist_id, set_id=set_id)
if not set_:
raise HTTPException(status_code=404, detail="Set not found")
set_ = playlists_service.add_temes_to_set(set_)
set_ = await playlists_service.add_set_score_to_set(set_)
return templates.TemplateResponse(
- "fragments/sessio/set/pagina.html",
+ "fragments/playlist/set/pagina.html",
{
"request": request,
"logged_in": logged_in,
"Pages": Pages,
- "session_id": session_id,
- "session": session,
"set": set_,
+ "session": None,
"date_names": sessions_service.get_date_names,
"LinkType": LinkType,
"ContentType": ContentType,
@@ -33,12 +31,12 @@ async def live(request: Request, logged_in: bool):
session = sessions_service.get_live_session()
set_ = None
if session and session.id:
- playlist = playlists_service.get_playlist(session_id=session.id)
- if playlist.sets:
+ playlist = sessions_service.get_session_setlist(session_id=session.id)
+ if playlist and playlist.sets:
set_ = playlists_service.add_temes_to_set(playlist.sets[-1])
set_ = await playlists_service.add_set_score_to_set(set_)
return templates.TemplateResponse(
- "fragments/sessio/set/pagina.html",
+ "fragments/playlist/set/pagina.html",
{
"request": request,
"logged_in": logged_in,
@@ -56,12 +54,12 @@ async def live_set(request: Request, logged_in: bool):
session = sessions_service.get_live_session()
set_ = None
if session and session.id:
- playlist = playlists_service.get_playlist(session_id=session.id)
- if playlist.sets:
+ playlist = sessions_service.get_session_setlist(session_id=session.id)
+ if playlist and playlist.sets:
set_ = playlists_service.add_temes_to_set(playlist.sets[-1])
set_ = await playlists_service.add_set_score_to_set(set_)
return templates.TemplateResponse(
- "fragments/sessio/set/set_page.html",
+ "fragments/playlist/set/set_page.html",
{
"request": request,
"logged_in": logged_in,
diff --git a/folkugat_web/model/playlists.py b/folkugat_web/model/playlists.py
index 6ef55fd..544965d 100644
--- a/folkugat_web/model/playlists.py
+++ b/folkugat_web/model/playlists.py
@@ -1,4 +1,5 @@
import dataclasses
+import enum
from collections.abc import Iterator
from typing import Self
@@ -6,10 +7,15 @@ from folkugat_web.model.temes import Tema
from folkugat_web.utils import groupby
+class PlaylistType(enum.Enum):
+ SESSION_SETLIST = "session_setlist"
+ SESSION_SLOWJAM = "session_slowjam"
+
+
@dataclasses.dataclass
class PlaylistEntry:
id: int | None
- session_id: int
+ playlist_id: int
set_id: int
tema_id: int | None
@@ -20,17 +26,21 @@ class TemaInSet:
tema_id: int | None
tema: Tema | None
- def to_playlist_entry(self, session_id: int, set_id: int) -> PlaylistEntry:
+ def to_playlist_entry(self, playlist_id: int, set_id: int) -> PlaylistEntry:
return PlaylistEntry(
id=self.id,
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=set_id,
tema_id=self.tema_id,
)
@classmethod
def from_playlist_entry(cls, entry: PlaylistEntry) -> Self:
- return cls(id=entry.id, tema_id=entry.tema_id, tema=None)
+ return cls(
+ id=entry.id,
+ tema_id=entry.tema_id,
+ tema=None,
+ )
@dataclasses.dataclass
@@ -45,10 +55,10 @@ class Set:
temes: list[TemaInSet]
score: SetScore | None
- def to_playlist_entries(self, session_id: int) -> Iterator[PlaylistEntry]:
+ def to_playlist_entries(self, playlist_id: int) -> Iterator[PlaylistEntry]:
for tema_in_set in self.temes:
yield tema_in_set.to_playlist_entry(
- session_id=session_id,
+ playlist_id=playlist_id,
set_id=self.id,
)
@@ -58,26 +68,29 @@ class Set:
raise ValueError("All PlaylistEntries must have the same session_id")
return cls(
id=set_id,
- temes=[TemaInSet.from_playlist_entry(entry) for entry in sorted(entries, key=lambda e: e.id or 0)],
+ temes=[
+ TemaInSet.from_playlist_entry(entry)
+ for entry in sorted(entries, key=lambda e: e.id or 0)
+ ],
score=None,
)
@dataclasses.dataclass
class Playlist:
- session_id: int
+ id: int
sets: list[Set]
def to_playlist_entries(self) -> Iterator[PlaylistEntry]:
for set_entry in self.sets:
- yield from set_entry.to_playlist_entries(session_id=self.session_id)
+ yield from set_entry.to_playlist_entries(playlist_id=self.id)
@classmethod
- def from_playlist_entries(cls, session_id: int, entries: list[PlaylistEntry]) -> Self:
- if any(entry.session_id != session_id for entry in entries):
- raise ValueError("All PlaylistEntries must have the same session_id")
+ def from_playlist_entries(cls, playlist_id: int, entries: list[PlaylistEntry]) -> Self:
+ if any(entry.playlist_id != playlist_id for entry in entries):
+ raise ValueError("All PlaylistEntries must have the same playlist_id")
return cls(
- session_id=session_id,
+ id=playlist_id,
sets=[
Set.from_playlist_entries(set_id, set_entries)
for set_id, set_entries in groupby(entries, key_fn=lambda e: e.set_id, group_fn=list)
diff --git a/folkugat_web/model/sessions.py b/folkugat_web/model/sessions.py
index 525544a..29fdd9a 100644
--- a/folkugat_web/model/sessions.py
+++ b/folkugat_web/model/sessions.py
@@ -2,6 +2,8 @@ import dataclasses
import datetime
import enum
+from folkugat_web.model import playlists
+
DEFAULT_START_TIME = datetime.time(20, 30)
DEFAULT_END_TIME = datetime.time(22, 30)
@@ -21,6 +23,8 @@ class Session:
venue: SessionVenue = dataclasses.field(default_factory=SessionVenue)
notes: str | None = None
cartell_url: str | None = None
+ slowjam: playlists.Playlist | None = None
+ setlist: playlists.Playlist | None = None
is_live: bool = False
diff --git a/folkugat_web/model/temes.py b/folkugat_web/model/temes.py
index c8dcf06..cb46d74 100644
--- a/folkugat_web/model/temes.py
+++ b/folkugat_web/model/temes.py
@@ -4,13 +4,15 @@ import dataclasses
import datetime
import enum
import itertools
-from typing import Self
+from typing import TYPE_CHECKING, Self
from folkugat_web.model.lilypond.processing import RenderError
from folkugat_web.model.search import NGrams
-from folkugat_web.model.sessions import Session
from folkugat_web.services import ngrams
+if TYPE_CHECKING:
+ from folkugat_web.model.sessions import Session
+
class ContentType(enum.Enum):
PARTITURA = "partitura"
diff --git a/folkugat_web/services/playlists.py b/folkugat_web/services/playlists.py
index 7dd8039..0c49d65 100644
--- a/folkugat_web/services/playlists.py
+++ b/folkugat_web/services/playlists.py
@@ -5,7 +5,6 @@ from folkugat_web.dal.sql._connection import get_connection
from folkugat_web.dal.sql.playlists import query, write
from folkugat_web.log import logger
from folkugat_web.model import playlists
-from folkugat_web.model import temes as temes_model
from folkugat_web.model.lilypond import score as lilypond_model
from folkugat_web.services import files as files_service
from folkugat_web.services.lilypond import build as lilypond_build
@@ -41,37 +40,37 @@ def add_tema_to_tema_in_set(tema_in_set: playlists.TemaInSet) -> playlists.TemaI
return tema_in_set
-def get_playlist(session_id: int, con: Connection | None = None) -> playlists.Playlist:
+def get_playlist(playlist_id: int, con: Connection | None = None) -> playlists.Playlist:
return playlists.Playlist.from_playlist_entries(
- session_id=session_id,
- entries=list(query.get_playlist_entries(session_id=session_id, con=con))
+ playlist_id=playlist_id,
+ entries=list(query.get_playlist_entries(playlist_id=playlist_id, con=con))
)
-def add_set(session_id: int, con: Connection | None = None) -> playlists.Set:
+def add_set(playlist_id: int, con: Connection | None = None) -> playlists.Set:
with get_connection(con) as con:
- curr_playlist = get_playlist(session_id=session_id, con=con)
+ curr_playlist = get_playlist(playlist_id=playlist_id, con=con)
new_set_id = max([set_entry.id for set_entry in curr_playlist.sets], default=0) + 1
- new_entry = playlists.PlaylistEntry(id=None, session_id=session_id, set_id=new_set_id, tema_id=None)
+ new_entry = playlists.PlaylistEntry(id=None, playlist_id=playlist_id, set_id=new_set_id, tema_id=None)
inserted_entry = write.insert_playlist_entry(new_entry)
return playlists.Set.from_playlist_entries(set_id=inserted_entry.set_id, entries=[inserted_entry])
-def get_set(session_id: int, set_id: int, con: Connection | None = None) -> playlists.Set | None:
- entries = list(query.get_playlist_entries(session_id=session_id, set_id=set_id, con=con))
+def get_set(playlist_id: int, set_id: int, con: Connection | None = None) -> playlists.Set | None:
+ entries = list(query.get_playlist_entries(playlist_id=playlist_id, set_id=set_id, con=con))
if entries:
return playlists.Set.from_playlist_entries(set_id=set_id, entries=entries)
else:
return None
-def delete_set(session_id: int, set_id: int, con: Connection | None = None):
- write.delete_playlist_set(session_id=session_id, set_id=set_id, con=con)
+def delete_set(playlist_id: int, set_id: int, con: Connection | None = None):
+ write.delete_playlist_set(playlist_id=playlist_id, set_id=set_id, con=con)
-def add_tema(session_id: int, set_id: int, con: Connection | None = None) -> playlists.TemaInSet:
+def add_tema(playlist_id: int, set_id: int, con: Connection | None = None) -> playlists.TemaInSet:
with get_connection(con) as con:
- new_entry = playlists.PlaylistEntry(id=None, session_id=session_id, set_id=set_id, tema_id=None)
+ new_entry = playlists.PlaylistEntry(id=None, playlist_id=playlist_id, set_id=set_id, tema_id=None)
inserted_entry = write.insert_playlist_entry(new_entry)
return playlists.TemaInSet.from_playlist_entry(inserted_entry)
@@ -87,10 +86,10 @@ def delete_tema(entry_id: int, con: Connection | None = None):
write.delete_playlist_entry(entry_id=entry_id, con=con)
-def set_tema(session_id: int, set_id: int, entry_id: int, tema_id: int | None,
+def set_tema(playlist_id: int, set_id: int, entry_id: int, tema_id: int | None,
con: Connection | None = None):
with get_connection(con) as con:
- new_entry = playlists.PlaylistEntry(id=entry_id, session_id=session_id, set_id=set_id, tema_id=tema_id)
+ new_entry = playlists.PlaylistEntry(id=entry_id, playlist_id=playlist_id, set_id=set_id, tema_id=tema_id)
write.update_playlist_entry(entry=new_entry, con=con)
@@ -163,9 +162,3 @@ async def add_set_score_to_set(tune_set: playlists.Set) -> playlists.Set:
)
else:
return tune_set
-
-
-def get_commonly_played_temes(
- tema_id: int,
-) -> list[temes_model.CommonlyPlayedTema]:
- return query.get_commonly_played_tunes(tema_id=tema_id)
diff --git a/folkugat_web/services/sessions.py b/folkugat_web/services/sessions.py
index 0a99a61..8c60ada 100644
--- a/folkugat_web/services/sessions.py
+++ b/folkugat_web/services/sessions.py
@@ -1,10 +1,16 @@
+import dataclasses
import datetime
from datetime import date as Date
from folkugat_web.config import date as config
+from folkugat_web.dal.sql import Connection, get_connection
+from folkugat_web.dal.sql.sessions import playlists as session_playlists
from folkugat_web.dal.sql.sessions import query, write
from folkugat_web.model import sessions as model
+from folkugat_web.model import temes as temes_model
+from folkugat_web.model.playlists import Playlist, PlaylistType
from folkugat_web.model.sql import Order, OrderCol, Range
+from folkugat_web.services import playlists as playlists_service
def get_date_names(date: Date) -> model.DateNames:
@@ -77,3 +83,80 @@ def stop_live_sessions():
def set_live_session(session_id: int):
write.set_live_session(session_id=session_id)
+
+
+def get_session_playlist_id(
+ session_id: int,
+ playlist_type: PlaylistType,
+ con: Connection | None = None,
+) -> int | None:
+ with get_connection(con=con) as con:
+ return session_playlists.get_playlist_id(
+ session_id=session_id,
+ playlist_type=playlist_type,
+ con=con,
+ )
+
+
+def get_session_playlist(
+ session_id: int,
+ playlist_type: PlaylistType,
+ con: Connection | None = None,
+) -> Playlist | None:
+ with get_connection(con=con) as con:
+ playlist_id = get_session_playlist_id(
+ session_id=session_id,
+ playlist_type=playlist_type,
+ con=con,
+ )
+ if playlist_id is None:
+ return None
+ return playlists_service.get_playlist(
+ playlist_id=playlist_id,
+ con=con,
+ )
+
+
+def get_session_setlist(
+ session_id: int,
+ con: Connection | None = None,
+) -> Playlist | None:
+ return get_session_playlist(
+ session_id=session_id,
+ playlist_type=PlaylistType.SESSION_SETLIST,
+ con=con,
+ )
+
+
+def get_session_slowjam(
+ session_id: int,
+ con: Connection | None = None,
+) -> Playlist | None:
+ return get_session_playlist(
+ session_id=session_id,
+ playlist_type=PlaylistType.SESSION_SLOWJAM,
+ con=con,
+ )
+
+
+def add_playlists_to_session(session: model.Session) -> model.Session:
+ if session.id is not None:
+ with get_connection() as con:
+ setlist = get_session_setlist(session_id=session.id, con=con)
+ if setlist:
+ setlist = playlists_service.add_temes_to_playlist(setlist)
+ slowjam = get_session_slowjam(session_id=session.id, con=con)
+ if slowjam:
+ slowjam = playlists_service.add_temes_to_playlist(slowjam)
+ session = dataclasses.replace(
+ session,
+ setlist=setlist,
+ slowjam=slowjam,
+ )
+ return session
+
+
+def get_commonly_played_temes(
+ tema_id: int,
+) -> list[temes_model.CommonlyPlayedTema]:
+ return session_playlists.get_commonly_played_tunes(tema_id=tema_id)
diff --git a/folkugat_web/services/temes/query.py b/folkugat_web/services/temes/query.py
index 6a338e9..d8028d5 100644
--- a/folkugat_web/services/temes/query.py
+++ b/folkugat_web/services/temes/query.py
@@ -1,6 +1,6 @@
from collections.abc import Iterable
-from folkugat_web.dal.sql.playlists import query as playlists_q
+from folkugat_web.dal.sql.sessions import playlists as session_playlists
from folkugat_web.dal.sql.temes import query as temes_q
from folkugat_web.model import sessions as sessions_model
from folkugat_web.model import temes as model
@@ -20,7 +20,7 @@ def tema_compute_stats(
) -> model.Tema:
if tema.id:
if tune_sessions_dict is None:
- tune_sessions_dict = playlists_q.get_tune_sessions(tema_ids=[tema.id])
+ tune_sessions_dict = session_playlists.get_tune_sessions(tema_ids=[tema.id])
if tema.id and (tune_sessions := tune_sessions_dict.get(tema.id)):
unique_tune_sessions = set(tune_sessions)
tema.stats = model.Stats(
@@ -33,7 +33,7 @@ def tema_compute_stats(
def temes_compute_stats(temes: Iterable[model.Tema]) -> list[model.Tema]:
temes = list(temes)
tema_ids = [tema.id for tema in temes if tema.id is not None]
- tune_sessions_dict = playlists_q.get_tune_sessions(tema_ids=tema_ids)
+ tune_sessions_dict = session_playlists.get_tune_sessions(tema_ids=tema_ids)
return [tema_compute_stats(tema=tema, tune_sessions_dict=tune_sessions_dict) for tema in temes]
@@ -41,5 +41,5 @@ def tema_compute_played_with(
tema: model.Tema,
) -> model.Tema:
if tema.id:
- tema.played_with = playlists_q.get_commonly_played_tunes(tema_id=tema.id)
+ tema.played_with = session_playlists.get_commonly_played_tunes(tema_id=tema.id)
return tema
diff --git a/scripts/05_migrate_playlists.py b/scripts/05_migrate_playlists.py
new file mode 100644
index 0000000..a6345ae
--- /dev/null
+++ b/scripts/05_migrate_playlists.py
@@ -0,0 +1,57 @@
+from folkugat_web.dal.sql import get_connection
+from folkugat_web.dal.sql.playlists import write as playlists_w
+from folkugat_web.dal.sql.playlists.ddl import (create_playlist_entries_table,
+ create_playlists_table)
+from folkugat_web.dal.sql.sessions import playlists as session_playlists
+from folkugat_web.dal.sql.sessions import query as sessions_q
+from folkugat_web.dal.sql.sessions.ddl import create_session_playlists_table
+from folkugat_web.model.playlists import PlaylistType
+
+with get_connection() as con:
+ cur = con.cursor()
+
+ drop_query = """DROP TABLE rehearse_data"""
+ _ = cur.execute(drop_query)
+
+ alter_query = """ALTER TABLE playlists RENAME TO playlists_old"""
+ _ = cur.execute(alter_query)
+
+ # Create new tables
+ create_playlist_entries_table(con=con)
+ create_playlists_table(con=con)
+ create_session_playlists_table(con=con)
+
+ # Create new playlists
+ sessions = sessions_q.get_sessions(con=con)
+ for session in sessions:
+ if session.id is None:
+ raise ValueError("Session without id!")
+
+ setlist_id = playlists_w.create_playlist(con=con)
+ session_playlists.insert_playlist(
+ session_id=session.id,
+ playlist_type=PlaylistType.SESSION_SETLIST,
+ playlist_id=setlist_id,
+ con=con,
+ )
+
+ slowjam_id = playlists_w.create_playlist(con=con)
+ session_playlists.insert_playlist(
+ session_id=session.id,
+ playlist_type=PlaylistType.SESSION_SLOWJAM,
+ playlist_id=slowjam_id,
+ con=con,
+ )
+
+ # Migrate
+ migrate_query = """
+ INSERT INTO playlist_entries
+ (id, playlist_id, set_id, tema_id)
+ SELECT
+ NULL, sp.playlist_id, pl_old.set_id, pl_old.tema_id
+ FROM playlists_old pl_old JOIN session_playlists sp USING (session_id)
+ WHERE sp.playlist_type = "session_setlist"
+ """
+ _ = cur.execute(migrate_query)
+
+print("DONE!")