See #432: expose artists, album and track tags in API
This commit is contained in:
parent
102aa6f5ba
commit
aa6bece8df
|
@ -67,10 +67,24 @@ class ArtistAlbumSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class ArtistWithAlbumsSerializer(serializers.ModelSerializer):
|
class ArtistWithAlbumsSerializer(serializers.ModelSerializer):
|
||||||
albums = ArtistAlbumSerializer(many=True, read_only=True)
|
albums = ArtistAlbumSerializer(many=True, read_only=True)
|
||||||
|
tags = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Artist
|
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):
|
class ArtistSimpleSerializer(serializers.ModelSerializer):
|
||||||
|
@ -124,6 +138,7 @@ class AlbumSerializer(serializers.ModelSerializer):
|
||||||
artist = ArtistSimpleSerializer(read_only=True)
|
artist = ArtistSimpleSerializer(read_only=True)
|
||||||
cover = cover_field
|
cover = cover_field
|
||||||
is_playable = serializers.SerializerMethodField()
|
is_playable = serializers.SerializerMethodField()
|
||||||
|
tags = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Album
|
model = models.Album
|
||||||
|
@ -139,6 +154,7 @@ class AlbumSerializer(serializers.ModelSerializer):
|
||||||
"creation_date",
|
"creation_date",
|
||||||
"is_playable",
|
"is_playable",
|
||||||
"is_local",
|
"is_local",
|
||||||
|
"tags",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_tracks(self, o):
|
def get_tracks(self, o):
|
||||||
|
@ -153,6 +169,10 @@ class AlbumSerializer(serializers.ModelSerializer):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None
|
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):
|
class TrackAlbumSerializer(serializers.ModelSerializer):
|
||||||
artist = ArtistSimpleSerializer(read_only=True)
|
artist = ArtistSimpleSerializer(read_only=True)
|
||||||
|
@ -192,6 +212,7 @@ class TrackSerializer(serializers.ModelSerializer):
|
||||||
album = TrackAlbumSerializer(read_only=True)
|
album = TrackAlbumSerializer(read_only=True)
|
||||||
uploads = serializers.SerializerMethodField()
|
uploads = serializers.SerializerMethodField()
|
||||||
listen_url = serializers.SerializerMethodField()
|
listen_url = serializers.SerializerMethodField()
|
||||||
|
tags = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Track
|
model = models.Track
|
||||||
|
@ -210,6 +231,7 @@ class TrackSerializer(serializers.ModelSerializer):
|
||||||
"copyright",
|
"copyright",
|
||||||
"license",
|
"license",
|
||||||
"is_local",
|
"is_local",
|
||||||
|
"tags",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_listen_url(self, obj):
|
def get_listen_url(self, obj):
|
||||||
|
@ -219,6 +241,10 @@ class TrackSerializer(serializers.ModelSerializer):
|
||||||
uploads = getattr(obj, "playable_uploads", [])
|
uploads = getattr(obj, "playable_uploads", [])
|
||||||
return TrackUploadSerializer(uploads, many=True).data
|
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")
|
@common_serializers.track_fields_for_update("name", "description", "privacy_level")
|
||||||
class LibraryForOwnerSerializer(serializers.ModelSerializer):
|
class LibraryForOwnerSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -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 api_serializers as federation_api_serializers
|
||||||
from funkwhale_api.federation import decorators as federation_decorators
|
from funkwhale_api.federation import decorators as federation_decorators
|
||||||
from funkwhale_api.federation import routes
|
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 funkwhale_api.users.oauth import permissions as oauth_permissions
|
||||||
|
|
||||||
from . import filters, licenses, models, serializers, tasks, utils
|
from . import filters, licenses, models, serializers, tasks, utils
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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 get_libraries(filter_uploads):
|
||||||
def libraries(self, request, *args, **kwargs):
|
def libraries(self, request, *args, **kwargs):
|
||||||
|
@ -71,7 +77,9 @@ class ArtistViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelV
|
||||||
albums = albums.annotate_playable_by_actor(
|
albums = albums.annotate_playable_by_actor(
|
||||||
utils.get_actor_from_request(self.request)
|
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)(
|
libraries = action(methods=["get"], detail=True)(
|
||||||
get_libraries(
|
get_libraries(
|
||||||
|
@ -103,7 +111,9 @@ class AlbumViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelVi
|
||||||
.with_playable_uploads(utils.get_actor_from_request(self.request))
|
.with_playable_uploads(utils.get_actor_from_request(self.request))
|
||||||
.order_for_album()
|
.order_for_album()
|
||||||
)
|
)
|
||||||
qs = queryset.prefetch_related(Prefetch("tracks", queryset=tracks))
|
qs = queryset.prefetch_related(
|
||||||
|
Prefetch("tracks", queryset=tracks), TAG_PREFETCH
|
||||||
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
libraries = action(methods=["get"], detail=True)(
|
libraries = action(methods=["get"], detail=True)(
|
||||||
|
@ -206,7 +216,7 @@ class TrackViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelVi
|
||||||
queryset = queryset.with_playable_uploads(
|
queryset = queryset.with_playable_uploads(
|
||||||
utils.get_actor_from_request(self.request)
|
utils.get_actor_from_request(self.request)
|
||||||
)
|
)
|
||||||
return queryset
|
return queryset.prefetch_related(TAG_PREFETCH)
|
||||||
|
|
||||||
libraries = action(methods=["get"], detail=True)(
|
libraries = action(methods=["get"], detail=True)(
|
||||||
get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track=o))
|
get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track=o))
|
||||||
|
|
|
@ -69,6 +69,7 @@ def test_artist_with_albums_serializer(factories, to_api_date):
|
||||||
"is_local": artist.is_local,
|
"is_local": artist.is_local,
|
||||||
"creation_date": to_api_date(artist.creation_date),
|
"creation_date": to_api_date(artist.creation_date),
|
||||||
"albums": [serializers.ArtistAlbumSerializer(album).data],
|
"albums": [serializers.ArtistAlbumSerializer(album).data],
|
||||||
|
"tags": [],
|
||||||
}
|
}
|
||||||
serializer = serializers.ArtistWithAlbumsSerializer(artist)
|
serializer = serializers.ArtistWithAlbumsSerializer(artist)
|
||||||
assert serializer.data == expected
|
assert serializer.data == expected
|
||||||
|
@ -175,6 +176,7 @@ def test_album_serializer(factories, to_api_date):
|
||||||
"release_date": to_api_date(album.release_date),
|
"release_date": to_api_date(album.release_date),
|
||||||
"tracks": serializers.AlbumTrackSerializer([track2, track1], many=True).data,
|
"tracks": serializers.AlbumTrackSerializer([track2, track1], many=True).data,
|
||||||
"is_local": album.is_local,
|
"is_local": album.is_local,
|
||||||
|
"tags": [],
|
||||||
}
|
}
|
||||||
serializer = serializers.AlbumSerializer(album)
|
serializer = serializers.AlbumSerializer(album)
|
||||||
|
|
||||||
|
@ -202,6 +204,7 @@ def test_track_serializer(factories, to_api_date):
|
||||||
"license": upload.track.license.code,
|
"license": upload.track.license.code,
|
||||||
"copyright": upload.track.copyright,
|
"copyright": upload.track.copyright,
|
||||||
"is_local": upload.track.is_local,
|
"is_local": upload.track.is_local,
|
||||||
|
"tags": [],
|
||||||
}
|
}
|
||||||
serializer = serializers.TrackSerializer(track)
|
serializer = serializers.TrackSerializer(track)
|
||||||
assert serializer.data == expected
|
assert serializer.data == expected
|
||||||
|
|
|
@ -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):
|
def test_artist_list_serializer(api_request, factories, logged_in_api_client):
|
||||||
|
tags = ["tag1", "tag2"]
|
||||||
track = factories["music.Upload"](
|
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
|
).track
|
||||||
artist = track.artist
|
artist = track.artist
|
||||||
request = api_request.get("/")
|
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}
|
expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
|
||||||
for artist in serializer.data:
|
for artist in serializer.data:
|
||||||
|
artist["tags"] = tags
|
||||||
for album in artist["albums"]:
|
for album in artist["albums"]:
|
||||||
album["is_playable"] = True
|
album["is_playable"] = True
|
||||||
|
|
||||||
url = reverse("api:v1:artists-list")
|
url = reverse("api:v1:artists-list")
|
||||||
response = logged_in_api_client.get(url)
|
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):
|
def test_album_list_serializer(api_request, factories, logged_in_api_client):
|
||||||
|
tags = ["tag1", "tag2"]
|
||||||
track = factories["music.Upload"](
|
track = factories["music.Upload"](
|
||||||
library__privacy_level="everyone", import_status="finished"
|
library__privacy_level="everyone",
|
||||||
|
import_status="finished",
|
||||||
|
track__album__set_tags=tags,
|
||||||
).track
|
).track
|
||||||
album = track.album
|
album = track.album
|
||||||
request = api_request.get("/")
|
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}
|
qs, many=True, context={"request": request}
|
||||||
)
|
)
|
||||||
expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
|
expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
|
||||||
|
for album in serializer.data:
|
||||||
|
album["tags"] = tags
|
||||||
url = reverse("api:v1:albums-list")
|
url = reverse("api:v1:albums-list")
|
||||||
response = logged_in_api_client.get(url)
|
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):
|
def test_track_list_serializer(api_request, factories, logged_in_api_client):
|
||||||
|
tags = ["tag1", "tag2"]
|
||||||
track = factories["music.Upload"](
|
track = factories["music.Upload"](
|
||||||
library__privacy_level="everyone", import_status="finished"
|
library__privacy_level="everyone",
|
||||||
|
import_status="finished",
|
||||||
|
track__set_tags=tags,
|
||||||
).track
|
).track
|
||||||
request = api_request.get("/")
|
request = api_request.get("/")
|
||||||
qs = track.__class__.objects.with_playable_uploads(None)
|
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}
|
qs, many=True, context={"request": request}
|
||||||
)
|
)
|
||||||
expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
|
expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
|
||||||
|
for track in serializer.data:
|
||||||
|
track["tags"] = tags
|
||||||
url = reverse("api:v1:tracks-list")
|
url = reverse("api:v1:tracks-list")
|
||||||
response = logged_in_api_client.get(url)
|
response = logged_in_api_client.get(url)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue