Skip to content
Snippets Groups Projects
Verified Commit 1674ad91 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

See #75: implemented subsonic playlist API endpoints

parent 7e9320fc
No related branches found
No related tags found
No related merge requests found
......@@ -150,3 +150,31 @@ def get_album_list2_data(albums):
get_album2_data(a)
for a in albums
]
def get_playlist_data(playlist):
return {
'id': playlist.pk,
'name': playlist.name,
'owner': playlist.user.username,
'public': 'false',
'songCount': playlist._tracks_count,
'duration': 0,
'created': playlist.creation_date,
}
def get_playlist_detail_data(playlist):
data = get_playlist_data(playlist)
qs = playlist.playlist_tracks.select_related(
'track__album__artist'
).prefetch_related('track__files').order_by('index')
data['entry'] = []
for plt in qs:
try:
tf = [tf for tf in plt.track.files.all()][0]
except IndexError:
continue
td = get_track_data(plt.track.album, plt.track, tf)
data['entry'].append(td)
return data
......@@ -13,6 +13,7 @@ from funkwhale_api.favorites.models import TrackFavorite
from funkwhale_api.music import models as music_models
from funkwhale_api.music import utils
from funkwhale_api.music import views as music_views
from funkwhale_api.playlists import models as playlists_models
from . import authentication
from . import filters
......@@ -38,13 +39,16 @@ def find_object(queryset, model_field='pk', field='id', cast=int):
'code': 0,
'message': 'For input string "{}"'.format(raw_value)
})
qs = queryset
if hasattr(qs, '__call__'):
qs = qs(request)
try:
obj = queryset.get(**{model_field: value})
except queryset.model.DoesNotExist:
obj = qs.get(**{model_field: value})
except qs.model.DoesNotExist:
return response.Response({
'code': 70,
'message': '{} not found'.format(
queryset.model.__class__.__name__)
qs.model.__class__.__name__)
})
kwargs['obj'] = obj
return func(self, request, *args, **kwargs)
......@@ -241,7 +245,6 @@ class SubsonicViewSet(viewsets.GenericViewSet):
}
return response.Response(data)
@list_route(
methods=['get', 'post'],
url_name='search3',
......@@ -308,3 +311,116 @@ class SubsonicViewSet(viewsets.GenericViewSet):
queryset = queryset[offset:size]
payload['searchResult3'][c['subsonic']] = c['serializer'](queryset)
return response.Response(payload)
@list_route(
methods=['get', 'post'],
url_name='get_playlists',
url_path='getPlaylists')
def get_playlists(self, request, *args, **kwargs):
playlists = request.user.playlists.with_tracks_count().select_related(
'user'
)
data = {
'playlists': {
'playlist': [
serializers.get_playlist_data(p) for p in playlists]
}
}
return response.Response(data)
@list_route(
methods=['get', 'post'],
url_name='get_playlist',
url_path='getPlaylist')
@find_object(
playlists_models.Playlist.objects.with_tracks_count())
def get_playlist(self, request, *args, **kwargs):
playlist = kwargs.pop('obj')
data = {
'playlist': serializers.get_playlist_detail_data(playlist)
}
return response.Response(data)
@list_route(
methods=['get', 'post'],
url_name='update_playlist',
url_path='updatePlaylist')
@find_object(
lambda request: request.user.playlists.all(),
field='playlistId')
def update_playlist(self, request, *args, **kwargs):
playlist = kwargs.pop('obj')
data = request.GET or request.POST
new_name = data.get('name', '')
if new_name:
playlist.name = new_name
playlist.save(update_fields=['name', 'modification_date'])
try:
to_remove = int(data['songIndexToRemove'])
plt = playlist.playlist_tracks.get(index=to_remove)
except (TypeError, ValueError, KeyError):
pass
except playlists_models.PlaylistTrack.DoesNotExist:
pass
else:
plt.delete(update_indexes=True)
try:
to_add = int(data['songIdToAdd'])
track = music_models.Track.objects.get(pk=to_add)
except (TypeError, ValueError, KeyError):
pass
except music_models.Track.DoesNotExist:
pass
else:
playlist.insert_many([track])
data = {
'status': 'ok'
}
return response.Response(data)
@list_route(
methods=['get', 'post'],
url_name='delete_playlist',
url_path='deletePlaylist')
@find_object(
lambda request: request.user.playlists.all())
def delete_playlist(self, request, *args, **kwargs):
playlist = kwargs.pop('obj')
playlist.delete()
data = {
'status': 'ok'
}
return response.Response(data)
@list_route(
methods=['get', 'post'],
url_name='create_playlist',
url_path='createPlaylist')
def create_playlist(self, request, *args, **kwargs):
data = request.GET or request.POST
name = data.get('name', '')
if not name:
return response.Response({
'code': 10,
'message': 'Playlist ID or name must be specified.'
}, data)
playlist = request.user.playlists.create(
name=name
)
try:
to_add = int(data['songId'])
track = music_models.Track.objects.get(pk=to_add)
except (TypeError, ValueError, KeyError):
pass
except music_models.Track.DoesNotExist:
pass
else:
playlist.insert_many([track])
playlist = request.user.playlists.with_tracks_count().get(
pk=playlist.pk)
data = {
'playlist': serializers.get_playlist_detail_data(playlist)
}
return response.Response(data)
......@@ -133,3 +133,43 @@ def test_get_album_list2_serializer(factories):
]
data = serializers.get_album_list2_data(qs)
assert data == expected
def test_playlist_serializer(factories):
plt = factories['playlists.PlaylistTrack']()
playlist = plt.playlist
qs = music_models.Album.objects.with_tracks_count().order_by('pk')
expected = {
'id': playlist.pk,
'name': playlist.name,
'owner': playlist.user.username,
'public': 'false',
'songCount': 1,
'duration': 0,
'created': playlist.creation_date,
}
qs = playlist.__class__.objects.with_tracks_count()
data = serializers.get_playlist_data(qs.first())
assert data == expected
def test_playlist_detail_serializer(factories):
plt = factories['playlists.PlaylistTrack']()
tf = factories['music.TrackFile'](track=plt.track)
playlist = plt.playlist
qs = music_models.Album.objects.with_tracks_count().order_by('pk')
expected = {
'id': playlist.pk,
'name': playlist.name,
'owner': playlist.user.username,
'public': 'false',
'songCount': 1,
'duration': 0,
'created': playlist.creation_date,
'entry': [
serializers.get_track_data(plt.track.album, plt.track, tf)
]
}
qs = playlist.__class__.objects.with_tracks_count()
data = serializers.get_playlist_detail_data(qs.first())
assert data == expected
......@@ -240,3 +240,94 @@ def test_search3(f, db, logged_in_api_client, factories):
'song': serializers.get_song_list_data([track]),
}
}
@pytest.mark.parametrize('f', ['xml', 'json'])
def test_get_playlists(f, db, logged_in_api_client, factories):
url = reverse('api:subsonic-get-playlists')
assert url.endswith('getPlaylists') is True
playlist = factories['playlists.Playlist'](
user=logged_in_api_client.user
)
response = logged_in_api_client.get(url, {'f': f})
qs = playlist.__class__.objects.with_tracks_count()
assert response.status_code == 200
assert response.data == {
'playlists': {
'playlist': [serializers.get_playlist_data(qs.first())],
}
}
@pytest.mark.parametrize('f', ['xml', 'json'])
def test_get_playlist(f, db, logged_in_api_client, factories):
url = reverse('api:subsonic-get-playlist')
assert url.endswith('getPlaylist') is True
playlist = factories['playlists.Playlist'](
user=logged_in_api_client.user
)
response = logged_in_api_client.get(url, {'f': f, 'id': playlist.pk})
qs = playlist.__class__.objects.with_tracks_count()
assert response.status_code == 200
assert response.data == {
'playlist': serializers.get_playlist_detail_data(qs.first())
}
@pytest.mark.parametrize('f', ['xml', 'json'])
def test_update_playlist(f, db, logged_in_api_client, factories):
url = reverse('api:subsonic-update-playlist')
assert url.endswith('updatePlaylist') is True
playlist = factories['playlists.Playlist'](
user=logged_in_api_client.user
)
plt = factories['playlists.PlaylistTrack'](
index=0, playlist=playlist)
new_track = factories['music.Track']()
response = logged_in_api_client.get(
url, {
'f': f,
'name': 'new_name',
'playlistId': playlist.pk,
'songIdToAdd': new_track.pk,
'songIndexToRemove': 0})
playlist.refresh_from_db()
assert response.status_code == 200
assert playlist.name == 'new_name'
assert playlist.playlist_tracks.count() == 1
assert playlist.playlist_tracks.first().track_id == new_track.pk
@pytest.mark.parametrize('f', ['xml', 'json'])
def test_delete_playlist(f, db, logged_in_api_client, factories):
url = reverse('api:subsonic-delete-playlist')
assert url.endswith('deletePlaylist') is True
playlist = factories['playlists.Playlist'](
user=logged_in_api_client.user
)
response = logged_in_api_client.get(
url, {'f': f, 'id': playlist.pk})
assert response.status_code == 200
with pytest.raises(playlist.__class__.DoesNotExist):
playlist.refresh_from_db()
@pytest.mark.parametrize('f', ['xml', 'json'])
def test_create_playlist(f, db, logged_in_api_client, factories):
url = reverse('api:subsonic-create-playlist')
assert url.endswith('createPlaylist') is True
track = factories['music.Track']()
response = logged_in_api_client.get(
url, {'f': f, 'name': 'hello', 'songId': track.pk})
assert response.status_code == 200
playlist = logged_in_api_client.user.playlists.latest('id')
plt = playlist.playlist_tracks.latest('id')
assert playlist.name == 'hello'
assert plt.index == 0
assert plt.track == track
qs = playlist.__class__.objects.with_tracks_count()
assert response.data == {
'playlist': serializers.get_playlist_detail_data(qs.first())
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment