This commit is contained in:
2024-05-17 12:34:45 +02:00
parent c1c9f98e87
commit 536fd65203
9 changed files with 200 additions and 127 deletions

View File

@@ -1,11 +1,6 @@
import sqlalchemy from sqlalchemy import create_engine
from sqlalchemy.ext.automap import automap_base from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import Session
Base = automap_base() engine = create_engine('sqlite:///db.sqlite')
Session = sessionmaker(engine)
dbEngine = sqlalchemy.create_engine('sqlite:///db.sqlite') session = Session()
dbSession = Session(dbEngine)
Base.prepare(dbEngine, reflect=True)

View File

@@ -3,6 +3,10 @@ from app.routers import admin, user, songs
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from app.dependencies import engine
from app.sql_models import Base
Base.metadata.create_all(engine)
app = FastAPI() app = FastAPI()
@@ -10,7 +14,6 @@ app.include_router(admin.router)
app.include_router(user.router) app.include_router(user.router)
app.include_router(songs.router) app.include_router(songs.router)
app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@@ -24,7 +27,4 @@ async def root(request: Request, session_id : str = ""):
else: else:
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="voting.html" request=request, name="voting.html"
) )
# 1PMy17eraogNUz436w3aZKxyij39G1didaN02Ka_-45Q
# 71046222

View File

@@ -1,6 +1,7 @@
from typing import Optional, List from typing import Optional, List
import gspread import gspread
from pydantic import BaseModel from pydantic import BaseModel
from enum import Enum
class GoogleFile(BaseModel): class GoogleFile(BaseModel):
file_id: str file_id: str
@@ -11,7 +12,14 @@ class Genre(BaseModel):
genre_name: str genre_name: str
class Song(BaseModel): class Song(BaseModel):
song_id: Optional[int] id: int
song_title: str og_artist: Optional[str]
url: str aca_artist: Optional[str]
genres: List[Genre] title: str
yt_url: Optional[str]
is_aca: bool
arng_url: Optional[str]
categories: dict[str, bool]
main_category: str
singable: bool
vote: Optional[int]

View File

@@ -1,9 +1,7 @@
from fastapi import APIRouter, HTTPException, Security, File, UploadFile from fastapi import APIRouter, Security
from app.models import GoogleFile from app.sql_models import SqlSong
from app.dependencies import dbEngine from app.dependencies import session
from app.routers.user import get_current_user from app.routers.user import get_current_user
import gspread
from gspread.urls import DRIVE_FILES_API_V3_URL
import pandas as pd import pandas as pd
import numpy as np import numpy as np
@@ -13,87 +11,38 @@ router = APIRouter(
responses={404: {"description": "Not found"}}, responses={404: {"description": "Not found"}},
) )
gc = gspread.service_account(filename="service_account.json") # type: ignore
google_files: dict[str, GoogleFile] = {} def get_main_category(categories) -> int:
if np.sum(categories != None) == 1:
spreadsheets: dict[str, gspread.Spreadsheet] = {} # type: ignore return np.argmax(categories != None, axis=0)
elif "h" in categories:
selected_worksheets: dict[tuple[str, int], bool] = {} return np.argmax(categories == "h", axis=0)
else:
return np.argmax(categories != None, axis=0)
def fetch_files():
if not (google_files):
for s in gc.http_client.request("get", DRIVE_FILES_API_V3_URL).json()["files"]: #
google_files[s["id"]] = GoogleFile(
file_id=s["id"], file_name=s["name"])
def load_spreadsheet(file_id):
if file_id not in spreadsheets:
spreadsheets[file_id] = gc.open_by_key(file_id)
# Route to get google files (sheets)
@router.get("/files")
async def get_files() -> dict[str, GoogleFile]:
fetch_files()
return google_files
@router.get("/files/{file_id}")
def get_worksheets(file_id: str) -> dict[int, str]:
fetch_files()
if file_id not in google_files:
raise HTTPException(status_code=404, detail="Spreadsheet not found.")
load_spreadsheet(file_id)
# type: ignore
return {ws.id: ws.title for ws in spreadsheets[file_id].worksheets()}
@router.get("/selected_worksheets")
def list_selected_worksheet() -> dict[tuple[str, int], bool]:
return selected_worksheets
@router.post("/selected_worksheets")
def select_worksheet(file_id: str, worksheet_id: int, ignore_arrangement: bool = False):
selected_worksheets[(file_id, worksheet_id)] = ignore_arrangement
@router.delete("/selected_worksheets")
def deselect_worksheet(file_id: str, worksheet_id: int):
del selected_worksheets[(file_id, worksheet_id)]
@router.post("/process_worksheets")
def process_worksheets():
fetch_files()
song_list = []
for (file_id, worksheet_id), ignore_arrangement in selected_worksheets.items():
load_spreadsheet(file_id)
worksheet = spreadsheets[file_id].get_worksheet_by_id(worksheet_id)
worksheet_df = pd.DataFrame(worksheet.get_all_records())
last_column = np.where(worksheet_df.columns == "Kommentare")[0][0]
worksheet_df = worksheet_df.iloc[:, 0:last_column+1]
worksheet_df["Ignore Arrangement"] = ignore_arrangement
song_list.append(worksheet_df)
song_list = pd.concat(song_list)
song_list.to_sql(name='songs', con=dbEngine,
index=False, if_exists='append')
# song_list.to_csv("song-list.csv")
@router.post("/process_file") @router.post("/process_file")
async def create_upload_file(file: UploadFile): async def create_upload_file(link_share: str):
return {"filename": file.filename} song_list = pd.read_excel(link_share)
song_list = song_list.replace({np.nan: None})
song_list = song_list.replace({"n/a": None})
category_names = list(song_list.iloc[0][6:19])
for row in song_list[1:].iterrows():
row = np.array(row[1])
s = SqlSong(og_artist=row[0],
aca_artist=row[1],
title=row[2],
yt_url=row[3],
is_aca=row[4] == "ja",
arng_url=row[5],
categories={n: v for n, v in zip(
category_names, row[6:19] != None)},
main_category=category_names[get_main_category(row[6:19])],
singable=row[19] != "nein"
)
session.add(s)
session.commit()

