Add list of played songs

This commit is contained in:
Phillip Kühne 2019-06-29 01:24:54 +02:00
parent 438bcffd0b
commit 5f551ac092
7 changed files with 181 additions and 12 deletions

3
.gitignore vendored
View File

@ -130,5 +130,4 @@ dmypy.json
!.vscode/extensions.json !.vscode/extensions.json
# Test data # Test data
test.db data/
config.json

View File

@ -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()

View File

@ -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)

View File

@ -38,3 +38,16 @@ main {
} }
} }
@media print {
body {
font-size: 1.3em;
}
.footer {
display: none !important;
}
.admincontrols {
display: none;
}
}

View File

@ -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 () {

View File

@ -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();
} }

View 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 %}