From 921317a217dc6193e5b6235bdc45fb5c0eebb583 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Thu, 19 Sep 2019 21:09:18 +0200 Subject: [PATCH] Implemented missing getSongsByGenre subsonic endpoint --- api/funkwhale_api/music/factories.py | 21 +++++++++++++- api/funkwhale_api/subsonic/views.py | 42 ++++++++++++++++++++++++++++ api/tests/subsonic/test_views.py | 22 +++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py index 7551502e0..52e5020bb 100644 --- a/api/funkwhale_api/music/factories.py +++ b/api/funkwhale_api/music/factories.py @@ -108,7 +108,6 @@ class TrackFactory( title = factory.Faker("sentence", nb_words=3) mbid = factory.Faker("uuid4") album = factory.SubFactory(AlbumFactory) - artist = factory.SelfAttribute("album.artist") position = 1 playable = playable_factory("track") @@ -124,6 +123,26 @@ class TrackFactory( fid=factory.Faker("federation_url", local=True), album__local=True ) + @factory.post_generation + def artist(self, created, extracted, **kwargs): + """ + A bit intricated, because we want to be able to specify a different + track artist with a fallback on album artist if nothing is specified. + + And handle cases where build or build_batch are used (so no db calls) + """ + if extracted: + self.artist = extracted + elif kwargs: + if created: + self.artist = ArtistFactory(**kwargs) + else: + self.artist = ArtistFactory.build(**kwargs) + elif self.album: + self.artist = self.album.artist + if created: + self.save() + @factory.post_generation def license(self, created, extracted, **kwargs): if not created: diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py index e206555ab..6633224df 100644 --- a/api/funkwhale_api/subsonic/views.py +++ b/api/funkwhale_api/subsonic/views.py @@ -333,6 +333,48 @@ class SubsonicViewSet(viewsets.GenericViewSet): } return response.Response(data) + @action( + detail=False, + methods=["get", "post"], + url_name="get_songs_by_genre", + url_path="getSongsByGenre", + ) + def get_songs_by_genre(self, request, *args, **kwargs): + data = request.GET or request.POST + actor = utils.get_actor_from_request(request) + queryset = music_models.Track.objects.all().exclude( + moderation_filters.get_filtered_content_query( + moderation_filters.USER_FILTER_CONFIG["TRACK"], request.user + ) + ) + queryset = queryset.playable_by(actor) + try: + size = int( + data["count"] + ) # yep. Some endpoints have size, other have count… + except (TypeError, KeyError, ValueError): + size = 50 + + genre = data.get("genre") + queryset = ( + queryset.playable_by(actor) + .filter( + Q(tagged_items__tag__name=genre) + | Q(artist__tagged_items__tag__name=genre) + | Q(album__artist__tagged_items__tag__name=genre) + | Q(album__tagged_items__tag__name=genre) + ) + .prefetch_related("uploads") + .distinct() + .order_by("-creation_date")[:size] + ) + data = { + "songsByGenre": { + "song": serializers.GetSongSerializer(queryset, many=True).data + } + } + return response.Response(data) + @action( detail=False, methods=["get", "post"], diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py index 361a46a73..298ad34f7 100644 --- a/api/tests/subsonic/test_views.py +++ b/api/tests/subsonic/test_views.py @@ -469,6 +469,28 @@ def test_get_album_list2_by_genre(f, db, logged_in_api_client, factories): } +@pytest.mark.parametrize("f", ["json"]) +@pytest.mark.parametrize( + "tags_field", + ["set_tags", "artist__set_tags", "album__set_tags", "album__artist__set_tags"], +) +def test_get_songs_by_genre(f, tags_field, db, logged_in_api_client, factories): + url = reverse("api:subsonic-get_songs_by_genre") + assert url.endswith("getSongsByGenre") is True + track1 = factories["music.Track"](playable=True, **{tags_field: ["Rock"]}) + track2 = factories["music.Track"](playable=True, **{tags_field: ["Rock"]}) + factories["music.Track"](playable=True, **{tags_field: ["Pop"]}) + expected = { + "songsByGenre": {"song": serializers.get_song_list_data([track2, track1])} + } + + response = logged_in_api_client.get( + url, {"f": f, "count": 5, "offset": 0, "genre": "rock"} + ) + assert response.status_code == 200 + assert response.data == expected + + @pytest.mark.parametrize("f", ["json"]) def test_search3(f, db, logged_in_api_client, factories): url = reverse("api:subsonic-search3") -- GitLab