View File

@@ -1,6 +1,7 @@
from fastapi import APIRouter, HTTPException, Security from fastapi import APIRouter, HTTPException, Security
from app.models import Song from app.models import Song
from app.dependencies import dbEngine, Base, dbSession from app.sql_models import SqlSong, SqlVote
from app.dependencies import session
from app.routers.user import get_current_user, User from app.routers.user import get_current_user, User
from typing import Annotated from typing import Annotated
@@ -10,7 +11,20 @@ router = APIRouter(
responses={404: {"description": "Not found"}}, responses={404: {"description": "Not found"}},
) )
@router.get("/") @router.get("/")
async def get_songs() -> list[dict]: async def get_songs(user_id : str = "") -> list[Song]:
return dbSession.query(Base.songs).all() sqlsongs = session.query(SqlSong).filter(SqlSong.singable == True).all()
votes = session.query(SqlVote).filter(SqlVote.user_id == user_id).all()
votes = {v.song_id : v.vote for v in votes}
return [Song(**s.__dict__, vote=votes.get(s.id, None)) for s in sqlsongs] # type: ignore
@router.post("/{song_id}/vote")
async def vote(song_id : str, user_id : str, vote : int):
vote_entry = session.query(SqlVote).filter((SqlVote.user_id == user_id) & (SqlVote.song_id == song_id)).first()
if vote_entry:
vote_entry.vote = str(vote) # type: ignore
else:
vote_entry = SqlVote(song_id=song_id, user_id=user_id, vote=vote)
session.add(vote_entry)
session.commit()

28
app/sql_models.py Normal file
View File

@@ -0,0 +1,28 @@
from sqlalchemy import Column, String, Integer, Boolean, PickleType, Enum
from sqlalchemy.orm import declarative_base
from typing import Optional
Base = declarative_base()
class SqlSong(Base):
__tablename__ = 'songs'
id = Column(Integer, primary_key=True)
og_artist = Column(String)
aca_artist = Column(String)
title = Column(String)
yt_url = Column(String)
is_aca = Column(Boolean)
arng_url = Column(String)
categories = Column(PickleType)
main_category = Column(String)
singable = Column(Boolean)
class SqlVote(Base):
__tablename__ = 'votes'
id = Column(Integer, primary_key=True)
song_id = Column(Integer)
user_id = Column(String)
vote = Column(Integer, nullable=True)

View File

@@ -1,10 +1,10 @@
fastapi fastapi
uvicorn uvicorn
gspread
pandas pandas
numpy numpy
sqlalchemy sqlalchemy
python-jose[cryptography] python-jose[cryptography]
passlib[bcrypt] passlib[bcrypt]
python-multipart python-multipart
jinja2 jinja2
openpyxl

View File

