Merge branch 'qa-017-round1' into 'develop'

Qa 017 round1

See merge request funkwhale/funkwhale!421
This commit is contained in:
Eliot Berriot 2018-09-28 14:54:22 +00:00
commit 68540cf4ab
14 changed files with 103 additions and 33 deletions

View File

@ -1,4 +1,3 @@
from rest_framework import serializers
from funkwhale_api.activity import serializers as activity_serializers

View File

@ -1,4 +1,3 @@
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry

View File

@ -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(

View File

@ -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

View File

@ -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)

View File

@ -1,4 +1,3 @@
from __future__ import absolute_import
import functools

View File

@ -1,4 +1,3 @@
from django.db import models
from rest_framework import serializers

View File

@ -1,4 +1,3 @@
import pytest
import uuid

View File

@ -1,4 +1,3 @@
import funkwhale_api
from funkwhale_api.instance import nodeinfo

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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) }}

View File

@ -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>