diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 5c92ad2b1..871dfc920 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -3,7 +3,7 @@ import urllib from django.conf import settings from django.db import transaction -from django.db.models import Count, Prefetch, Sum, F +from django.db.models import Count, Prefetch, Sum, F, Q from django.db.models.functions import Length from django.utils import timezone @@ -26,6 +26,28 @@ from . import filters, models, serializers, tasks, utils logger = logging.getLogger(__name__) +def get_libraries(filter_uploads): + def view(self, request, *args, **kwargs): + obj = self.get_object() + actor = utils.get_actor_from_request(request) + uploads = models.Upload.objects.all() + uploads = filter_uploads(obj, uploads) + uploads = uploads.playable_by(actor) + libraries = models.Library.objects.filter( + pk__in=uploads.values_list("library", flat=True) + ) + libraries = libraries.select_related("actor") + page = self.paginate_queryset(libraries) + if page is not None: + serializer = federation_api_serializers.LibrarySerializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = federation_api_serializers.LibrarySerializer(libraries, many=True) + return Response(serializer.data) + + return view + + class TagViewSetMixin(object): def get_queryset(self): queryset = super().get_queryset() @@ -50,6 +72,14 @@ class ArtistViewSet(viewsets.ReadOnlyModelViewSet): ) return queryset.prefetch_related(Prefetch("albums", queryset=albums)).distinct() + libraries = detail_route(methods=["get"])( + get_libraries( + filter_uploads=lambda o, uploads: uploads.filter( + Q(track__artist=o) | Q(track__album__artist=o) + ) + ) + ) + class AlbumViewSet(viewsets.ReadOnlyModelViewSet): queryset = ( @@ -76,6 +106,10 @@ class AlbumViewSet(viewsets.ReadOnlyModelViewSet): qs = queryset.prefetch_related(Prefetch("tracks", queryset=tracks)) return qs.distinct() + libraries = detail_route(methods=["get"])( + get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track__album=o)) + ) + class LibraryViewSet( mixins.CreateModelMixin, @@ -197,6 +231,10 @@ class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet): serializer = serializers.LyricsSerializer(lyrics) return Response(serializer.data) + libraries = detail_route(methods=["get"])( + get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track=o)) + ) + def get_file_path(audio_file): serve_path = settings.MUSIC_DIRECTORY_SERVE_PATH diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 691fa049e..2f9d66e5b 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -449,3 +449,34 @@ def test_user_can_list_own_library_follows(factories, logged_in_api_client): "previous": None, "results": [federation_api_serializers.LibraryFollowSerializer(follow).data], } + + +@pytest.mark.parametrize("entity", ["artist", "album", "track"]) +def test_can_get_libraries_for_music_entities( + factories, api_client, entity, preferences +): + preferences["common__api_authentication_required"] = False + upload = factories["music.Upload"](playable=True) + # another private library that should not appear + factories["music.Upload"]( + import_status="finished", library__privacy_level="me", track=upload.track + ).library + library = upload.library + data = { + "artist": upload.track.artist, + "album": upload.track.album, + "track": upload.track, + } + + url = reverse("api:v1:{}s-libraries".format(entity), kwargs={"pk": data[entity].pk}) + + response = api_client.get(url) + expected = federation_api_serializers.LibrarySerializer(library).data + + assert response.status_code == 200 + assert response.data == { + "count": 1, + "next": None, + "previous": None, + "results": [expected], + } diff --git a/changes/changelog.d/551.enhancement b/changes/changelog.d/551.enhancement new file mode 100644 index 000000000..267dba9f7 --- /dev/null +++ b/changes/changelog.d/551.enhancement @@ -0,0 +1 @@ +Added a library widget to display libraries associated with a track, album and artist (#551) diff --git a/front/src/components/common/CopyInput.vue b/front/src/components/common/CopyInput.vue index c2db315bd..af82f2c66 100644 --- a/front/src/components/common/CopyInput.vue +++ b/front/src/components/common/CopyInput.vue @@ -4,7 +4,7 @@ Text copied to clipboard!

- @@ -12,7 +12,10 @@ diff --git a/front/src/components/library/Album.vue b/front/src/components/library/Album.vue index 7ee5ae068..03d83e064 100644 --- a/front/src/components/library/Album.vue +++ b/front/src/components/library/Album.vue @@ -45,6 +45,14 @@ +
+

+ User libraries +

+ + This album is present in the following libraries: + +
@@ -55,6 +63,7 @@ import logger from '@/logging' import backend from '@/audio/backend' import PlayButton from '@/components/audio/PlayButton' import TrackTable from '@/components/audio/track/Table' +import LibraryWidget from '@/components/federation/LibraryWidget' const FETCH_URL = 'albums/' @@ -62,7 +71,8 @@ export default { props: ['id'], components: { PlayButton, - TrackTable + TrackTable, + LibraryWidget }, data () { return { diff --git a/front/src/components/library/Artist.vue b/front/src/components/library/Artist.vue index ee9c625e1..fe74f6f17 100644 --- a/front/src/components/library/Artist.vue +++ b/front/src/components/library/Artist.vue @@ -56,6 +56,14 @@ +
+

+ User libraries +

+ + This artist is present in the following libraries: + +
@@ -69,6 +77,7 @@ import AlbumCard from '@/components/audio/album/Card' import RadioButton from '@/components/radios/Button' import PlayButton from '@/components/audio/PlayButton' import TrackTable from '@/components/audio/track/Table' +import LibraryWidget from '@/components/federation/LibraryWidget' export default { props: ['id'], @@ -76,7 +85,8 @@ export default { AlbumCard, RadioButton, PlayButton, - TrackTable + TrackTable, + LibraryWidget }, data () { return { diff --git a/front/src/components/library/Track.vue b/front/src/components/library/Track.vue index 1ede2218b..75d5f650d 100644 --- a/front/src/components/library/Track.vue +++ b/front/src/components/library/Track.vue @@ -118,6 +118,14 @@ +
+

+ User libraries +

+ + This track is present in the following libraries: + +
@@ -131,6 +139,7 @@ import logger from '@/logging' import PlayButton from '@/components/audio/PlayButton' import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon' import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon' +import LibraryWidget from '@/components/federation/LibraryWidget' const FETCH_URL = 'tracks/' @@ -139,7 +148,8 @@ export default { components: { PlayButton, TrackPlaylistIcon, - TrackFavoriteIcon + TrackFavoriteIcon, + LibraryWidget }, data () { return { diff --git a/front/src/views/content/remote/Card.vue b/front/src/views/content/remote/Card.vue index 862a9a69a..9cbaf857f 100644 --- a/front/src/views/content/remote/Card.vue +++ b/front/src/views/content/remote/Card.vue @@ -26,7 +26,7 @@ %{ count } tracks -
+