From e9186ca81330b66680da4b989fbdae6cc3181cfb Mon Sep 17 00:00:00 2001
From: Agate <me@agate.blue>
Date: Fri, 31 Jul 2020 11:46:25 +0200
Subject: [PATCH] Fx #1178: Display channel and track downloads count

---
 api/funkwhale_api/audio/serializers.py       |  5 +++++
 api/funkwhale_api/audio/views.py             | 10 +++++++++-
 api/funkwhale_api/music/serializers.py       |  1 +
 api/tests/audio/test_serializers.py          |  4 +++-
 api/tests/music/test_serializers.py          |  1 +
 changes/changelog.d/1178.enhancement         |  1 +
 front/src/components/library/TrackDetail.vue |  8 ++++++++
 front/src/views/channels/DetailBase.vue      |  3 ++-
 8 files changed, 30 insertions(+), 3 deletions(-)
 create mode 100644 changes/changelog.d/1178.enhancement

diff --git a/api/funkwhale_api/audio/serializers.py b/api/funkwhale_api/audio/serializers.py
index c31cdb69da..d6d75a58fe 100644
--- a/api/funkwhale_api/audio/serializers.py
+++ b/api/funkwhale_api/audio/serializers.py
@@ -235,6 +235,7 @@ class ChannelUpdateSerializer(serializers.Serializer):
 class ChannelSerializer(serializers.ModelSerializer):
     artist = serializers.SerializerMethodField()
     actor = serializers.SerializerMethodField()
+    downloads_count = serializers.SerializerMethodField()
     attributed_to = federation_serializers.APIActorSerializer()
     rss_url = serializers.CharField(source="get_rss_url")
     url = serializers.SerializerMethodField()
@@ -250,6 +251,7 @@ class ChannelSerializer(serializers.ModelSerializer):
             "metadata",
             "rss_url",
             "url",
+            "downloads_count",
         ]
 
     def get_artist(self, obj):
@@ -264,6 +266,9 @@ class ChannelSerializer(serializers.ModelSerializer):
     def get_subscriptions_count(self, obj):
         return obj.actor.received_follows.exclude(approved=False).count()
 
+    def get_downloads_count(self, obj):
+        return getattr(obj, "_downloads_count", None)
+
     def get_actor(self, obj):
         if obj.attributed_to == actors.get_service_actor():
             return None
diff --git a/api/funkwhale_api/audio/views.py b/api/funkwhale_api/audio/views.py
index 4f32ad63c0..40ae127356 100644
--- a/api/funkwhale_api/audio/views.py
+++ b/api/funkwhale_api/audio/views.py
@@ -7,7 +7,7 @@ from rest_framework import viewsets
 
 from django import http
 from django.db import transaction
-from django.db.models import Count, Prefetch, Q
+from django.db.models import Count, Prefetch, Q, Sum
 from django.utils import timezone
 
 from funkwhale_api.common import locales
