Implement Veto Mode

This commit is contained in:
matthias@matsewe.de
2024-05-29 10:33:42 +02:00
parent 010d6fc8d6
commit 0546a88e32
9 changed files with 150 additions and 24 deletions

View File

@@ -10,9 +10,7 @@ def get_songs_and_vote_for_session(db, session_name) -> list[models.Song]:
models.Vote.session_id == session_entry.id).subquery()
songs_and_votes = db.query(
models.Song, votes.c.vote
).filter(
models.Song.singable == True
models.Song, votes.c.vote, votes.c.comment
).join(votes, isouter=True).filter().all()
return songs_and_votes
@@ -46,7 +44,8 @@ def create_song(db,
arng_url,
categories,
main_category,
singable
singable,
comment
):
s = models.Song(og_artist=og_artist,
aca_artist=aca_artist,
@@ -61,7 +60,8 @@ def create_song(db,
arng_url=arng_url,
categories=categories,
main_category=main_category,
singable=singable)
singable=singable,
comment=comment)
db.add(s)
db.commit()
@@ -80,6 +80,21 @@ def create_or_update_vote(db, song_id, session_name, vote):
db.add(vote_entry)
db.commit()
def create_or_update_comment(db, song_id, session_name, comment):
session_entry = activate_session(db, session_name)
if comment == "":
comment = None
vote_entry = db.query(models.Vote).filter(
(models.Vote.session_id == session_entry.id) & (models.Vote.song_id == song_id)).first()
if vote_entry:
vote_entry.comment = comment # type: ignore
else:
vote_entry = models.Vote(
song_id=song_id, session_id=session_entry.id, comment=comment)
db.add(vote_entry)
db.commit()
def activate_session(db, session_name):
session_entry = db.query(models.Session).filter(
@@ -104,3 +119,20 @@ def deactivate_session(db, session_name):
session_entry = models.Session(session_name=session_name, active=False)
db.add(session_entry)
db.commit()
def get_setting(db, key):
entry = db.query(models.Config.value).filter(models.Config.key == key).first()
if entry:
return entry[0]
else:
return None
def set_setting(db, key, value):
setting_entry = db.query(models.Config).filter(models.Config.key == key).first()
if setting_entry:
setting_entry.value = value
else:
setting_entry = models.Config(key=key, value=value)
db.add(setting_entry)
db.commit()

View File

@@ -21,5 +21,6 @@ async def get_db():
class Base(DeclarativeBase):
type_annotation_map = {
dict[str, bool]: PickleType
dict[str, bool]: PickleType,
object: PickleType
}

View File

@@ -4,10 +4,11 @@ from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from app.database import engine, Base, get_db
from app.crud import get_songs_and_vote_for_session
from app.crud import get_songs_and_vote_for_session, get_setting
from sqlalchemy.orm import Session
from typing import Annotated
from app.schemas import Song
import json
Base.metadata.create_all(engine)
@@ -31,17 +32,23 @@ async def root(request: Request) -> HTMLResponse:
@app.get("/vote")
async def vote(request: Request, session_id: str, db: Annotated[Session, Depends(get_db)]) -> HTMLResponse:
songs = [Song(**s.__dict__, vote=v)
for s, v in get_songs_and_vote_for_session(db, session_id)]
async def vote(request: Request, session_id: str, db: Session = Depends(get_db)) -> HTMLResponse:
veto_mode = get_setting(db, "veto_mode")
songs = [Song(**s.__dict__, vote=v, vote_comment=c)
for s, v, c in get_songs_and_vote_for_session(db, session_id)]
songs_by_category = {}
all_categories = set()
wildcard_songs = []
current_songs = []
other_songs = []
for song in songs:
if (not song.singable) and (not veto_mode):
continue
if song.is_current:
current_songs.append(song)
continue
@@ -50,26 +57,35 @@ async def vote(request: Request, session_id: str, db: Annotated[Session, Depends
wildcard_songs.append(song)
continue
if not song.main_category:
other_songs.append(song)
continue
if song.main_category not in songs_by_category:
songs_by_category[song.main_category] = []
songs_by_category[song.main_category].append(song)
all_categories.update(song.categories.keys())
songs_by_category["Sonstige"] = other_songs
songs_by_category["Wildcard (nicht a cappella)"] = wildcard_songs
songs_by_category["Aktuelles Programm"] = current_songs
all_categories = list(all_categories)
all_categories.sort()
all_categories.append("Sonstige")
all_categories.append("Wildcard (nicht a cappella)")
all_categories.append("Aktuelles Programm")
print(all_categories)
# print(all_categories)
# with open('/data/songs_by_cat.json', 'w') as f:
# json.dump({cat : [s.__dict__ for s in songs] for cat, songs in songs_by_category.items()}, f)
return templates.TemplateResponse(
request=request, name="voting.html", context={
"songs_by_category": songs_by_category,
"all_categories": {c: i+1 for i, c in enumerate(all_categories)},
"session_id": session_id
"session_id": session_id,
"veto_mode": veto_mode
}
)

View File

@@ -24,6 +24,7 @@ class Song(Base):
categories: Mapped[Optional[dict[str, bool]]]
main_category: Mapped[Optional[str]]
singable: Mapped[Optional[bool]]
comment: Mapped[Optional[str]]
class Session(Base):
@@ -42,6 +43,13 @@ class Vote(Base):
song_id: Mapped[int] = mapped_column(Integer, ForeignKey("songs.id"))
session_id: Mapped[int] = mapped_column(Integer, ForeignKey("sessions.id"))
vote: Mapped[Optional[int]]
comment: Mapped[Optional[str]]
time_created: Mapped[datetime] = mapped_column(server_default=func.now())
time_updated: Mapped[Optional[datetime]
] = mapped_column(onupdate=func.now())
class Config(Base):
__tablename__ = 'config'
#id: Mapped[int] = mapped_column(primary_key=True)
key: Mapped[str] = mapped_column(primary_key=True)
value: Mapped[object]

View File

@@ -8,7 +8,7 @@ from sqlalchemy.orm import Session
from app.database import get_db, engine, Base
from app.routers.user import get_current_user
from app.crud import create_song
from app.crud import create_song, get_setting, set_setting
router = APIRouter(
prefix="/admin",
@@ -66,7 +66,7 @@ def get_spotify_id(url):
@router.post("/load_list")
async def create_upload_file(db: Session = Depends(get_db)):
async def create_upload_file(include_non_singable: bool = False, db: Session = Depends(get_db)):
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
@@ -78,16 +78,24 @@ async def create_upload_file(db: Session = Depends(get_db)):
category_names = list(song_list.iloc[0][8:17])
for i, row in song_list[1:].iterrows():
if (row[17] == "nein") and not include_non_singable:
continue
row = np.array(row)
if not row[2]: # no title
continue
yt_id = get_youtube_id(row[3])
spfy_id = get_spotify_id(row[3])
categories = {n: v for n, v in zip(
category_names, row[8:17] != None)}
if (not np.any(list(categories.values()))) and (row[5] != "ja"):
continue
if (not np.any(list(categories.values()))):
main_category = None
else:
main_category = category_names[get_main_category(row[8:17])]
create_song(db,
og_artist=row[0],
@@ -102,6 +110,18 @@ async def create_upload_file(db: Session = Depends(get_db)):
is_aca=row[6] == "ja",
arng_url=row[7],
categories=categories,
main_category=category_names[get_main_category(row[8:17])],
singable=row[17] != "nein"
main_category=main_category,
singable=row[17] != "nein",
comment=row[18]
)
@router.post("/toggle_veto_mode")
async def toggle_veto_mode(db: Session = Depends(get_db)) -> bool:
veto_setting = get_setting(db, "veto_mode")
if veto_setting:
set_setting(db, "veto_mode", False)
return False
else:
set_setting(db, "veto_mode", True)
return True

View File

@@ -5,7 +5,7 @@ from sqlalchemy.orm import Session
import app.models as models
from app.database import get_db
from app.schemas import Song
from app.crud import get_songs_and_vote_for_session, create_or_update_vote, get_all_songs_and_votes
from app.crud import get_songs_and_vote_for_session, create_or_update_vote, get_all_songs_and_votes, create_or_update_comment
router = APIRouter(
prefix="/songs",
@@ -16,13 +16,17 @@ router = APIRouter(
@router.get("/")
async def get_songs(session_id: str = "", db: Annotated[Session, Depends(get_db)] = None) -> list[Song]:
return [Song(**s.__dict__, vote=v) for s, v in get_songs_and_vote_for_session(db, session_id)]
return [Song(**s.__dict__, vote=v, vote_comment=c) for s, v, c in get_songs_and_vote_for_session(db, session_id)]
@router.post("/{song_id}/vote")
async def vote(song_id: str, session_id: str, vote: int, db: Annotated[Session, Depends(get_db)]):
create_or_update_vote(db, song_id, session_id, vote)
@router.post("/{song_id}/comment")
async def comment(song_id: str, session_id: str, comment: str, db: Annotated[Session, Depends(get_db)]):
create_or_update_comment(db, song_id, session_id, comment)
#create_or_update_vote(db, song_id, session_id, vote)
@router.get("/evaluation")
async def get_evaluation(db: Annotated[Session, Depends(get_db)] = None) -> dict[int, dict[int, int]]:

View File

@@ -17,4 +17,6 @@ class Song(BaseModel):
categories: Optional[dict[str, bool]]
main_category: Optional[str]
singable: Optional[bool]
comment: Optional[str]
vote: Optional[int]
vote_comment: Optional[str]

View File

@@ -224,8 +224,6 @@
font-size: 0.7em;
}
h1 {
font-family: sans-serif;
padding: 0.1em;

View File

@@ -9,6 +9,19 @@
<link rel="shortcut icon" href="https://choriosity.de/assets/images/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" type="text/css" href="/static/site.css">
{% if veto_mode %}
<style type="text/css">
.comment {
width: 100%;
margin-top: 0.3em;
font-size: 1.2em;
}
.not_singable {
background-color: color-mix(in srgb, #e1412f 30%, #f0f0f0);
}
</style>
{% endif %}
<script src="https://open.spotify.com/embed/iframe-api/v1" async></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
@@ -119,12 +132,22 @@
switch (vote) {
case 0:
neutral_button.addClass("selected")
{% if veto_mode %}
$("#song-" + song_id).removeClass("not_singable")
{% endif %}
break;
case 1:
yes_button.addClass("selected")
{% if veto_mode %}
$("#song-" + song_id).removeClass("not_singable")
{% endif %}
break;
case -1:
no_button.addClass("selected")
{% if veto_mode %}
$("#song-" + song_id).addClass("not_singable")
{% endif %}
break;
default:
break;
}
@@ -134,18 +157,35 @@
method: "POST"
})
}
{% if veto_mode %}
function updateComment(song_id, el) {
comment = el.value
$.ajax({
url: "/songs/" + song_id + "/comment?" + $.param({ session_id: session_id, comment: comment }),
method: "POST"
})
}
{% endif %}
</script>
</head>
<body>
{% if veto_mode %}
<h1>Veto Mode</h1>
<div class="text">Du kannst ungeeignete Vorschläge durch eine Nein-Stimme markieren und Kommentare zu allen Liedern abgeben.
</div>
{% else %}
<h1>Hallo :)</h1>
<div class="text">Du kannst die Liederwahl jederzeit unterbrechen und zu einem späteren Zeitpunkt weitermachen.
</div>
{% endif %}
<div id="songs">
{% for main_category, songs in songs_by_category.items() %}
<h1 style="--hue: {{ all_categories[main_category] / all_categories|length }};">{{ main_category }}</h1>
{% for song in songs -%}
<div class="song" id="song-{{ song.id }}">
<div class="song{% if (song.vote == -1) or (not song.vote and not song.singable) %} not_singable{% endif %}"
id="song-{{ song.id }}">
<div class="cover-container">
<img src="{{ song.thumbnail }}" class="cover">
<div class="overlay"
@@ -163,7 +203,7 @@
endif %}{% endfor %}<span style="--main-color: transparent;">&nbsp;</span>
</div>
<div class="vote-buttons">
<div class="button button-no {% if song.vote == -1 %}selected{% endif %}"
<div class="button button-no {% if (song.vote == -1) or (not song.vote and not song.singable) %}selected{% endif %}"
onmousedown="vote({{ song.id }}, -1); return false;" onclick="return false;"><img
src="/static/no.svg">
</div>
@@ -176,6 +216,11 @@
</div>
</div>
<div class="clear"></div>
{% if veto_mode %}
<input type="text" class="comment"
value="{% if song.vote_comment %}{{ song.vote_comment }}{% else %}{% if song.comment %}{{ song.comment }}{% else %}{% endif %}{% endif %}"
placeholder="Kommentar" onchange="updateComment({{ song.id }}, this);">
{% endif %}
</div>
{% endfor %}
{% endfor %}