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