diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py index 6fd95f34821d4c104561bcd72f798e74350ad6e5..5a01772ee92aa4ea9c12b14daeba178add5d8ac9 100644 --- a/api/funkwhale_api/music/serializers.py +++ b/api/funkwhale_api/music/serializers.py @@ -67,10 +67,24 @@ class ArtistAlbumSerializer(serializers.ModelSerializer): class ArtistWithAlbumsSerializer(serializers.ModelSerializer): albums = ArtistAlbumSerializer(many=True, read_only=True) + tags = serializers.SerializerMethodField() class Meta: model = models.Artist - fields = ("id", "fid", "mbid", "name", "creation_date", "albums", "is_local") + fields = ( + "id", + "fid", + "mbid", + "name", + "creation_date", + "albums", + "is_local", + "tags", + ) + + def get_tags(self, obj): + tagged_items = getattr(obj, "_prefetched_tagged_items", []) + return [ti.tag.name for ti in tagged_items] class ArtistSimpleSerializer(serializers.ModelSerializer): @@ -124,6 +138,7 @@ class AlbumSerializer(serializers.ModelSerializer): artist = ArtistSimpleSerializer(read_only=True) cover = cover_field is_playable = serializers.SerializerMethodField() + tags = serializers.SerializerMethodField() class Meta: model = models.Album @@ -139,6 +154,7 @@ class AlbumSerializer(serializers.ModelSerializer): "creation_date", "is_playable", "is_local", + "tags", ) def get_tracks(self, o): @@ -153,6 +169,10 @@ class AlbumSerializer(serializers.ModelSerializer): except AttributeError: return None + def get_tags(self, obj): + tagged_items = getattr(obj, "_prefetched_tagged_items", []) + return [ti.tag.name for ti in tagged_items] + class TrackAlbumSerializer(serializers.ModelSerializer): artist = ArtistSimpleSerializer(read_only=True) @@ -192,6 +212,7 @@ class TrackSerializer(serializers.ModelSerializer): album = TrackAlbumSerializer(read_only=True) uploads = serializers.SerializerMethodField() listen_url = serializers.SerializerMethodField() + tags = serializers.SerializerMethodField() class Meta: model = models.Track @@ -210,6 +231,7 @@ class TrackSerializer(serializers.ModelSerializer): "copyright", "license", "is_local", + "tags", ) def get_listen_url(self, obj): @@ -219,6 +241,10 @@ class TrackSerializer(serializers.ModelSerializer): uploads = getattr(obj, "playable_uploads", []) return TrackUploadSerializer(uploads, many=True).data + def get_tags(self, obj): + tagged_items = getattr(obj, "_prefetched_tagged_items", []) + return [ti.tag.name for ti in tagged_items] + @common_serializers.track_fields_for_update("name", "description", "privacy_level") class LibraryForOwnerSerializer(serializers.ModelSerializer): diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 5b34c3ce2b3cd1faf67c1cd9a8867e28c6bfff6d..bc405571dde08e1851954babfaea24e6a0b441fb 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -23,13 +23,19 @@ from funkwhale_api.federation import actors from funkwhale_api.federation import api_serializers as federation_api_serializers from funkwhale_api.federation import decorators as federation_decorators from funkwhale_api.federation import routes -from funkwhale_api.tags.models import Tag +from funkwhale_api.tags.models import Tag, TaggedItem from funkwhale_api.users.oauth import permissions as oauth_permissions from . import filters, licenses, models, serializers, tasks, utils logger = logging.getLogger(__name__) +TAG_PREFETCH = Prefetch( + "tagged_items", + queryset=TaggedItem.objects.all().select_related().order_by("tag__name"), + to_attr="_prefetched_tagged_items", +) + def get_libraries(filter_uploads): def libraries(self, request, *args, **kwargs): @@ -71,7 +77,9 @@ class ArtistViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelV albums = albums.annotate_playable_by_actor( utils.get_actor_from_request(self.request) ) - return queryset.prefetch_related(Prefetch("albums", queryset=albums)) + return queryset.prefetch_related( + Prefetch("albums", queryset=albums), TAG_PREFETCH + ) libraries = action(methods=["get"], detail=True)( get_libraries( @@ -103,7 +111,9 @@ class AlbumViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelVi .with_playable_uploads(utils.get_actor_from_request(self.request)) .order_for_album() ) - qs = queryset.prefetch_related(Prefetch("tracks", queryset=tracks)) + qs = queryset.prefetch_related( + Prefetch("tracks", queryset=tracks), TAG_PREFETCH + ) return qs libraries = action(methods=["get"], detail=True)( @@ -206,7 +216,7 @@ class TrackViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelVi queryset = queryset.with_playable_uploads( utils.get_actor_from_request(self.request) ) - return queryset + return queryset.prefetch_related(TAG_PREFETCH) libraries = action(methods=["get"], detail=True)( get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track=o)) diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py index 5f9b7d9d151035a78396ac3e4e3d91d30884c298..af33e05ed026a2423dcc816fee8efa575198761e 100644 --- a/api/tests/music/test_serializers.py +++ b/api/tests/music/test_serializers.py @@ -69,6 +69,7 @@ def test_artist_with_albums_serializer(factories, to_api_date): "is_local": artist.is_local, "creation_date": to_api_date(artist.creation_date), "albums": [serializers.ArtistAlbumSerializer(album).data], + "tags": [], } serializer = serializers.ArtistWithAlbumsSerializer(artist) assert serializer.data == expected @@ -175,6 +176,7 @@ def test_album_serializer(factories, to_api_date): "release_date": to_api_date(album.release_date), "tracks": serializers.AlbumTrackSerializer([track2, track1], many=True).data, "is_local": album.is_local, + "tags": [], } serializer = serializers.AlbumSerializer(album) @@ -202,6 +204,7 @@ def test_track_serializer(factories, to_api_date): "license": upload.track.license.code, "copyright": upload.track.copyright, "is_local": upload.track.is_local, + "tags": [], } serializer = serializers.TrackSerializer(track) assert serializer.data == expected diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 25845e738b228b1b1e969c04070f2de134d454eb..a5ef808f79bbffa4500e4156386270b72383f582 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -16,8 +16,11 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__)) def test_artist_list_serializer(api_request, factories, logged_in_api_client): + tags = ["tag1", "tag2"] track = factories["music.Upload"]( - library__privacy_level="everyone", import_status="finished" + library__privacy_level="everyone", + import_status="finished", + track__album__artist__set_tags=tags, ).track artist = track.artist request = api_request.get("/") @@ -27,8 +30,10 @@ def test_artist_list_serializer(api_request, factories, logged_in_api_client): ) expected = {"count": 1, "next": None, "previous": None, "results": serializer.data} for artist in serializer.data: + artist["tags"] = tags for album in artist["albums"]: album["is_playable"] = True + url = reverse("api:v1:artists-list") response = logged_in_api_client.get(url) @@ -37,8 +42,11 @@ def test_artist_list_serializer(api_request, factories, logged_in_api_client): def test_album_list_serializer(api_request, factories, logged_in_api_client): + tags = ["tag1", "tag2"] track = factories["music.Upload"]( - library__privacy_level="everyone", import_status="finished" + library__privacy_level="everyone", + import_status="finished", + track__album__set_tags=tags, ).track album = track.album request = api_request.get("/") @@ -47,6 +55,8 @@ def test_album_list_serializer(api_request, factories, logged_in_api_client): qs, many=True, context={"request": request} ) expected = {"count": 1, "next": None, "previous": None, "results": serializer.data} + for album in serializer.data: + album["tags"] = tags url = reverse("api:v1:albums-list") response = logged_in_api_client.get(url) @@ -55,8 +65,11 @@ def test_album_list_serializer(api_request, factories, logged_in_api_client): def test_track_list_serializer(api_request, factories, logged_in_api_client): + tags = ["tag1", "tag2"] track = factories["music.Upload"]( - library__privacy_level="everyone", import_status="finished" + library__privacy_level="everyone", + import_status="finished", + track__set_tags=tags, ).track request = api_request.get("/") qs = track.__class__.objects.with_playable_uploads(None) @@ -64,6 +77,8 @@ def test_track_list_serializer(api_request, factories, logged_in_api_client): qs, many=True, context={"request": request} ) expected = {"count": 1, "next": None, "previous": None, "results": serializer.data} + for track in serializer.data: + track["tags"] = tags url = reverse("api:v1:tracks-list") response = logged_in_api_client.get(url)