Revamped "Manage Unmatched" page, added "Manage Manual" page.

This commit is contained in:
theguardian
2013-10-11 20:08:36 -07:00
parent 4ea867836e
commit b0d75b46ec
4 changed files with 351 additions and 58 deletions

View File

@@ -315,6 +315,7 @@ function doAjaxCall(url,elem,reload,form) {
console.log('refresh'); refreshTable();
}
if ( reload == "tabs") refreshTab();
if ( reload == "page") location.reload();
if ( form ) {
// Change the option to 'choose...'
$(formID + " select").children('option[disabled=disabled]').attr('selected','selected');

View File

@@ -0,0 +1,121 @@
<%inherit file="base.html" />
<%!
import headphones
from headphones import db, helpers
myDB = db.DBConnection()
%>
<%def name="headerIncludes()">
<div id="subhead_container">
<div id="subhead_menu">
</div>
</div>
<a href="manageUnmatched" class="back">&laquo; Back to Unmatched Albums</a>
</%def>
<%def name="body()">
<div class="table_wrapper">
<div id="manageheader" class="title">
<h1 class="clearfix"><img src="interfaces/default/images/icon_manage.png" alt="manage"/>Manage Manually Changed Albums</h1>
</div>
<table class="display" id="artist_table">
<thead>
<tr>
<th id="artist">Local Artist</th>
<th id="album">Local Album</th>
<th id="status">Previous Action</th>
</tr>
</thead>
<tbody>
<% count_albums=0 %>
%for album in manualalbums:
<tr class="gradeZ">
<%
old_artist_clean = album['ArtistName'].replace('&','%26').replace('+', '%2B').replace("'","%27")
old_album_clean = album['AlbumTitle'].replace('&','%26').replace('+', '%2B').replace("'","%27")
%>
<td id="artist">${album['ArtistName']}<BR>
<button id="restore_artist${count_albums}" onClick="restore_Artist(this.id)">(<-) Restore Artist</button>
<div id="restore_artist_dialog${count_albums}" title="Restore Artist" style="display:none">
<table>
<tr><td>Are you sure you want to restore Local Artist: ${album['ArtistName']} to unmatched?</td></tr>
<tr><td align="right"><BR>
%if album['AlbumStatus'] == "Ignored":
<button href="#" onclick="doAjaxCall('markManual?action=unignoreArtist&existing_artist=${old_artist_clean}', $(this), 'page');" data-success="Successfully restored ${album['ArtistName']} to unmatched">Restore Artist</button>
%elif album['AlbumStatus'] == "Matched":
<button href="#" onclick="doAjaxCall('markManual?action=unmatchArtist&existing_artist=${old_artist_clean}', $(this), 'page');" data-success="Successfully restored ${album['ArtistName']} to unmatched">Restore Artist</button>
%endif
</td></tr>
</table>
</div>
</td>
<td id="album">${album['AlbumTitle']}<BR>
<button id="restore_album${count_albums}" onClick="restore_Album(this.id)">(<-) Restore Album</button>
<div id="restore_album_dialog${count_albums}" title="Restore Album" style="display:none">
<table>
<tr><td>Are you sure you want to restore Local Album: ${album['AlbumTitle']} to unmatched?</td></tr>
<tr><td align="right"><BR>
%if album['AlbumStatus'] == "Ignored":
<button href="#" onclick="doAjaxCall('markManual?action=unignoreAlbum&existing_artist=${old_artist_clean}&existing_album=${old_album_clean}', $(this), 'page');" data-success="Successfully restored ${album['AlbumTitle']} to unmatched">Restore Album</button>
%elif album['AlbumStatus'] == "Matched":
<button href="#" onclick="doAjaxCall('markManual?action=unmatchAlbum&existing_artist=${old_artist_clean}&existing_album=${old_album_clean}', $(this), 'page');" data-success="Successfully restored ${album['AlbumTitle']} to unmatched">Restore Album</button>
%endif
</td></tr>
</table>
</div>
</td>
<td id="status">${album['AlbumStatus']}
</td>
</tr>
<% count_albums+=1 %>
%endfor
</tbody>
</table>
</div>
</%def>
<%def name="headIncludes()">
<link rel="stylesheet" href="interfaces/default/css/data_table.css">
</%def>
<%def name="javascriptIncludes()">
<script src="js/libs/jquery.dataTables.min.js"></script>
<script>
$(document).ready(function()
{
$('#artist_table').dataTable(
{
"bStateSave": true,
"bPaginate": true,
"oLanguage": {
"sSearch": "",
"sLengthMenu":"Show _MENU_ albums per page",
"sInfo":"Showing _START_ to _END_ of _TOTAL_ albums",
"sInfoEmpty":"Showing 0 to 0 of 0 albums",
"sInfoFiltered":"(filtered from _MAX_ total albums)",
"sEmptyTable": " ",
},
"sPaginationType": "full_numbers",
});
initActions();
});
function restore_Artist(clicked_id) {
n=clicked_id.replace("restore_artist","");
$("#restore_artist_dialog"+n).dialog();
return false;
}
function restore_Album(clicked_id) {
n=clicked_id.replace("restore_album","");
$("#restore_album_dialog"+n).dialog();
return false;
}
</script>
</%def>

View File

@@ -16,6 +16,7 @@
<%def name="headerIncludes()">
<div id="subhead_container">
<div id="subhead_menu">
<a class="menu_link_edit" href="manageManual">Manage Manually Matched & Ignored</a>
</div>
</div>
<a href="manage" class="back">&laquo; Back to manage overview</a>
@@ -31,56 +32,76 @@
<table class="display" id="artist_table">
<thead>
<tr>
<th id="artist" align="center">Local Artist</th>
<th id="album" align="center">Local Album</th>
<th id="matchartist" align="center">Match Artist</th>
<th id="matchalbum" align="center">Match Album</th>
<th id="artist">Local Artist</th>
<th id="album">Local Album</th>
</tr>
</thead>
<tbody>
<% count_albums=0 %>
%for album in unmatchedalbums:
<tr class="gradeZ">
<td id="artist" align="center">${album['ArtistName']}
<form action="markUnmatched" method="get">
<input type="hidden" name="action" value="ignoreArtist">
<input type="hidden" name="existing_artist" value="${album['ArtistName']}">
<input type="submit" value="(-) Ignore Artist">
</form>
<%
old_artist_clean = album['ArtistName'].replace('&','%26').replace("'","%27")
old_album_clean = album['AlbumTitle'].replace('&','%26').replace("'","%27")
old_artist_js = album['ArtistName'].replace("'","\\'").replace('"','\\"')
old_album_js = album['AlbumTitle'].replace("'","\\'").replace('"','\\"')
%>
<td id="artist">${album['ArtistName']}<BR>
<button id="ignore_artists${count_albums}" onClick="ignore_Artist(this.id)">(-) Ignore Artist</button>
<div id="ignore_artist_dialog${count_albums}" title="Ignore Artist" style="display:none">
<table>
<tr><td>Are you sure you want to ignore Local Artist: ${album['ArtistName']} from future matching?</td></tr>
<tr><td align="right"><BR>
<button href="#" onclick="doAjaxCall('markUnmatched?action=ignoreArtist&existing_artist=${old_artist_clean}', $(this), 'page');" data-success="Successfully ignored ${album['ArtistName']} from future matching">Ignore Artist</button>
</td></tr>
</table>
</div>
<button id="match_artists${count_albums}" onClick="load_Artist(this.id)">(->) Match Artist</button>
<div id="artist_dialog${count_albums}" title="Match Artist" style="display:none">
<table width=400>
<tr><td>Local Artist:</td><td>${album['ArtistName']}</td></tr>
<tr><td>Match Artist</td><td>
<select id="artist_options${count_albums}" name="new_artist">
</select>
</td></tr>
<tr><td></td><td align="right"><BR>
<button href="#" onclick="artist_matcher(${count_albums}, '${old_artist_js}')">Match Artist</button>
</td></tr>
</table>
</div>
</td>
<td id="album" align="center">${album['AlbumTitle']}
<form action="markUnmatched" method="get">
<input type="hidden" name="action" value="ignoreAlbum">
<input type="hidden" name="existing_artist" value="${album['ArtistName']}">
<input type="hidden" name="existing_album" value="${album['AlbumTitle']}">
<input type="submit" value="(-) Ignore Album">
</form>
</td>
<td id="matchartist" align="center">
<button id="LoadArtists${count_albums}" onClick="click_Artist(this.id)">Load Artist List</button>
<form id="matchArtist_form${count_albums}" action="markUnmatched" method="get">
<input type="hidden" name="action" value="matchArtist">
<input type="hidden" name="existing_artist" value="${album['ArtistName']}">
<select id="artist_options${count_albums}" name="new_artist">
<option value="Null/None" selected="selected">Click "Load Artists" For List</option>
</select>
<input type="submit" value="(->) Match Artist">
</form>
</td>
<td id="matchalbum" align="center">
<button id="LoadAlbumArtists${count_albums}" onClick="click_AlbumArtist(this.id)">Load Artist List</button>
<form id="matchAlbumArtist_form${count_albums}" action="markUnmatched" method="get">
<input type="hidden" name="action" value="matchAlbum">
<input type="hidden" name="existing_artist" value="${album['ArtistName']}">
<input type="hidden" name="existing_album" value="${album['AlbumTitle']}">
<td id="album">${album['AlbumTitle']}<BR>
<button id="ignore_albums${count_albums}" onClick="ignore_Album(this.id)">(-) Ignore Album</button>
<div id="ignore_album_dialog${count_albums}" title="Ignore Album" style="display:none">
<table>
<tr><td>Are you sure you want to ignore Local Album: ${album['AlbumTitle']} from future matching?</td></tr>
<tr><td align="right"><BR>
<button href="#" onclick="doAjaxCall('markUnmatched?action=ignoreAlbum&existing_artist=${old_artist_clean}&existing_album=${old_album_clean}', $(this), 'page');" data-success="Successfully ignored ${album['AlbumTitle']} from future matching">Ignore Album</button>
</td></tr>
</table>
</div>
<button id="match_albums${count_albums}" onClick="load_AlbumArtist(this.id)">(->) Match Album</button>
<div id="album_dialog${count_albums}" title="Match Album" style="display:none">
<table width=650>
<tr><td>Local Artist:</td><td>${album['ArtistName']}</td></tr>
<tr><td>Local Album:</td><td>${album['AlbumTitle']}</td></tr>
<tr><td>Match Artist</td><td>
<select id="album_artist_options${count_albums}" name="new_artist">
<option value="Null/None" selected="selected">Click "Load Artists" For List</option>
</select>
</td></tr>
<tr><td>Match Album</td><td>
<select id="album_options${count_albums}" name="new_album">
<option value="Null/None" selected="selected">Select an Artist to View</option>
</select>
<input type="submit" value="(->) Match Album">
</form>
</td></tr>
<tr><td></td><td align="right"><BR>
<button href="#" onclick="album_matcher(${count_albums}, '${old_artist_js}', '${old_album_js}')">Match Album</button>
</td></tr>
</table>
</div>
</td>
</tr>
<% count_albums+=1 %>
@@ -116,8 +137,25 @@
initActions();
});
function click_Artist(clicked_id) {
n=clicked_id.replace("LoadArtists","");
function ignore_Artist(clicked_id) {
n=clicked_id.replace("ignore_artists","");
$("#ignore_artist_dialog"+n).dialog();
return false;
}
function ignore_Album(clicked_id) {
n=clicked_id.replace("ignore_albums","");
$("#ignore_album_dialog"+n).dialog();
return false;
}
function load_Artist(clicked_id) {
n=clicked_id.replace("match_artists","");
var d = $("#artist_dialog"+n).dialog();
d.dialog("option", "width", 450);
d.dialog("option", "position", "center");
$('#artist_options'+n).html('');
$.each(${json_artists}, function(key, value) {
$('#artist_options'+n).append($("<option/>", {
@@ -125,33 +163,97 @@ initActions();
text: value
}));
});
change_artist(n)
return false;
}
function click_AlbumArtist(clicked_id) {
n=clicked_id.replace("LoadAlbumArtists","");
$('#album_artist_options'+n).html('');
function change_artist(n) {
selected_artist = $("#artist_options"+n).find("option:selected").text();
selected_artist_clean = selected_artist.replace('&', '%26').replace('+', '%2B');
$("#artist_options"+n).change(function() {
selected_artist = $("#artist_options"+n).find("option:selected").text();
selected_artist_clean = selected_artist.replace('&', '%26').replace('+', '%2B');
});
}
function artist_matcher(n, existing_artist) {
var existing_artist = existing_artist.toString();
var existing_artist_clean = existing_artist.replace('&', '%26').replace('+', '%2B');
$('#match_artists'+n).attr('data-success', 'Successfully matched '+existing_artist+' with '+selected_artist);
doAjaxCall('markUnmatched?action=matchArtist&existing_artist='+existing_artist_clean+'&new_artist='+selected_artist_clean, $('#match_artists'+n), 'page');
}
function load_AlbumArtist(clicked_id) {
n=clicked_id.replace("match_albums","");
var d = $("#album_dialog"+n).dialog();
d.dialog("option", "width", 700);
d.dialog("option", "position", "center");
$('#album_artist_options'+n).html('');
$.each(${json_artists}, function(key, value) {
$('#album_artist_options'+n).append($("<option/>", {
value: value,
text: value
}));
});
update_albums(n)
load_json_albums(n)
return false;
}
function update_albums(n) {
function load_json_albums(n) {
selected_album_artist = $("#album_artist_options"+n).find("option:selected").text();
selected_album_artist_clean = selected_album_artist.replace('&', '%26').replace('+', '%2B');
$('#album_options'+n).html('')
$.getJSON("getAlbumsByArtist_json?artist="+selected_album_artist_clean, function( data ) {
$.each( data, function( key, value ) {
$('#album_options'+n).append($("<option/>", {
value: value,
text: value
}));
});
selected_album= $("#album_options"+n).find("option:selected").text();
selected_album_clean = selected_album.replace('&', '%26').replace('+', '%2B');
});
change_json_albums(n)
change_album(n)
}
function change_json_albums(n) {
$("#album_artist_options"+n).change(function(){
var selected_artist = $("#album_artist_options"+n).find("option:selected").text();
selected_album_artist = $("#album_artist_options"+n).find("option:selected").text();
selected_album_artist_clean = selected_album_artist.replace('&', '%26').replace('+', '%2B');
$('#album_options'+n).html('')
$.getJSON("getAlbumsByArtist_json?artist="+selected_artist, function( data ) {
$.getJSON("getAlbumsByArtist_json?artist="+selected_album_artist_clean, function( data ) {
$.each( data, function( key, value ) {
$('#album_options'+n).append($("<option/>", {
value: value,
text: value
}));
});
selected_album= $("#album_options"+n).find("option:selected").text();
selected_album_clean = selected_album.replace('&', '%26').replace('+', '%2B');
});
change_album(n)
});
}
function change_album(n) {
$("#album_options"+n).change(function() {
selected_album= $("#album_options"+n).find("option:selected").text();
selected_album_clean = selected_album.replace('&', '%26').replace('+', '%2B');
});
}
function album_matcher(n, existing_artist, existing_album) {
var existing_artist = existing_artist.toString();
var existing_artist_clean = existing_artist.replace('&', '%26').replace('+', '%2B');
var existing_album = existing_album.toString();
var existing_album_clean = existing_album.replace('&', '%26').replace('+', '%2B');
$('#match_albums'+n).attr('data-success', 'Successfully matched '+existing_album+' with '+selected_album);
doAjaxCall('markUnmatched?action=matchAlbum&existing_artist='+existing_artist_clean+'&new_artist='+selected_album_artist_clean+'&existing_album='+existing_album_clean+'&new_album='+selected_album_clean, $('#match_albums'+n), 'page');
}
</script>
</%def>

View File

@@ -26,6 +26,7 @@ import time
import threading
import string
import json
from operator import itemgetter
import headphones
@@ -378,7 +379,7 @@ class WebInterface(object):
def markUnmatched(self, action=None, existing_artist=None, existing_album=None, new_artist=None, new_album=None):
myDB = db.DBConnection()
if action == "ignoreArtist":
artist = existing_artist
myDB.action('UPDATE have SET Matched="Ignored" WHERE ArtistName=? AND Matched IS NULL', [artist])
@@ -412,14 +413,13 @@ class WebInterface(object):
if match_tracks:
myDB.upsert("tracks", newValueDict, controlValueDict)
myDB.action('UPDATE have SET Matched="Manual" WHERE CleanName=?', [new_clean_filename])
album_id = match_tracks['AlbumID']
update_count+=1
#This was throwing errors and I don't know why, but it seems to be working fine.
#else:
#logger.info("There was an error modifying Artist %s. This should not have happened" % existing_artist)
logger.info("Manual matching yielded %s new matches for Artist %s" % (update_count, new_artist))
logger.info("Manual matching yielded %s new matches for Artist: %s" % (update_count, new_artist))
if update_count > 0:
librarysync.update_album_status(album_id)
librarysync.update_album_status()
else:
logger.info("Artist %s already named appropriately; nothing to modify" % existing_artist)
@@ -451,21 +451,90 @@ class WebInterface(object):
if match_tracks:
myDB.upsert("tracks", newValueDict, controlValueDict)
myDB.action('UPDATE have SET Matched="Manual" WHERE CleanName=?', [new_clean_filename])
album_id = match_tracks['AlbumID']
update_count+=1
#This was throwing errors and I don't know why, but it seems to be working fine.
#else:
#logger.info("There was an error modifying Artist %s / Album %s with clean name %s" % (existing_artist, existing_album, existing_clean_string))
logger.info("Manual matching yielded %s new matches for Artist %s / Album %s" % (update_count, new_artist, new_album))
logger.info("Manual matching yielded %s new matches for Artist: %s / Album: %s" % (update_count, new_artist, new_album))
if update_count > 0:
librarysync.update_album_status()
librarysync.update_album_status(album_id)
else:
logger.info("Artist %s / Album %s already named appropriately; nothing to modify" % (existing_artist, existing_album))
raise cherrypy.HTTPRedirect('manageUnmatched')
markUnmatched.exposed = True
def manageManual(self):
myDB = db.DBConnection()
manual_albums = []
manualalbums = myDB.select('SELECT ArtistName, AlbumTitle, TrackTitle, CleanName, Matched from have')
for albums in manualalbums:
original_clean = helpers.cleanName(albums['ArtistName']+" "+albums['AlbumTitle']+" "+albums['TrackTitle'])
if albums['Matched'] == "Ignored" or albums['Matched'] == "Manual" or albums['CleanName'] != original_clean:
if albums['Matched'] == "Ignored":
album_status = "Ignored"
elif albums['Matched'] == "Manual" or albums['CleanName'] != original_clean:
album_status = "Matched"
manual_dict = { 'ArtistName' : albums['ArtistName'], 'AlbumTitle' : albums['AlbumTitle'], 'AlbumStatus' : album_status }
if manual_dict not in manual_albums:
manual_albums.append(manual_dict)
manual_albums_sorted = sorted(manual_albums, key=itemgetter('ArtistName', 'AlbumTitle'))
return serve_template(templatename="managemanual.html", title="Manage Manual Items", manualalbums=manual_albums_sorted)
manageManual.exposed = True
def markManual(self, action=None, existing_artist=None, existing_album=None):
myDB = db.DBConnection()
if action == "unignoreArtist":
artist = existing_artist
myDB.action('UPDATE have SET Matched=NULL WHERE ArtistName=? AND Matched="Ignored"', [artist])
logger.info("Artist: %s successfully restored to unmatched list" % artist)
elif action == "unignoreAlbum":
artist = existing_artist
album = existing_album
myDB.action('UPDATE have SET Matched=NULL WHERE ArtistName=? AND AlbumTitle=? AND Matched="Ignored"', (artist, album))
logger.info("Album: %s successfully restored to unmatched list" % album)
elif action == "unmatchArtist":
artist = existing_artist
update_clean = myDB.select('SELECT ArtistName, AlbumTitle, TrackTitle, CleanName, Matched from have WHERE ArtistName=?', [artist])
update_count = 0
for tracks in update_clean:
original_clean = helpers.cleanName(tracks['ArtistName']+" "+tracks['AlbumTitle']+" "+tracks['TrackTitle']).lower()
album = tracks['AlbumTitle']
track_title = tracks['TrackTitle']
if tracks['CleanName'] != original_clean:
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE CleanName=?', [None, None, None, tracks['CleanName']])
myDB.action('UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE CleanName=?', [None, None, None, tracks['CleanName']])
myDB.action('UPDATE have SET CleanName=?, Matched=NULL WHERE ArtistName=? AND AlbumTitle=? AND TrackTitle=?', (original_clean, artist, album, track_title))
update_count+=1
if update_count > 0:
librarysync.update_album_status()
logger.info("Artist: %s successfully restored to unmatched list" % artist)
elif action == "unmatchAlbum":
artist = existing_artist
album = existing_album
update_clean = myDB.select('SELECT ArtistName, AlbumTitle, TrackTitle, CleanName, Matched from have WHERE ArtistName=? AND AlbumTitle=?', (artist, album))
update_count = 0
for tracks in update_clean:
original_clean = helpers.cleanName(tracks['ArtistName']+" "+tracks['AlbumTitle']+" "+tracks['TrackTitle']).lower()
track_title = tracks['TrackTitle']
if tracks['CleanName'] != original_clean:
album_id_check = myDB.action('SELECT AlbumID from tracks WHERE CleanName=?', [tracks['CleanName']]).fetchone()
if album_id_check:
album_id = album_id_check[0]
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE CleanName=?', [None, None, None, tracks['CleanName']])
myDB.action('UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE CleanName=?', [None, None, None, tracks['CleanName']])
myDB.action('UPDATE have SET CleanName=?, Matched=NULL WHERE ArtistName=? AND AlbumTitle=? AND TrackTitle=?', (original_clean, artist, album, track_title))
update_count+=1
if update_count > 0:
librarysync.update_album_status(album_id)
logger.info("Album: %s successfully restored to unmatched list" % album)
markManual.exposed = True
def markArtists(self, action=None, **args):
myDB = db.DBConnection()
artistsToAdd = []