See #272: updated API to return upload data on tracks

This commit is contained in:
Eliot Berriot 2018-10-26 14:18:50 +02:00
parent 8489c79c89
commit d3f8fb6cb0
No known key found for this signature in database
GPG key ID: DD6965E2476E5C27
16 changed files with 97 additions and 142 deletions

View file

@ -51,7 +51,7 @@ class TrackFavoriteViewSet(
queryset = queryset.filter(
fields.privacy_level_query(self.request.user, "user__privacy_level")
)
tracks = Track.objects.annotate_playable_by_actor(
tracks = Track.objects.with_playable_uploads(
music_utils.get_actor_from_request(self.request)
).select_related("artist", "album__artist")
queryset = queryset.prefetch_related(Prefetch("track", queryset=tracks))

View file

@ -41,7 +41,7 @@ class ListeningViewSet(
queryset = queryset.filter(
fields.privacy_level_query(self.request.user, "user__privacy_level")
)
tracks = Track.objects.annotate_playable_by_actor(
tracks = Track.objects.with_playable_uploads(
music_utils.get_actor_from_request(self.request)
).select_related("artist", "album__artist")
return queryset.prefetch_related(Prefetch("track", queryset=tracks))

View file

@ -124,8 +124,8 @@ class ArtistQuerySet(models.QuerySet):
def annotate_playable_by_actor(self, actor):
tracks = (
Track.objects.playable_by(actor)
.filter(artist=models.OuterRef("id"))
Upload.objects.playable_by(actor)
.filter(track__artist=models.OuterRef("id"))
.order_by("id")
.values("id")[:1]
)
@ -192,8 +192,8 @@ class AlbumQuerySet(models.QuerySet):
def annotate_playable_by_actor(self, actor):
tracks = (
Track.objects.playable_by(actor)
.filter(album=models.OuterRef("id"))
Upload.objects.playable_by(actor)
.filter(track__album=models.OuterRef("id"))
.order_by("id")
.values("id")[:1]
)
@ -207,6 +207,15 @@ class AlbumQuerySet(models.QuerySet):
else:
return self.exclude(tracks__in=tracks).distinct()
def with_prefetched_tracks_and_playable_uploads(self, actor):
tracks = Track.objects.with_playable_uploads(actor)
return self.prefetch_related(
models.Prefetch(
'tracks',
queryset=tracks,
)
)
class Album(APIModelMixin):
title = models.CharField(max_length=255)
@ -403,18 +412,14 @@ class TrackQuerySet(models.QuerySet):
else:
return self.exclude(uploads__in=files).distinct()
def annotate_duration(self):
first_upload = Upload.objects.filter(track=models.OuterRef("pk")).order_by("pk")
return self.annotate(
duration=models.Subquery(first_upload.values("duration")[:1])
)
def annotate_file_data(self):
first_upload = Upload.objects.filter(track=models.OuterRef("pk")).order_by("pk")
return self.annotate(
bitrate=models.Subquery(first_upload.values("bitrate")[:1]),
size=models.Subquery(first_upload.values("size")[:1]),
mimetype=models.Subquery(first_upload.values("mimetype")[:1]),
def with_playable_uploads(self, actor):
uploads = Upload.objects.playable_by(actor).select_related('track')
return self.prefetch_related(
models.Prefetch(
'uploads',
queryset=uploads,
to_attr='playable_uploads'
)
)

View file

@ -59,7 +59,7 @@ class ArtistSimpleSerializer(serializers.ModelSerializer):
class AlbumTrackSerializer(serializers.ModelSerializer):
artist = ArtistSimpleSerializer(read_only=True)
is_playable = serializers.SerializerMethodField()
uploads = serializers.SerializerMethodField()
listen_url = serializers.SerializerMethodField()
duration = serializers.SerializerMethodField()
@ -73,16 +73,14 @@ class AlbumTrackSerializer(serializers.ModelSerializer):
"artist",
"creation_date",
"position",
"is_playable",
"uploads",
"listen_url",
"duration",
)
def get_is_playable(self, obj):
try:
return bool(obj.is_playable_by_actor)
except AttributeError:
return None
def get_uploads(self, obj):
uploads = getattr(obj, "playable_uploads", [])
return TrackUploadSerializer(uploads, many=True).data
def get_listen_url(self, obj):
return obj.listen_url
@ -123,7 +121,9 @@ class AlbumSerializer(serializers.ModelSerializer):
def get_is_playable(self, obj):
try:
return any([bool(t.is_playable_by_actor) for t in obj.tracks.all()])
return any(
[bool(getattr(t, "playable_uploads", [])) for t in obj.tracks.all()]
)
except AttributeError:
return None
@ -145,16 +145,26 @@ class TrackAlbumSerializer(serializers.ModelSerializer):
)
class TrackUploadSerializer(serializers.ModelSerializer):
class Meta:
model = models.Upload
fields = (
"uuid",
"listen_url",
"size",
"duration",
"bitrate",
"mimetype",
"extension",
)
class TrackSerializer(serializers.ModelSerializer):
artist = ArtistSimpleSerializer(read_only=True)
album = TrackAlbumSerializer(read_only=True)
lyrics = serializers.SerializerMethodField()
is_playable = serializers.SerializerMethodField()
uploads = serializers.SerializerMethodField()
listen_url = serializers.SerializerMethodField()
duration = serializers.SerializerMethodField()
bitrate = serializers.SerializerMethodField()
size = serializers.SerializerMethodField()
mimetype = serializers.SerializerMethodField()
class Meta:
model = models.Track
@ -167,12 +177,8 @@ class TrackSerializer(serializers.ModelSerializer):
"creation_date",
"position",
"lyrics",
"is_playable",
"uploads",
"listen_url",
"duration",
"bitrate",
"size",
"mimetype",
)
def get_lyrics(self, obj):
@ -181,35 +187,9 @@ class TrackSerializer(serializers.ModelSerializer):
def get_listen_url(self, obj):
return obj.listen_url
def get_is_playable(self, obj):
try:
return bool(obj.is_playable_by_actor)
except AttributeError:
return None
def get_duration(self, obj):
try:
return obj.duration
except AttributeError:
return None
def get_bitrate(self, obj):
try:
return obj.bitrate
except AttributeError:
return None
def get_size(self, obj):
try:
return obj.size
except AttributeError:
return None
def get_mimetype(self, obj):
try:
return obj.mimetype
except AttributeError:
return None
def get_uploads(self, obj):
uploads = getattr(obj, "playable_uploads", [])
return TrackUploadSerializer(uploads, many=True).data
class LibraryForOwnerSerializer(serializers.ModelSerializer):

View file

@ -5,7 +5,6 @@ import mutagen
import pydub
from funkwhale_api.common.search import normalize_query, get_query # noqa
from funkwhale_api.common import utils
def guess_mimetype(f):

View file

@ -93,17 +93,9 @@ class AlbumViewSet(viewsets.ReadOnlyModelViewSet):
def get_queryset(self):
queryset = super().get_queryset()
tracks = models.Track.objects.annotate_playable_by_actor(
tracks = models.Track.objects.select_related("artist").with_playable_uploads(
utils.get_actor_from_request(self.request)
).select_related("artist")
if (
hasattr(self, "kwargs")
and self.kwargs
and self.request.method.lower() == "get"
):
# we are detailing a single album, so we can add the overhead
# to fetch additional data
tracks = tracks.annotate_duration()
)
qs = queryset.prefetch_related(Prefetch("tracks", queryset=tracks))
return qs.distinct()
@ -194,18 +186,10 @@ class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet):
if user.is_authenticated and filter_favorites == "true":
queryset = queryset.filter(track_favorites__user=user)
queryset = queryset.annotate_playable_by_actor(
queryset = queryset.with_playable_uploads(
utils.get_actor_from_request(self.request)
).annotate_duration()
if (
hasattr(self, "kwargs")
and self.kwargs
and self.request.method.lower() == "get"
):
# we are detailing a single track, so we can add the overhead
# to fetch additional data
queryset = queryset.annotate_file_data()
return queryset.distinct()
)
return queryset
@detail_route(methods=["get"])
@transaction.non_atomic_requests

View file

@ -38,15 +38,14 @@ class PlaylistQuerySet(models.QuerySet):
)
return self.prefetch_related(plt_prefetch)
def annotate_playable_by_actor(self, actor):
plts = (
PlaylistTrack.objects.playable_by(actor)
.filter(playlist=models.OuterRef("id"))
.order_by("id")
.values("id")[:1]
def with_playable_plts(self, actor):
return self.prefetch_related(
models.Prefetch(
"playlist_tracks",
queryset=PlaylistTrack.objects.playable_by(actor),
to_attr="playable_plts",
)
)
subquery = models.Subquery(plts)
return self.annotate(is_playable_by_actor=subquery)
def playable_by(self, actor, include=True):
plts = PlaylistTrack.objects.playable_by(actor, include)
@ -148,7 +147,7 @@ class Playlist(models.Model):
class PlaylistTrackQuerySet(models.QuerySet):
def for_nested_serialization(self, actor=None):
tracks = music_models.Track.objects.annotate_playable_by_actor(actor)
tracks = music_models.Track.objects.with_playable_uploads(actor)
tracks = tracks.select_related("artist", "album__artist")
return self.prefetch_related(
models.Prefetch("track", queryset=tracks, to_attr="_prefetched_track")
@ -156,8 +155,8 @@ class PlaylistTrackQuerySet(models.QuerySet):
def annotate_playable_by_actor(self, actor):
tracks = (
music_models.Track.objects.playable_by(actor)
.filter(pk=models.OuterRef("track"))
music_models.Upload.objects.playable_by(actor)
.filter(track__pk=models.OuterRef("track"))
.order_by("id")
.values("id")[:1]
)

View file

@ -93,7 +93,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
def get_is_playable(self, obj):
try:
return bool(obj.is_playable_by_actor)
return bool(obj.playable_plts)
except AttributeError:
return None

View file

@ -78,7 +78,7 @@ class PlaylistViewSet(
def get_queryset(self):
return self.queryset.filter(
fields.privacy_level_query(self.request.user)
).annotate_playable_by_actor(music_utils.get_actor_from_request(self.request))
).with_playable_plts(music_utils.get_actor_from_request(self.request))
def perform_create(self, serializer):
return serializer.save(