Skip to content
Snippets Groups Projects
Commit 68540cf4 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

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

Qa 017 round1

See merge request !421
parents 8bd1ed61 c641b8f5
No related branches found
No related tags found
No related merge requests found
Showing
with 103 additions and 33 deletions
from rest_framework import serializers
from funkwhale_api.activity import serializers as activity_serializers
......
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)
from __future__ import absolute_import
import functools
......
from django.db import models
from rest_framework import serializers
......
import pytest
import uuid
......
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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment