Merge branch 'qa-017-round1' into 'develop'
Qa 017 round1 See merge request funkwhale/funkwhale!421
This commit is contained in:
commit
68540cf4ab
|
@ -1,4 +1,3 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from funkwhale_api.activity import serializers as activity_serializers
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
from dynamic_preferences import types
|
||||
from dynamic_preferences.registries import global_preferences_registry
|
||||
|
||||
|
|
|
@ -38,6 +38,23 @@ 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]
|
||||
)
|
||||
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)
|
||||
if include:
|
||||
return self.filter(playlist_tracks__in=plts)
|
||||
else:
|
||||
return self.exclude(playlist_tracks__in=plts)
|
||||
|
||||
|
||||
class Playlist(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
|
@ -139,6 +156,23 @@ class PlaylistTrackQuerySet(models.QuerySet):
|
|||
)
|
||||
)
|
||||
|
||||
def annotate_playable_by_actor(self, actor):
|
||||
tracks = (
|
||||
music_models.Track.objects.playable_by(actor)
|
||||
.filter(pk=models.OuterRef("track"))
|
||||
.order_by("id")
|
||||
.values("id")[:1]
|
||||
)
|
||||
subquery = models.Subquery(tracks)
|
||||
return self.annotate(is_playable_by_actor=subquery)
|
||||
|
||||
def playable_by(self, actor, include=True):
|
||||
tracks = music_models.Track.objects.playable_by(actor, include)
|
||||
if include:
|
||||
return self.filter(track__pk__in=tracks)
|
||||
else:
|
||||
return self.exclude(track__pk__in=tracks)
|
||||
|
||||
|
||||
class PlaylistTrack(models.Model):
|
||||
track = models.ForeignKey(
|
||||
|
|
|
@ -11,10 +11,17 @@ from . import models
|
|||
|
||||
class PlaylistTrackSerializer(serializers.ModelSerializer):
|
||||
track = TrackSerializer()
|
||||
is_playable = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = models.PlaylistTrack
|
||||
fields = ("id", "track", "playlist", "index", "creation_date")
|
||||
fields = ("id", "track", "playlist", "index", "creation_date", "is_playable")
|
||||
|
||||
def get_is_playable(self, obj):
|
||||
try:
|
||||
return bool(obj.is_playable_by_actor)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
class PlaylistTrackWriteSerializer(serializers.ModelSerializer):
|
||||
|
@ -68,6 +75,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
|
|||
duration = serializers.SerializerMethodField(read_only=True)
|
||||
album_covers = serializers.SerializerMethodField(read_only=True)
|
||||
user = UserBasicSerializer(read_only=True)
|
||||
is_playable = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = models.Playlist
|
||||
|
@ -81,9 +89,16 @@ class PlaylistSerializer(serializers.ModelSerializer):
|
|||
"tracks_count",
|
||||
"album_covers",
|
||||
"duration",
|
||||
"is_playable",
|
||||
)
|
||||
read_only_fields = ["id", "modification_date", "creation_date"]
|
||||
|
||||
def get_is_playable(self, obj):
|
||||
try:
|
||||
return bool(obj.is_playable_by_actor)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def get_tracks_count(self, obj):
|
||||
try:
|
||||
return obj.tracks_count
|
||||
|
|
|
@ -6,7 +6,7 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
|||
from rest_framework.response import Response
|
||||
|
||||
from funkwhale_api.common import fields, permissions
|
||||
|
||||
from funkwhale_api.music import utils as music_utils
|
||||
from . import filters, models, serializers
|
||||
|
||||
|
||||
|
@ -74,7 +74,9 @@ class PlaylistViewSet(
|
|||
return Response(status=204)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.filter(fields.privacy_level_query(self.request.user))
|
||||
return self.queryset.filter(
|
||||
fields.privacy_level_query(self.request.user)
|
||||
).annotate_playable_by_actor(music_utils.get_actor_from_request(self.request))
|
||||
|
||||
def perform_create(self, serializer):
|
||||
return serializer.save(
|
||||
|
@ -116,7 +118,7 @@ class PlaylistTrackViewSet(
|
|||
lookup_field="playlist__privacy_level",
|
||||
user_field="playlist__user",
|
||||
)
|
||||
)
|
||||
).annotate_playable_by_actor(music_utils.get_actor_from_request(self.request))
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
instance.delete(update_indexes=True)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
from django.db import models
|
||||
from rest_framework import serializers
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import pytest
|
||||
import uuid
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import funkwhale_api
|
||||
from funkwhale_api.instance import nodeinfo
|
||||
|
||||
|
|
|
@ -122,3 +122,38 @@ def test_insert_many_honor_max_tracks(preferences, factories):
|
|||
track = factories["music.Track"]()
|
||||
with pytest.raises(exceptions.ValidationError):
|
||||
playlist.insert_many([track, track, track])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"privacy_level,expected", [("me", False), ("instance", False), ("everyone", True)]
|
||||
)
|
||||
def test_playlist_track_playable_by_anonymous(privacy_level, expected, factories):
|
||||
plt = factories["playlists.PlaylistTrack"]()
|
||||
track = plt.track
|
||||
factories["music.Upload"](
|
||||
track=track, library__privacy_level=privacy_level, import_status="finished"
|
||||
)
|
||||
queryset = plt.__class__.objects.playable_by(None).annotate_playable_by_actor(None)
|
||||
match = plt in list(queryset)
|
||||
assert match is expected
|
||||
if expected:
|
||||
assert bool(queryset.first().is_playable_by_actor) is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"privacy_level,expected", [("me", False), ("instance", False), ("everyone", True)]
|
||||
)
|
||||
def test_playlist_playable_by_anonymous(privacy_level, expected, factories):
|
||||
plt = factories["playlists.PlaylistTrack"]()
|
||||
playlist = plt.playlist
|
||||
track = plt.track
|
||||
factories["music.Upload"](
|
||||
track=track, library__privacy_level=privacy_level, import_status="finished"
|
||||
)
|
||||
queryset = playlist.__class__.objects.playable_by(None).annotate_playable_by_actor(
|
||||
None
|
||||
)
|
||||
match = playlist in list(queryset)
|
||||
assert match is expected
|
||||
if expected:
|
||||
assert bool(queryset.first().is_playable_by_actor) is expected
|
||||
|
|
|
@ -25,6 +25,16 @@ def test_serializer_includes_tracks_count(factories, logged_in_api_client):
|
|||
assert response.data["tracks_count"] == 1
|
||||
|
||||
|
||||
def test_serializer_includes_is_playable(factories, logged_in_api_client):
|
||||
playlist = factories["playlists.Playlist"]()
|
||||
factories["playlists.PlaylistTrack"](playlist=playlist)
|
||||
|
||||
url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
|
||||
response = logged_in_api_client.get(url)
|
||||
|
||||
assert response.data["is_playable"] is False
|
||||
|
||||
|
||||
def test_playlist_inherits_user_privacy(logged_in_api_client):
|
||||
url = reverse("api:v1:playlists-list")
|
||||
user = logged_in_api_client.user
|
||||
|
|
|
@ -13,14 +13,6 @@
|
|||
<router-link class="ui item" to="/library/playlists" exact>
|
||||
<translate>Playlists</translate>
|
||||
</router-link>
|
||||
<div class="ui secondary right menu">
|
||||
<router-link v-if="showImports" class="ui item" to="/library/import/launch" exact>
|
||||
<translate>Import</translate>
|
||||
</router-link>
|
||||
<router-link v-if="showImports" class="ui item" to="/library/import/batches">
|
||||
<translate>Import batches</translate>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<router-view :key="$route.fullPath"></router-view>
|
||||
</div>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<div class="content">
|
||||
<div class="header">
|
||||
<div class="right floated">
|
||||
<play-button :icon-only="true" class="ui inline" :button-classes="['ui', 'circular', 'large', {orange: playlist.tracks_count > 0}, 'icon', 'button', {disabled: playlist.tracks_count === 0}]" :playlist="playlist"></play-button>
|
||||
<play-button class="basic inline icon" :dropdown-only="true" :dropdown-icon-classes="['ellipsis', 'vertical', 'large', {disabled: playlist.tracks_count === 0}, 'grey']" :playlist="playlist"></play-button>
|
||||
<play-button :is-playable="playlist.is_playable" :icon-only="true" class="ui inline" :button-classes="['ui', 'circular', 'large', {orange: playlist.tracks_count > 0}, 'icon', 'button', {disabled: playlist.tracks_count === 0}]" :playlist="playlist"></play-button>
|
||||
<play-button :is-playable="playlist.is_playable" class="basic inline icon" :dropdown-only="true" :dropdown-icon-classes="['ellipsis', 'vertical', 'large', {disabled: playlist.tracks_count === 0}, 'grey']" :playlist="playlist"></play-button>
|
||||
</div>
|
||||
<router-link :title="playlist.name" class="discrete link" :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">
|
||||
{{ playlist.name | truncate(30) }}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="ui text loader"><translate>Loading usage data...</translate></div>
|
||||
</div>
|
||||
<div :class="['ui', {'success': progress < 60}, {'yellow': progress >= 60 && progress < 96}, {'error': progress >= 95}, 'progress']">
|
||||
<div class="bar">
|
||||
<div class="bar" :style="{width: `${progress}%`}">
|
||||
<div class="progress">{{ progress }}%</div>
|
||||
</div>
|
||||
<div class="label" v-if="quotaStatus">
|
||||
|
@ -98,7 +98,6 @@
|
|||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
import {humanSize} from '@/filters'
|
||||
import {compileTokens} from '@/search'
|
||||
|
||||
|
@ -145,12 +144,6 @@ export default {
|
|||
purgeErroredFiles () {
|
||||
this.purge('errored')
|
||||
},
|
||||
updateProgressBar () {
|
||||
$(this.$el).find('.ui.progress').progress({
|
||||
percent: this.progress,
|
||||
showActivity: false
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
progress () {
|
||||
|
@ -159,11 +152,6 @@ export default {
|
|||
}
|
||||
return Math.min(parseInt(this.quotaStatus.current * 100 / this.quotaStatus.max), 100)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
progress () {
|
||||
this.updateProgressBar()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue