From c7782693bca3aeddcceeba932dd30a4e8bd79233 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 29 May 2018 00:07:20 +0200 Subject: [PATCH] See #223: api for listing/managing library files --- api/config/api_urls.py | 4 ++ api/funkwhale_api/manage/__init__.py | 3 + api/funkwhale_api/manage/filters.py | 25 ++++++++ api/funkwhale_api/manage/serializers.py | 81 +++++++++++++++++++++++++ api/funkwhale_api/manage/urls.py | 11 ++++ api/funkwhale_api/manage/views.py | 49 +++++++++++++++ api/funkwhale_api/music/factories.py | 5 ++ api/tests/manage/__init__.py | 0 api/tests/manage/test_serializers.py | 10 +++ api/tests/manage/test_views.py | 25 ++++++++ 10 files changed, 213 insertions(+) create mode 100644 api/funkwhale_api/manage/__init__.py create mode 100644 api/funkwhale_api/manage/filters.py create mode 100644 api/funkwhale_api/manage/serializers.py create mode 100644 api/funkwhale_api/manage/urls.py create mode 100644 api/funkwhale_api/manage/views.py create mode 100644 api/tests/manage/__init__.py create mode 100644 api/tests/manage/test_serializers.py create mode 100644 api/tests/manage/test_views.py diff --git a/api/config/api_urls.py b/api/config/api_urls.py index e75781d14..98b863a93 100644 --- a/api/config/api_urls.py +++ b/api/config/api_urls.py @@ -38,6 +38,10 @@ v1_patterns += [ include( ('funkwhale_api.instance.urls', 'instance'), namespace='instance')), + url(r'^manage/', + include( + ('funkwhale_api.manage.urls', 'manage'), + namespace='manage')), url(r'^federation/', include( ('funkwhale_api.federation.api_urls', 'federation'), diff --git a/api/funkwhale_api/manage/__init__.py b/api/funkwhale_api/manage/__init__.py new file mode 100644 index 000000000..03e091e5c --- /dev/null +++ b/api/funkwhale_api/manage/__init__.py @@ -0,0 +1,3 @@ +""" +App that includes all views/serializers and stuff for management API +""" diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py new file mode 100644 index 000000000..9853b7a61 --- /dev/null +++ b/api/funkwhale_api/manage/filters.py @@ -0,0 +1,25 @@ +from django.db.models import Count + +from django_filters import rest_framework as filters + +from funkwhale_api.common import fields +from funkwhale_api.music import models as music_models + + +class ManageTrackFileFilterSet(filters.FilterSet): + q = fields.SearchFilter(search_fields=[ + 'track__title', + 'track__album__title', + 'track__artist__name', + 'source', + ]) + + class Meta: + model = music_models.TrackFile + fields = [ + 'q', + 'track__album', + 'track__artist', + 'track', + 'library_track' + ] diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py new file mode 100644 index 000000000..bbd393236 --- /dev/null +++ b/api/funkwhale_api/manage/serializers.py @@ -0,0 +1,81 @@ +from django.db import transaction +from rest_framework import serializers + +from funkwhale_api.common import serializers as common_serializers +from funkwhale_api.music import models as music_models + +from . import filters + + +class ManageTrackFileArtistSerializer(serializers.ModelSerializer): + class Meta: + model = music_models.Artist + fields = [ + 'id', + 'mbid', + 'creation_date', + 'name', + ] + + +class ManageTrackFileAlbumSerializer(serializers.ModelSerializer): + artist = ManageTrackFileArtistSerializer() + + class Meta: + model = music_models.Album + fields = ( + 'id', + 'mbid', + 'title', + 'artist', + 'release_date', + 'cover', + 'creation_date', + ) + + +class ManageTrackFileTrackSerializer(serializers.ModelSerializer): + artist = ManageTrackFileArtistSerializer() + album = ManageTrackFileAlbumSerializer() + + class Meta: + model = music_models.Track + fields = ( + 'id', + 'mbid', + 'title', + 'album', + 'artist', + 'creation_date', + 'position', + ) + + +class ManageTrackFileSerializer(serializers.ModelSerializer): + track = ManageTrackFileTrackSerializer() + + class Meta: + model = music_models.TrackFile + fields = ( + 'id', + 'path', + 'source', + 'filename', + 'mimetype', + 'track', + 'duration', + 'mimetype', + 'bitrate', + 'size', + 'path', + 'library_track', + ) + + +class ManageTrackFileActionSerializer(common_serializers.ActionSerializer): + actions = ['delete'] + filterset_class = filters.ManageTrackFileFilterSet + + @transaction.atomic + def handle_delete(self, objects): + return objects.delete() diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py new file mode 100644 index 000000000..c434581ec --- /dev/null +++ b/api/funkwhale_api/manage/urls.py @@ -0,0 +1,11 @@ +from django.conf.urls import include, url +from . import views + +from rest_framework import routers +library_router = routers.SimpleRouter() +library_router.register(r'track-files', views.ManageTrackFileViewSet, 'track-files') + +urlpatterns = [ + url(r'^library/', + include((library_router.urls, 'instance'), namespace='library')), +] diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py new file mode 100644 index 000000000..74059caa1 --- /dev/null +++ b/api/funkwhale_api/manage/views.py @@ -0,0 +1,49 @@ +from rest_framework import mixins +from rest_framework import response +from rest_framework import viewsets +from rest_framework.decorators import list_route + +from funkwhale_api.music import models as music_models +from funkwhale_api.users.permissions import HasUserPermission + +from . import filters +from . import serializers + + +class ManageTrackFileViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): + queryset = ( + music_models.TrackFile.objects.all() + .select_related( + 'track__artist', + 'track__album__artist', + 'library_track') + .order_by('-id') + ) + serializer_class = serializers.ManageTrackFileSerializer + filter_class = filters.ManageTrackFileFilterSet + permission_classes = (HasUserPermission,) + required_permissions = ['library'] + ordering_fields = [ + 'accessed_date', + 'modification_date', + 'creation_date', + 'track__artist__name', + 'bitrate', + 'size', + 'duration', + ] + + @list_route(methods=['post']) + def action(self, request, *args, **kwargs): + queryset = self.get_queryset() + serializer = serializers.ManageTrackFileActionSerializer( + request.data, + queryset=queryset, + ) + serializer.is_valid(raise_exception=True) + result = serializer.save() + return response.Response(result, status=200) diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py index 412e2f798..11423f5b0 100644 --- a/api/funkwhale_api/music/factories.py +++ b/api/funkwhale_api/music/factories.py @@ -117,6 +117,11 @@ class ImportJobFactory(factory.django.DjangoModelFactory): status='finished', audio_file=None, ) + with_audio_file = factory.Trait( + status='finished', + audio_file=factory.django.FileField( + from_path=os.path.join(SAMPLES_PATH, 'test.ogg')), + ) @registry.register(name='music.FileImportJob') diff --git a/api/tests/manage/__init__.py b/api/tests/manage/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py new file mode 100644 index 000000000..45167722c --- /dev/null +++ b/api/tests/manage/test_serializers.py @@ -0,0 +1,10 @@ +from funkwhale_api.manage import serializers + + +def test_manage_track_file_action_delete(factories): + tfs = factories['music.TrackFile'](size=5) + s = serializers.ManageTrackFileActionSerializer(queryset=None) + + s.handle_delete(tfs.__class__.objects.all()) + + assert tfs.__class__.objects.count() == 0 diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py new file mode 100644 index 000000000..0507e6c11 --- /dev/null +++ b/api/tests/manage/test_views.py @@ -0,0 +1,25 @@ +import pytest + +from django.urls import reverse + +from funkwhale_api.manage import serializers +from funkwhale_api.manage import views + + +@pytest.mark.parametrize('view,permissions,operator', [ + (views.ManageTrackFileViewSet, ['library'], 'and'), +]) +def test_permissions(assert_user_permission, view, permissions, operator): + assert_user_permission(view, permissions, operator) + + +def test_track_file_view(factories, superuser_api_client): + tfs = factories['music.TrackFile'].create_batch(size=5) + qs = tfs[0].__class__.objects.order_by('-creation_date') + url = reverse('api:v1:manage:library:track-files-list') + + response = superuser_api_client.get(url, {'sort': '-creation_date'}) + expected = serializers.ManageTrackFileSerializer(qs, many=True).data + + assert response.data['count'] == len(tfs) + assert response.data['results'] == expected