From 472cc7e26a231e06ecb00f3666a4d9a08f863bef Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Wed, 11 Apr 2018 21:58:41 +0200 Subject: [PATCH] Detail library view with settings update --- api/funkwhale_api/federation/serializers.py | 37 ++++- api/funkwhale_api/federation/views.py | 3 + api/tests/federation/test_views.py | 31 ++++ front/src/router/index.js | 10 +- front/src/views/federation/Base.vue | 7 + front/src/views/federation/Home.vue | 2 +- front/src/views/federation/LibraryDetail.vue | 149 +++++++++++++++++++ 7 files changed, 228 insertions(+), 11 deletions(-) create mode 100644 front/src/views/federation/Base.vue create mode 100644 front/src/views/federation/LibraryDetail.vue diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index bca35902..c717e679 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -112,6 +112,8 @@ class APIActorSerializer(serializers.ModelSerializer): 'manually_approves_followers', ] + + class LibraryActorSerializer(ActorSerializer): url = serializers.ListField( child=serializers.JSONField()) @@ -137,33 +139,52 @@ class LibraryActorSerializer(ActorSerializer): return validated_data +class APIFollowSerializer(serializers.ModelSerializer): + class Meta: + model = models.Follow + fields = [ + 'uuid', + 'actor', + 'target', + 'approved', + 'creation_date', + 'modification_date', + ] + + class APILibrarySerializer(serializers.ModelSerializer): - actor = APIActorSerializer + actor = APIActorSerializer() + follow = APIFollowSerializer() class Meta: model = models.Library - fields = [ + + read_only_fields = [ 'actor', - 'autoimport', - 'federation_enabled', - 'download_files', - 'tracks_count', - 'url', 'uuid', - 'creation_date', + 'url', + 'tracks_count', 'follow', 'fetched_date', 'modification_date', + 'creation_date', ] + fields = [ + 'autoimport', + 'federation_enabled', + 'download_files', + ] + read_only_fields class APILibraryCreateSerializer(serializers.ModelSerializer): actor = serializers.URLField() federation_enabled = serializers.BooleanField() + uuid = serializers.UUIDField(read_only=True) class Meta: model = models.Library fields = [ + 'uuid', 'actor', 'autoimport', 'federation_enabled', diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py index bceac424..cac6c416 100644 --- a/api/funkwhale_api/federation/views.py +++ b/api/funkwhale_api/federation/views.py @@ -166,6 +166,8 @@ class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet): class LibraryViewSet( + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet): permission_classes = [rest_permissions.DjangoModelPermissions] @@ -173,6 +175,7 @@ class LibraryViewSet( 'actor', 'follow', ) + lookup_field = 'uuid' filter_class = filters.LibraryFilter serializer_class = serializers.APILibrarySerializer ordering_fields = ( diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py index a2786be0..72feaabf 100644 --- a/api/tests/federation/test_views.py +++ b/api/tests/federation/test_views.py @@ -275,3 +275,34 @@ def test_can_list_libraries(factories, superuser_api_client): serializers.APILibrarySerializer(library1).data, serializers.APILibrarySerializer(library2).data, ] + + +def test_can_detail_library(factories, superuser_api_client): + library = factories['federation.Library']() + + url = reverse( + 'api:v1:federation:libraries-detail', + kwargs={'uuid': str(library.uuid)}) + response = superuser_api_client.get(url) + + assert response.status_code == 200 + assert response.data == serializers.APILibrarySerializer(library).data + + +def test_can_patch_library(factories, superuser_api_client): + library = factories['federation.Library']() + data = { + 'federation_enabled': not library.federation_enabled, + 'download_files': not library.download_files, + 'autoimport': not library.autoimport, + } + url = reverse( + 'api:v1:federation:libraries-detail', + kwargs={'uuid': str(library.uuid)}) + response = superuser_api_client.patch(url, data) + + assert response.status_code == 200 + library.refresh_from_db() + + for k, v in data.items(): + assert getattr(library, k) == v diff --git a/front/src/router/index.js b/front/src/router/index.js index 0981c37f..394a1284 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -25,7 +25,9 @@ import RequestsList from '@/components/requests/RequestsList' import PlaylistDetail from '@/views/playlists/Detail' import PlaylistList from '@/views/playlists/List' import Favorites from '@/components/favorites/List' -import Federation from '@/views/federation/Home' +import FederationBase from '@/views/federation/Base' +import FederationHome from '@/views/federation/Home' +import FederationLibraryDetail from '@/views/federation/LibraryDetail' Vue.use(Router) @@ -86,7 +88,11 @@ export default new Router({ }, { path: '/manage/federation', - component: Federation + component: FederationBase, + children: [ + { path: '', component: FederationHome }, + { path: 'library/:id', name: 'federation.libraries.detail', component: FederationLibraryDetail, props: true } + ] }, { path: '/library', diff --git a/front/src/views/federation/Base.vue b/front/src/views/federation/Base.vue new file mode 100644 index 00000000..6add8e5e --- /dev/null +++ b/front/src/views/federation/Base.vue @@ -0,0 +1,7 @@ +<template> + <div class="main pusher" v-title="'Federation'"> + <div class="ui secondary pointing menu"> + </div> + <router-view :key="$route.fullPath"></router-view> + </div> +</template> diff --git a/front/src/views/federation/Home.vue b/front/src/views/federation/Home.vue index c9e3693d..89048aac 100644 --- a/front/src/views/federation/Home.vue +++ b/front/src/views/federation/Home.vue @@ -1,5 +1,5 @@ <template> - <div class="main pusher" v-title="'Federation'"> + <div> <div class="ui vertical stripe segment"> <h1 class="ui header">Manage federation</h1> <library-form @scanned="updateLibraryData"></library-form> diff --git a/front/src/views/federation/LibraryDetail.vue b/front/src/views/federation/LibraryDetail.vue new file mode 100644 index 00000000..d2430bda --- /dev/null +++ b/front/src/views/federation/LibraryDetail.vue @@ -0,0 +1,149 @@ +<template> + <div> + <div v-if="isLoading" class="ui vertical segment" v-title="'Library'"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="object"> + <div :class="['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']" v-title="libraryUsername"> + <div class="segment-content"> + <h2 class="ui center aligned icon header"> + <i class="circular inverted cloud olive icon"></i> + <div class="content"> + {{ libraryUsername }} + </div> + </h2> + </div> + <div class="ui hidden divider"></div> + <div class="ui one column centered grid"> + <table class="ui collapsing very basic table"> + <tbody> + <tr> + <td>Follow status</td> + <td> + <template v-if="object.follow.approved === null"> + <i class="loading icon"></i> Pending approval + </template> + <template v-else-if="object.follow.approved === true"> + <i class="check icon"></i> Following + </template> + <template v-else-if="object.follow.approved === false"> + <i class="x icon"></i> Not following + </template> + </td> + <td> + </td> + </tr> + <tr> + <td>Federation</td> + <td> + <div class="ui toggle checkbox"> + <input + @change="update('federation_enabled')" + v-model="object.federation_enabled" type="checkbox"> + <label></label> + </div> + </td> + <td> + </td> + </tr> + <tr> + <td>Auto importing</td> + <td> + <div class="ui toggle checkbox"> + <input + @change="update('autoimport')" + v-model="object.autoimport" type="checkbox"> + <label></label> + </div> + </td> + <td></td> + </tr> + <tr> + <td>File mirroring</td> + <td> + <div class="ui toggle checkbox"> + <input + @change="update('download_files')" + v-model="object.download_files" type="checkbox"> + <label></label> + </div> + </td> + <td></td> + </tr> + <tr> + <td>Library size</td> + <td> + {{ object.tracks_count }} tracks + </td> + <td></td> + </tr> + </tbody> + </table> + </div> + </div> + <div class="ui vertical stripe segment"> + <h2>Tracks available in this library</h2> + <div class="ui stackable doubling three column grid"> + </div> + </div> + </template> + </div> +</template> + +<script> +import axios from 'axios' +import logger from '@/logging' + +export default { + props: ['id'], + components: {}, + data () { + return { + isLoading: true, + object: null + } + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + var self = this + this.isLoading = true + let url = 'federation/libraries/' + this.id + '/' + logger.default.debug('Fetching library "' + this.id + '"') + axios.get(url).then((response) => { + self.object = response.data + self.isLoading = false + }) + }, + update (attr) { + let newValue = this.object[attr] + let params = {} + let self = this + params[attr] = newValue + axios.patch('federation/libraries/' + this.id + '/', params).then((response) => { + console.log(`${attr} was updated succcessfully to ${newValue}`) + }, (error) => { + console.log(`Error while setting ${attr} to ${newValue}`, error) + self.object[attr] = !newValue + }) + } + }, + computed: { + libraryUsername () { + let actor = this.object.actor + return `${actor.preferred_username}@${actor.domain}` + } + }, + watch: { + id () { + this.fetchData() + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> -- GitLab