@@ -93,6 +93,14 @@ class ChannelViewSet(
             return serializers.ChannelUpdateSerializer
         return serializers.ChannelCreateSerializer
 
+    def get_queryset(self):
+        queryset = super().get_queryset()
+        if self.action == "retrieve":
+            queryset = queryset.annotate(
+                _downloads_count=Sum("artist__tracks__downloads_count")
+            )
+        return queryset
+
     def perform_create(self, serializer):
         return serializer.save(attributed_to=self.request.user.actor)
 
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index c546dc3165..124c57f62f 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -294,6 +294,7 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
     is_local = serializers.BooleanField()
     position = serializers.IntegerField()
     disc_number = serializers.IntegerField()
+    downloads_count = serializers.IntegerField()
     copyright = serializers.CharField()
     license = serializers.SerializerMethodField()
     cover = cover_field
diff --git a/api/tests/audio/test_serializers.py b/api/tests/audio/test_serializers.py
index 0eecba8d9e..0d1c91cc57 100644
--- a/api/tests/audio/test_serializers.py
+++ b/api/tests/audio/test_serializers.py
@@ -213,7 +213,7 @@ def test_channel_serializer_update_podcast(factories):
 def test_channel_serializer_representation(factories, to_api_date):
     content = factories["common.Content"]()
     channel = factories["audio.Channel"](artist__description=content)
-
+    setattr(channel, "_downloads_count", 12)
     expected = {
         "artist": music_serializers.serialize_artist_simple(channel.artist),
         "uuid": str(channel.uuid),
@@ -225,6 +225,7 @@ def test_channel_serializer_representation(factories, to_api_date):
         "metadata": {},
         "rss_url": channel.get_rss_url(),
         "url": channel.actor.url,
+        "downloads_count": 12,
     }
     expected["artist"]["description"] = common_serializers.ContentSerializer(
         content
@@ -248,6 +249,7 @@ def test_channel_serializer_external_representation(factories, to_api_date):
         "metadata": {},
         "rss_url": channel.get_rss_url(),
         "url": channel.actor.url,
+        "downloads_count": None,
     }
     expected["artist"]["description"] = common_serializers.ContentSerializer(
         content
diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py
index 9500a2c6a2..1fb5ac120d 100644
--- a/api/tests/music/test_serializers.py
+++ b/api/tests/music/test_serializers.py
@@ -234,6 +234,7 @@ def test_track_serializer(factories, to_api_date):
         "tags": [],
         "attributed_to": federation_serializers.APIActorSerializer(actor).data,
         "cover": common_serializers.AttachmentSerializer(track.attachment_cover).data,
+        "downloads_count": track.downloads_count,
     }
     serializer = serializers.TrackSerializer(track)
     assert serializer.data == expected
diff --git a/changes/changelog.d/1178.enhancement b/changes/changelog.d/1178.enhancement
new file mode 100644
index 0000000000..b247fd0986
--- /dev/null
+++ b/changes/changelog.d/1178.enhancement
@@ -0,0 +1 @@
+Display channel and track downloads count (#1178)
\ No newline at end of file
diff --git a/front/src/components/library/TrackDetail.vue b/front/src/components/library/TrackDetail.vue
index 5802a734e5..1d9461ae50 100644
--- a/front/src/components/library/TrackDetail.vue
+++ b/front/src/components/library/TrackDetail.vue
@@ -48,6 +48,14 @@
                     <translate v-else translate-context="*/*/*">N/A</translate>
                   </td>
                 </tr>
+                <tr>
+                  <td>
+                    <translate translate-context="Content/*/*">Downloads</translate>
+                  </td>
+                  <td class="right aligned">
+                    {{ track.downloads_count }}
+                  </td>
+                </tr>
               </tbody>
             </table>
 
diff --git a/front/src/views/channels/DetailBase.vue b/front/src/views/channels/DetailBase.vue
index 627c5010b8..c6e00b73ff 100644
--- a/front/src/views/channels/DetailBase.vue
+++ b/front/src/views/channels/DetailBase.vue
@@ -28,7 +28,8 @@
                   <translate key="2" v-else translate-context="*/*/*" :translate-params="{count: totalTracks}" :translate-n="totalTracks" translate-plural="%{ count } tracks">%{ count } track</translate>
                 </template>
                 <template v-if="object.attributed_to.full_username === $store.state.auth.fullUsername || $store.getters['channels/isSubscribed'](object.uuid)">
-                  ยท <translate translate-context="Content/Channel/Paragraph" translate-plural="%{ count } subscribers" :translate-n="object.subscriptions_count" :translate-params="{count: object.subscriptions_count}">%{ count } subscriber</translate>
+                  <br><translate translate-context="Content/Channel/Paragraph" translate-plural="%{ count } subscribers" :translate-n="object.subscriptions_count" :translate-params="{count: object.subscriptions_count}">%{ count } subscriber</translate>
+                  <br><translate translate-context="Content/Channel/Paragraph" translate-plural="%{ count } listenings" :translate-n="object.downloads_count" :translate-params="{count: object.downloads_count}">%{ count } listening</translate>
                 </template>
                 <div class="ui hidden small divider"></div>
                 <a @click.stop.prevent="showSubscribeModal = true" class="ui icon small basic button">
-- 
GitLab