From a3831f9b7cbc7f25c5993df96d1751f956109b0b Mon Sep 17 00:00:00 2001 From: krateng Date: Wed, 18 Oct 2023 19:04:19 +0200 Subject: [PATCH] Added disassociation functionality --- maloja/apis/native_v1.py | 16 ++++---- maloja/database/__init__.py | 32 ++++++++++----- maloja/database/sqldb.py | 47 ++++++++++++++++++++++ maloja/web/jinja/album.jinja | 2 + maloja/web/jinja/artist.jinja | 1 + maloja/web/jinja/icons/disassociate.jinja | 5 +++ maloja/web/jinja/icons/remove_album.jinja | 6 +++ maloja/web/jinja/icons/remove_artist.jinja | 5 +++ maloja/web/static/css/maloja.css | 4 +- maloja/web/static/js/edit.js | 47 ++++++++++++++++++++++ 10 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 maloja/web/jinja/icons/disassociate.jinja create mode 100644 maloja/web/jinja/icons/remove_album.jinja create mode 100644 maloja/web/jinja/icons/remove_artist.jinja diff --git a/maloja/apis/native_v1.py b/maloja/apis/native_v1.py index 48c4c84..c51c367 100644 --- a/maloja/apis/native_v1.py +++ b/maloja/apis/native_v1.py @@ -774,23 +774,25 @@ def merge_artists(target_id,source_ids): @api.post("associate_albums_to_artist") @authenticated_function(api=True) @catch_exceptions -def associate_albums_to_artist(target_id,source_ids): - result = database.associate_albums_to_artist(target_id,source_ids) +def associate_albums_to_artist(target_id,source_ids,remove=False): + result = database.associate_albums_to_artist(target_id,source_ids,remove=remove) + descword = "removed" if remove else "added" if result: return { "status":"success", - "desc":f"{result['target']} was added as album artist of {', '.join(src['albumtitle'] for src in result['sources'])}" + "desc":f"{result['target']} was {descword} as album artist of {', '.join(src['albumtitle'] for src in result['sources'])}" } @api.post("associate_tracks_to_artist") @authenticated_function(api=True) @catch_exceptions -def associate_tracks_to_artist(target_id,source_ids): - result = database.associate_tracks_to_artist(target_id,source_ids) +def associate_tracks_to_artist(target_id,source_ids,remove=False): + result = database.associate_tracks_to_artist(target_id,source_ids,remove=remove) + descword = "removed" if remove else "added" if result: return { "status":"success", - "desc":f"{result['target']} was added as artist for {', '.join(src['title'] for src in result['sources'])}" + "desc":f"{result['target']} was {descword} as artist for {', '.join(src['title'] for src in result['sources'])}" } @api.post("associate_tracks_to_album") @@ -801,7 +803,7 @@ def associate_tracks_to_album(target_id,source_ids): if result: return { "status":"success", - "desc":f"{', '.join(src['title'] for src in result['sources'])} were added to {result['target']['albumtitle']}" + "desc":f"{', '.join(src['title'] for src in result['sources'])} were " + f"added to {result['target']['albumtitle']}" if target_id else "removed from their album" } diff --git a/maloja/database/__init__.py b/maloja/database/__init__.py index 372186e..067060b 100644 --- a/maloja/database/__init__.py +++ b/maloja/database/__init__.py @@ -269,11 +269,15 @@ def merge_albums(target_id,source_ids): @waitfordb -def associate_albums_to_artist(target_id,source_ids): +def associate_albums_to_artist(target_id,source_ids,remove=False): sources = [sqldb.get_album(id) for id in source_ids] target = sqldb.get_artist(target_id) - log(f"Adding {sources} into {target}") - sqldb.add_artists_to_albums(artist_ids=[target_id],album_ids=source_ids) + if remove: + log(f"Removing {sources} from {target}") + sqldb.remove_artists_from_albums(artist_ids=[target_id],album_ids=source_ids) + else: + log(f"Adding {sources} into {target}") + sqldb.add_artists_to_albums(artist_ids=[target_id],album_ids=source_ids) result = {'sources':sources,'target':target} dbcache.invalidate_entity_cache() dbcache.invalidate_caches() @@ -281,11 +285,15 @@ def associate_albums_to_artist(target_id,source_ids): return result @waitfordb -def associate_tracks_to_artist(target_id,source_ids): +def associate_tracks_to_artist(target_id,source_ids,remove=False): sources = [sqldb.get_track(id) for id in source_ids] target = sqldb.get_artist(target_id) - log(f"Adding {sources} into {target}") - sqldb.add_artists_to_tracks(artist_ids=[target_id],track_ids=source_ids) + if remove: + log(f"Removing {sources} from {target}") + sqldb.remove_artists_from_tracks(artist_ids=[target_id],track_ids=source_ids) + else: + log(f"Adding {sources} into {target}") + sqldb.add_artists_to_tracks(artist_ids=[target_id],track_ids=source_ids) result = {'sources':sources,'target':target} dbcache.invalidate_entity_cache() dbcache.invalidate_caches() @@ -294,10 +302,14 @@ def associate_tracks_to_artist(target_id,source_ids): @waitfordb def associate_tracks_to_album(target_id,source_ids): + # target_id None means remove from current album! sources = [sqldb.get_track(id) for id in source_ids] - target = sqldb.get_album(target_id) - log(f"Adding {sources} into {target}") - sqldb.add_tracks_to_albums({src:target_id for src in source_ids}) + if target_id: + target = sqldb.get_album(target_id) + log(f"Adding {sources} into {target}") + sqldb.add_tracks_to_albums({src:target_id for src in source_ids}) + else: + sqldb.remove_album(source_ids) result = {'sources':sources,'target':target} dbcache.invalidate_entity_cache() dbcache.invalidate_caches() @@ -350,7 +362,7 @@ def get_tracks(dbconn=None,**keys): def get_artists(dbconn=None): return sqldb.get_artists(dbconn=dbconn) - +@waitfordb def get_albums_artist_appears_on(dbconn=None,**keys): artist_id = sqldb.get_artist_id(keys['artist'],dbconn=dbconn) diff --git a/maloja/database/sqldb.py b/maloja/database/sqldb.py index fca8ef6..25a8b4e 100644 --- a/maloja/database/sqldb.py +++ b/maloja/database/sqldb.py @@ -397,6 +397,14 @@ def add_tracks_to_albums(track_to_album_id_dict,replace=False,dbconn=None): for track_id in track_to_album_id_dict: add_track_to_album(track_id,track_to_album_id_dict[track_id],dbconn=dbconn) +@connection_provider +def remove_album(*track_ids,dbconn=None): + + DB['tracks'].update().where( + DB['tracks'].c.track_id.in_(track_ids) + ).values( + album_id=None + ) ### these will 'get' the ID of an entity, creating it if necessary @@ -640,6 +648,29 @@ def add_artists_to_tracks(track_ids,artist_ids,dbconn=None): return True +@connection_provider +def remove_artists_from_tracks(track_ids,artist_ids,dbconn=None): + + # only tracks that have at least one other artist + subquery = DB['trackartists'].select().where( + ~DB['trackartists'].c.artist_id.in_(artist_ids) + ).with_only_columns( + DB['trackartists'].c.track_id + ).distinct().alias('sub') + + op = DB['trackartists'].delete().where( + sql.and_( + DB['trackartists'].c.track_id.in_(track_ids), + DB['trackartists'].c.artist_id.in_(artist_ids), + DB['trackartists'].c.track_id.in_(subquery.select()) + ) + ) + + result = dbconn.execute(op) + clean_db(dbconn=dbconn) + + return True + @connection_provider def add_artists_to_albums(album_ids,artist_ids,dbconn=None): @@ -655,6 +686,22 @@ def add_artists_to_albums(album_ids,artist_ids,dbconn=None): return True +@connection_provider +def remove_artists_from_albums(album_ids,artist_ids,dbconn=None): + + # no check here, albums are allowed to have zero artists + + op = DB['albumartists'].delete().where( + sql.and_( + DB['albumartists'].c.album_id.in_(album_ids), + DB['albumartists'].c.artist_id.in_(artist_ids) + ) + ) + + result = dbconn.execute(op) + clean_db(dbconn=dbconn) + + return True ### Merge diff --git a/maloja/web/jinja/album.jinja b/maloja/web/jinja/album.jinja index ad70d3b..6d7ebe1 100644 --- a/maloja/web/jinja/album.jinja +++ b/maloja/web/jinja/album.jinja @@ -29,6 +29,8 @@
{% include 'icons/add_album.jinja' %} + {% include 'icons/association_mark.jinja' %} {% include 'icons/association_unmark.jinja' %} {% include 'icons/association_cancel.jinja' %} diff --git a/maloja/web/jinja/artist.jinja b/maloja/web/jinja/artist.jinja index 168994f..fe0a35c 100644 --- a/maloja/web/jinja/artist.jinja +++ b/maloja/web/jinja/artist.jinja @@ -39,6 +39,7 @@
{% include 'icons/add_artist.jinja' %} + {% include 'icons/remove_artist.jinja' %} {% include 'icons/association_cancel.jinja' %}
diff --git a/maloja/web/jinja/icons/disassociate.jinja b/maloja/web/jinja/icons/disassociate.jinja new file mode 100644 index 0000000..46bccc0 --- /dev/null +++ b/maloja/web/jinja/icons/disassociate.jinja @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/maloja/web/jinja/icons/remove_album.jinja b/maloja/web/jinja/icons/remove_album.jinja new file mode 100644 index 0000000..60c79af --- /dev/null +++ b/maloja/web/jinja/icons/remove_album.jinja @@ -0,0 +1,6 @@ +
+ + + + +
diff --git a/maloja/web/jinja/icons/remove_artist.jinja b/maloja/web/jinja/icons/remove_artist.jinja new file mode 100644 index 0000000..3af031c --- /dev/null +++ b/maloja/web/jinja/icons/remove_artist.jinja @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/maloja/web/static/css/maloja.css b/maloja/web/static/css/maloja.css index 6d40ad4..0828875 100644 --- a/maloja/web/static/css/maloja.css +++ b/maloja/web/static/css/maloja.css @@ -162,7 +162,9 @@ we want icons to not be displayed in list rows, but show them with reduced opaci .mergeicons:not(.somethingmarked_for_merge) #mergeicon, /* can't merge when nothing is selected */ .mergeicons.marked_for_merge #mergeicon, /* cant merge into one of the things we have selected */ .associateicons:not(.sources_marked_for_associate) #associatealbumicon, -.associateicons:not(.sources_marked_for_associate) #associateartisticon /* nothing marked yet, can't associate with this */ +.associateicons:not(.sources_marked_for_associate) #associateartisticon, +.associateicons:not(.sources_marked_for_associate) #removealbumicon, +.associateicons:not(.sources_marked_for_associate) #removeartisticon /* nothing marked yet, can't associate with this */ { pointer-events: none; opacity:0.5; diff --git a/maloja/web/static/js/edit.js b/maloja/web/static/js/edit.js index 6caf52b..4e38f3e 100644 --- a/maloja/web/static/js/edit.js +++ b/maloja/web/static/js/edit.js @@ -453,6 +453,53 @@ function associate(element) { } +function removeAssociate(element) { + const parentElement = element.closest('[data-entity_id]'); + var entity_type = parentElement.dataset.entity_type; + var entity_id = parentElement.dataset.entity_id; + entity_id = parseInt(entity_id); + + var requests_todo = 0; + for (var target_entity_type of associate_sources[entity_type]) { + var key = "marked_for_associate_" + target_entity_type; + var current_stored = getStoredList(key); + + if (current_stored.length != 0) { + requests_todo += 1; + callback_func = function(req){ + if (req.status == 200) { + + toggleAssociationIcons(parentElement); + notifyCallback(req); + requests_todo -= 1; + if (requests_todo == 0) { + setTimeout(window.location.reload.bind(window.location),1000); + } + + } + else { + notifyCallback(req); + } + }; + + neo.xhttpreq( + "/apis/mlj_1/associate_" + target_entity_type + "s_to_" + entity_type, + data={ + 'source_ids':current_stored, + 'target_id':entity_id, + 'remove': true + }, + method="POST", + callback=callback_func, + json=true + ); + + storeList(key,[]); + } + + } +} + function cancelMerge(element) { const parentElement = element.closest('[data-entity_id]');