mirror of
https://github.com/PhoenixTwoFive/karaoqueue.git
synced 2025-05-21 12:01:49 +02:00
Add list of played songs
This commit is contained in:
parent
438bcffd0b
commit
5f551ac092
3
.gitignore
vendored
3
.gitignore
vendored
@ -130,5 +130,4 @@ dmypy.json
|
|||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
# Test data
|
# Test data
|
||||||
test.db
|
data/
|
||||||
config.json
|
|
||||||
|
@ -7,6 +7,7 @@ from io import StringIO
|
|||||||
song_table = "songs"
|
song_table = "songs"
|
||||||
entry_table = "entries"
|
entry_table = "entries"
|
||||||
index_label = "Id"
|
index_label = "Id"
|
||||||
|
done_table = "done_songs"
|
||||||
|
|
||||||
def open_db():
|
def open_db():
|
||||||
conn = sqlite3.connect("data/test.db")
|
conn = sqlite3.connect("data/test.db")
|
||||||
@ -28,14 +29,19 @@ def import_songs(song_csv):
|
|||||||
|
|
||||||
def create_entry_table():
|
def create_entry_table():
|
||||||
conn = open_db()
|
conn = open_db()
|
||||||
t = (entry_table,)
|
|
||||||
conn.execute('CREATE TABLE IF NOT EXISTS '+entry_table +
|
conn.execute('CREATE TABLE IF NOT EXISTS '+entry_table +
|
||||||
' (ID INTEGER PRIMARY KEY NOT NULL, Song_Id INTEGER NOT NULL, Name VARCHAR(255))')
|
' (ID INTEGER PRIMARY KEY NOT NULL, Song_Id INTEGER NOT NULL, Name VARCHAR(255))')
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def create_done_song_table():
|
||||||
|
conn = open_db()
|
||||||
|
conn.execute('CREATE TABLE IF NOT EXISTS '+done_table +
|
||||||
|
' (Song_Id INTEGER PRIMARY KEY NOT NULL, Plays INTEGER)')
|
||||||
|
conn.close()
|
||||||
|
|
||||||
def create_song_table():
|
def create_song_table():
|
||||||
conn = open_db()
|
conn = open_db()
|
||||||
t = (entry_table,)
|
|
||||||
conn.execute("CREATE TABLE IF NOT EXISTS \""+song_table+"""\" (
|
conn.execute("CREATE TABLE IF NOT EXISTS \""+song_table+"""\" (
|
||||||
"Id" INTEGER,
|
"Id" INTEGER,
|
||||||
"Title" TEXT,
|
"Title" TEXT,
|
||||||
@ -52,21 +58,37 @@ def create_song_table():
|
|||||||
def create_list_view():
|
def create_list_view():
|
||||||
conn = open_db()
|
conn = open_db()
|
||||||
conn.execute("""CREATE VIEW IF NOT EXISTS [Liste] AS
|
conn.execute("""CREATE VIEW IF NOT EXISTS [Liste] AS
|
||||||
SELECT Name, Title, Artist, entries.Id
|
SELECT Name, Title, Artist, entries.Id, songs.Id
|
||||||
FROM entries, songs
|
FROM entries, songs
|
||||||
WHERE entries.Song_Id=songs.Id""")
|
WHERE entries.Song_Id=songs.Id""")
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def create_done_song_view():
|
||||||
|
conn = open_db()
|
||||||
|
conn.execute("""CREATE VIEW IF NOT EXISTS [Abspielliste] AS
|
||||||
|
SELECT Artist || \" - \" || Title AS Song, Plays AS Wiedergaben
|
||||||
|
FROM songs, done_songs
|
||||||
|
WHERE done_songs.Song_Id=songs.Id""")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
def get_list():
|
def get_list():
|
||||||
conn = open_db()
|
conn = open_db()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute("SELECT * FROM Liste")
|
cur.execute("SELECT * FROM Liste")
|
||||||
return cur.fetchall()
|
return cur.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def get_played_list():
|
||||||
|
conn = open_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT * FROM Abspielliste")
|
||||||
|
return cur.fetchall()
|
||||||
|
|
||||||
def get_song_list():
|
def get_song_list():
|
||||||
conn =open_db()
|
conn =open_db()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute("SELECT Title || \" - \" || Artist AS Song, Id FROM songs")
|
cur.execute("SELECT Artist || \" - \" || Title AS Song, Id FROM songs")
|
||||||
return cur.fetchall()
|
return cur.fetchall()
|
||||||
|
|
||||||
def get_song_completions(input_string):
|
def get_song_completions(input_string):
|
||||||
@ -86,6 +108,30 @@ def add_entry(name,song_id):
|
|||||||
conn.close()
|
conn.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def add_sung_song(entry_id):
|
||||||
|
conn = open_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("""SELECT Song_Id FROM entries WHERE Id=?""",(entry_id,))
|
||||||
|
song_id = cur.fetchone()[0]
|
||||||
|
cur.execute("""INSERT OR REPLACE INTO done_songs (Song_Id, Plays)
|
||||||
|
VALUES("""+str(song_id)+""",
|
||||||
|
COALESCE(
|
||||||
|
(SELECT Plays FROM done_songs
|
||||||
|
WHERE Song_Id="""+str(song_id)+"), 0) + 1)"
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
delete_entry(entry_id)
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def clear_played_songs():
|
||||||
|
conn = open_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("DELETE FROM done_songs")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
def delete_entry(id):
|
def delete_entry(id):
|
||||||
conn = open_db()
|
conn = open_db()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
24
app/main.py
24
app/main.py
@ -29,6 +29,11 @@ def enqueue():
|
|||||||
def songlist():
|
def songlist():
|
||||||
return render_template('songlist.html', list=database.get_song_list(), auth=basic_auth.authenticate())
|
return render_template('songlist.html', list=database.get_song_list(), auth=basic_auth.authenticate())
|
||||||
|
|
||||||
|
@app.route("/plays")
|
||||||
|
@basic_auth.required
|
||||||
|
def played_list():
|
||||||
|
return render_template('played_list.html', list=database.get_played_list(), auth=basic_auth.authenticate())
|
||||||
|
|
||||||
@app.route("/api/songs")
|
@app.route("/api/songs")
|
||||||
def songs():
|
def songs():
|
||||||
list = database.get_song_list()
|
list = database.get_song_list()
|
||||||
@ -63,6 +68,23 @@ def delete_entry(entry_id):
|
|||||||
else:
|
else:
|
||||||
return Response('{"status": "FAIL"}', mimetype='text/json')
|
return Response('{"status": "FAIL"}', mimetype='text/json')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/entries/mark_sung/<entry_id>")
|
||||||
|
@basic_auth.required
|
||||||
|
def mark_sung(entry_id):
|
||||||
|
if database.add_sung_song(entry_id):
|
||||||
|
return Response('{"status": "OK"}', mimetype='text/json')
|
||||||
|
else:
|
||||||
|
return Response('{"status": "FAIL"}', mimetype='text/json')
|
||||||
|
|
||||||
|
@app.route("/api/played/clear")
|
||||||
|
@basic_auth.required
|
||||||
|
def clear_played_songs():
|
||||||
|
if database.clear_played_songs():
|
||||||
|
return Response('{"status": "OK"}', mimetype='text/json')
|
||||||
|
else:
|
||||||
|
return Response('{"status": "FAIL"}', mimetype='text/json')
|
||||||
|
|
||||||
@app.route("/api/entries/delete_all")
|
@app.route("/api/entries/delete_all")
|
||||||
@basic_auth.required
|
@basic_auth.required
|
||||||
def delete_all_entries():
|
def delete_all_entries():
|
||||||
@ -81,7 +103,9 @@ def activate_job():
|
|||||||
helpers.create_data_directory()
|
helpers.create_data_directory()
|
||||||
database.create_entry_table()
|
database.create_entry_table()
|
||||||
database.create_song_table()
|
database.create_song_table()
|
||||||
|
database.create_done_song_table()
|
||||||
database.create_list_view()
|
database.create_list_view()
|
||||||
|
database.create_done_song_view()
|
||||||
helpers.setup_config(app)
|
helpers.setup_config(app)
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,3 +38,16 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admincontrols {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,11 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/list">Songsuche</a>
|
<a class="nav-link" href="/list">Songsuche</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if auth %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/plays">Abspielliste</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<!--<form class="form-inline my-2 my-lg-0">
|
<!--<form class="form-inline my-2 my-lg-0">
|
||||||
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
|
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
|
||||||
@ -79,6 +84,9 @@
|
|||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
|
||||||
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous">
|
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous">
|
||||||
</script>
|
</script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"
|
||||||
|
integrity="sha256-4F7e4JsAJyLUdpP7Q8Sah866jCOhv72zU5E8lIRER4w=" crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
{% block extrajs %}{% endblock %}
|
{% block extrajs %}{% endblock %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
<th scope="col">Song</th>
|
<th scope="col">Song</th>
|
||||||
<th scope="col">Künstler</th>
|
<th scope="col">Künstler</th>
|
||||||
<th scope="col">Löschen</th>
|
<th scope="col">Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for entry in list: %}
|
{% for entry in list: %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -35,7 +35,12 @@
|
|||||||
{{ entry[2] }}
|
{{ entry[2] }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button type='button' class='btn btn-danger justify-content-center align-content-between'
|
<button type='button' class='btn btn-success'
|
||||||
|
data-toggle="tooltip" data-placement="top" title="Als gesungen markieren"
|
||||||
|
onclick='markEntryAsSung({{ entry[3] }})'><i
|
||||||
|
class="fas fa-check"></i></button>
|
||||||
|
<button type='button' class='btn btn-danger'
|
||||||
|
data-toggle="tooltip" data-placement="top" title="Eintrag löschen"
|
||||||
onclick='confirmDeleteEntry("{{ entry[0] }}",{{ entry[3] }})'><i
|
onclick='confirmDeleteEntry("{{ entry[0] }}",{{ entry[3] }})'><i
|
||||||
class="fas fa-trash"></i></button>
|
class="fas fa-trash"></i></button>
|
||||||
</td>
|
</td>
|
||||||
@ -47,9 +52,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extrajs %}
|
{% block extrajs %}
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"
|
|
||||||
integrity="sha256-4F7e4JsAJyLUdpP7Q8Sah866jCOhv72zU5E8lIRER4w=" crossorigin="anonymous"></script>
|
|
||||||
<script>
|
<script>
|
||||||
|
$(function () {
|
||||||
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
|
})
|
||||||
function confirmDeleteEntry(name, entry_id) {
|
function confirmDeleteEntry(name, entry_id) {
|
||||||
bootbox.confirm("Wirklich den Eintrag von "+name+" löschen?", function(result){
|
bootbox.confirm("Wirklich den Eintrag von "+name+" löschen?", function(result){
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -107,7 +113,18 @@
|
|||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: '/api/entries/delete/'+entry_id,
|
url: '/api/entries/delete/'+entry_id,
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
|
async: false
|
||||||
|
});
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
function markEntryAsSung(entry_id) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: '/api/entries/mark_sung/'+entry_id,
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: 'json',
|
||||||
|
async: false
|
||||||
});
|
});
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
@ -116,7 +133,8 @@
|
|||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: '/api/entries/delete_all',
|
url: '/api/entries/delete_all',
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
|
async: false
|
||||||
});
|
});
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
61
app/templates/played_list.html
Normal file
61
app/templates/played_list.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}Songsuche{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="card admincontrols" style="width: 100%">
|
||||||
|
<div class="card-body">
|
||||||
|
<button type="button" class="topbutton btn btn-danger" onclick="confirmDeleteAllEntries()"><i
|
||||||
|
class="fas fa-trash mr-2"></i>Abspielliste löschen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Song</th>
|
||||||
|
<th scope="col">Wiedergaben</th>
|
||||||
|
</tr>
|
||||||
|
{% for entry in list: %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ entry[0] }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ entry[1] }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
{% block extrajs %}
|
||||||
|
<script>
|
||||||
|
function confirmDeleteAllEntries() {
|
||||||
|
bootbox.confirm({
|
||||||
|
message: "Wirklich Abspielliste löschen?",
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: 'Ja',
|
||||||
|
className: 'btn btn-danger'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: 'Nein',
|
||||||
|
className: 'btn btn-secondary'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function(result){
|
||||||
|
if (result) {
|
||||||
|
deleteAllEntries()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function deleteAllEntries() {
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: '/api/played/clear',
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: 'json',
|
||||||
|
async: false
|
||||||
|
});
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user