diff --git a/api/funkwhale_api/audio/serializers.py b/api/funkwhale_api/audio/serializers.py index c31cdb69da4cf80964b88953d6a24f2a4db46af2..d6d75a58fedcd7fe371a49dc302ca293161e0122 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 4f32ad63c0509ae1e6d9f52be87d0f87349701b5..40ae127356b65f75308a9ff5250f5a97cb0ce54a 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 c546dc3165b5bf327631740e203423ab2812e47e..124c57f62f81cbb91b3be61eaedbe2f4cfe63bd4 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 0eecba8d9e8a871a37e01acedf8273fae7e23ebe..0d1c91cc57ffa11dc82a10f89a442b3901575757 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 9500a2c6a2da4743b8ccf61da48cf96646f4045f..1fb5ac120d0ce55dde5a9c526e4cc33dd97f0a69 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 0000000000000000000000000000000000000000..b247fd09860b99fcb5bfcd538891e15d94fee8f7 --- /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 5802a734e5f965425783eac37e39607c78621dcd..1d9461ae504c08ae043f6306a87c14718fec2f43 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 627c5010b8699fb9aa889c866c78def03c5b0ec9..c6e00b73ff8ab82b5e7e339013a48f315186fb17 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">