@@ -1,4 +1,4 @@
document.addEventListener('DOMContentLoaded', function () { function makeScroll() {
const eles = document.getElementsByClassName('categories'); const eles = document.getElementsByClassName('categories');
Array.prototype.forEach.call(eles, ele => { Array.prototype.forEach.call(eles, ele => {
@@ -44,27 +44,88 @@ document.addEventListener('DOMContentLoaded', function () {
// Attach the handler // Attach the handler
ele.addEventListener('mousedown', mouseDownHandler); ele.addEventListener('mousedown', mouseDownHandler);
}); });
}); }
function vote(song_id, vote) {
var session_id = getQueryParameter("session_id");
no_button = $("#song-" + song_id).find(".button-no")
yes_button = $("#song-" + song_id).find(".button-yes")
neutral_button = $("#song-" + song_id).find(".button-neutral")
no_button.removeClass("selected")
yes_button.removeClass("selected")
neutral_button.removeClass("selected")
switch (vote) {
case 0:
neutral_button.addClass("selected")
break;
case 1:
yes_button.addClass("selected")
break;
case -1:
no_button.addClass("selected")
default:
break;
}
$.ajax({
url: "/songs/" + song_id + "/vote?" + $.param({ user_id: session_id, vote: vote }),
method: "POST"
})
}
const getQueryParameter = (param) => new URLSearchParams(document.location.search.substring(1)).get(param); const getQueryParameter = (param) => new URLSearchParams(document.location.search.substring(1)).get(param);
$(document).ready(function () { $(document).ready(function () {
var session_id = getQueryParameter("session_id"); var session_id = getQueryParameter("session_id");
$(".greeting-id").append(session_id); var songTemplate = $('script[data-template="song"]').text().split(/\$\{(.+?)\}/g);
$(".greeting-id").append("Foo");
function render(props) {
return function(tok, i) { return (i % 2) ? props[tok] : tok; };
}
song_list = {}
$.ajax({ $.ajax({
url: "/songs" url: "/songs",
}).then(function (user_list) { data: { user_id: session_id }
$('.greeting-id').append(user_list.total); }).then(function (songs) {
localStorage.setItem("test-storage", user_list.total); $.each(songs, function (key, song) {
var users = [];
$.each(user_list.data, function (key, user) { var mc = song.main_category;
users.push("<li id='" + user.id + "'>" + user.first_name + "</li>");
if (!(mc in song_list)) {
song_list[mc] = ""
}
var cats = ""
var cat_id = 1
$.each(song.categories, function (cat_name, is_cat) {
if (is_cat) {
cats = cats + '<span class="cat-' + cat_id + '">' + cat_name + '</span>';
}
cat_id += 1
});
var s = songTemplate.map(render({
"id" : song.id,
"title" : song.og_artist + ": " + song.title,
"cover_image" : "cover.jpg",
"no_selected" : (song.vote == -1) ? "selected" : "",
"neutral_selected" : (song.vote == 0) ? "selected" : "",
"yes_selected" : (song.vote == 1) ? "selected" : "",
"categories" : cats
})).join('')
song_list[mc] += s
});
$.each(song_list, function(mc, s) {
$('body').append("<h1>" + mc + "</h1>");
$('body').append(s);
}); });
$("<ul/>", {
"class": "my-new-list",
html: users.join("")
}).appendTo("body");
}); });
}); });

View File

@@ -4,11 +4,29 @@
<title>Liederwahl</title> <title>Liederwahl</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', path='/colors.css') }}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', path='/colors.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', path='/site.css') }}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', path='/site.css') }}">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="{{ url_for('static', path='/voting_script.js') }}"></script> <script src="{{ url_for('static', path='/voting_script.js') }}"></script>
<script type="text/template" data-template="song">
<div class="song" id="song-${id}">
<div class="cover-container">
<img src="{{ url_for('static', path='/') }}${cover_image}" class="cover">
<div class="overlay"><img src="{{ url_for('static', path='/play.svg') }}"></div>
</div>
<div class="song-data">${title}</div>
<div class="categories" id="container">
${categories}
</div>
<div class="vote-buttons">
<div class="button button-no ${no_selected}" onmousedown="vote(${id}, -1)"><img src="{{ url_for('static', path='/no.svg') }}"></div><div class="button button-neutral ${neutral_selected}" onmousedown="vote(${id}, 0)"><img src="{{ url_for('static', path='/neutral.svg') }}"></div><div class="button button-yes ${yes_selected}" onmousedown="vote(${id}, 1)"><img src="{{ url_for('static', path='/yes.svg') }}"></div>
</div>
<div class="clear"></div>
</div>
</script>
</head> </head>
<body> <body>
<div class="song"> <!--<div class="song">
<div class="cover-container"> <div class="cover-container">
<img src="{{ url_for('static', path='/cover.jpg') }}" class="cover"> <img src="{{ url_for('static', path='/cover.jpg') }}" class="cover">
<div class="overlay"><img src="{{ url_for('static', path='/play.svg') }}"></div> <div class="overlay"><img src="{{ url_for('static', path='/play.svg') }}"></div>
@@ -21,7 +39,7 @@
<div class="button button-no"><img src="{{ url_for('static', path='/no.svg') }}"></div><div class="button button-neutral"><img src="{{ url_for('static', path='/neutral.svg') }}"></div><div class="button button-yes selected"><img src="{{ url_for('static', path='/yes.svg') }}"></div> <div class="button button-no"><img src="{{ url_for('static', path='/no.svg') }}"></div><div class="button button-neutral"><img src="{{ url_for('static', path='/neutral.svg') }}"></div><div class="button button-yes selected"><img src="{{ url_for('static', path='/yes.svg') }}"></div>
</div> </div>
<div class="clear"></div> <div class="clear"></div>
</div> </div>-->
</body> </body>
</html> </html>