From 9552b49a46613732b70fd074d652bdf67a39fa4f Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Fri, 13 Sep 2019 05:53:40 +0200
Subject: [PATCH] See #890: expose number of reports linked to tracks, albums,
 libraries, accounts and artists via the /stats route

---
 api/funkwhale_api/common/models.py                  |  4 ++--
 api/funkwhale_api/federation/models.py              |  2 ++
 api/funkwhale_api/manage/views.py                   |  2 ++
 api/funkwhale_api/moderation/models.py              |  3 +++
 api/tests/federation/test_models.py                 |  1 +
 api/tests/manage/test_views.py                      |  4 ++++
 front/src/views/admin/library/AlbumDetail.vue       | 10 ++++++++++
 front/src/views/admin/library/ArtistDetail.vue      | 10 ++++++++++
 front/src/views/admin/library/LibraryDetail.vue     | 10 ++++++++++
 front/src/views/admin/library/TrackDetail.vue       | 10 ++++++++++
 front/src/views/admin/moderation/AccountsDetail.vue | 10 ++++++++++
 front/src/views/admin/moderation/ReportsList.vue    |  9 +++++++++
 12 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/api/funkwhale_api/common/models.py b/api/funkwhale_api/common/models.py
index 52a02cad9..9fd1a3c76 100644
--- a/api/funkwhale_api/common/models.py
+++ b/api/funkwhale_api/common/models.py
@@ -73,7 +73,7 @@ class LocalFromFidQuerySet:
             return self.filter(~query)
 
 
-class MutationQuerySet(models.QuerySet):
+class GenericTargetQuerySet(models.QuerySet):
     def get_for_target(self, target):
         content_type = ContentType.objects.get_for_model(target)
         return self.filter(target_content_type=content_type, target_id=target.pk)
@@ -119,7 +119,7 @@ class Mutation(models.Model):
     )
     target = GenericForeignKey("target_content_type", "target_id")
 
-    objects = MutationQuerySet.as_manager()
+    objects = GenericTargetQuerySet.as_manager()
 
     def get_federation_id(self):
         if self.fid:
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index df81cd500..fa5050e34 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -248,6 +248,7 @@ class Actor(models.Model):
 
     def get_stats(self):
         from funkwhale_api.music import models as music_models
+        from funkwhale_api.moderation import models as moderation_models
 
         data = Actor.objects.filter(pk=self.pk).aggregate(
             outbox_activities=models.Count("outbox_activities", distinct=True),
@@ -260,6 +261,7 @@ class Actor(models.Model):
         data["artists"] = music_models.Artist.objects.filter(
             from_activity__actor=self.pk
         ).count()
+        data["reports"] = moderation_models.Report.objects.get_for_target(self).count()
         data["albums"] = music_models.Album.objects.filter(
             from_activity__actor=self.pk
         ).count()
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index 8a4f91e77..a016f4326 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -41,6 +41,7 @@ def get_stats(tracks, target):
     ).count()
     data["libraries"] = uploads.values_list("library", flat=True).distinct().count()
     data["uploads"] = uploads.count()
+    data["reports"] = moderation_models.Report.objects.get_for_target(target).count()
     data.update(get_media_stats(uploads))
     return data
 
@@ -248,6 +249,7 @@ class ManageLibraryViewSet(
             "tracks": tracks.count(),
             "albums": albums.count(),
             "artists": len(artists),
+            "reports": moderation_models.Report.objects.get_for_target(library).count(),
         }
         data.update(get_media_stats(uploads.all()))
         return response.Response(data, status=200)
diff --git a/api/funkwhale_api/moderation/models.py b/api/funkwhale_api/moderation/models.py
index c2b91760d..e6b9cf09e 100644
--- a/api/funkwhale_api/moderation/models.py
+++ b/api/funkwhale_api/moderation/models.py
@@ -10,6 +10,7 @@ from django.dispatch import receiver
 from django.urls import reverse
 from django.utils import timezone
 
+from funkwhale_api.common import models as common_models
 from funkwhale_api.federation import models as federation_models
 from funkwhale_api.federation import utils as federation_utils
 
@@ -152,6 +153,8 @@ class Report(federation_models.FederationMixin):
         "Note", content_type_field="target_content_type", object_id_field="target_id"
     )
 
+    objects = common_models.GenericTargetQuerySet.as_manager()
+
     def get_federation_id(self):
         if self.fid:
             return self.fid
diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py
index d6f862bb3..a7460b01c 100644
--- a/api/tests/federation/test_models.py
+++ b/api/tests/federation/test_models.py
@@ -124,6 +124,7 @@ def test_actor_stats(factories):
         "albums": 0,
         "uploads": 0,
         "artists": 0,
+        "reports": 0,
         "outbox_activities": 0,
         "received_library_follows": 0,
         "emitted_library_follows": 0,
diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py
index 7f17fce11..70520ab75 100644
--- a/api/tests/manage/test_views.py
+++ b/api/tests/manage/test_views.py
@@ -189,6 +189,7 @@ def test_artist_detail_stats(factories, superuser_api_client):
         "listenings": 0,
         "playlists": 0,
         "mutations": 0,
+        "reports": 0,
         "track_favorites": 0,
         "media_total_size": 0,
         "media_downloaded_size": 0,
@@ -238,6 +239,7 @@ def test_album_detail_stats(factories, superuser_api_client):
         "listenings": 0,
         "playlists": 0,
         "mutations": 0,
+        "reports": 0,
         "track_favorites": 0,
         "media_total_size": 0,
         "media_downloaded_size": 0,
@@ -284,6 +286,7 @@ def test_track_detail_stats(factories, superuser_api_client):
         "listenings": 0,
         "playlists": 0,
         "mutations": 0,
+        "reports": 0,
         "track_favorites": 0,
         "media_total_size": 0,
         "media_downloaded_size": 0,
@@ -346,6 +349,7 @@ def test_library_detail_stats(factories, superuser_api_client):
         "tracks": 0,
         "albums": 0,
         "artists": 0,
+        "reports": 0,
         "media_total_size": 0,
         "media_downloaded_size": 0,
     }
diff --git a/front/src/views/admin/library/AlbumDetail.vue b/front/src/views/admin/library/AlbumDetail.vue
index 031fdaff0..857b5ad10 100644
--- a/front/src/views/admin/library/AlbumDetail.vue
+++ b/front/src/views/admin/library/AlbumDetail.vue
@@ -183,6 +183,16 @@
                       {{ stats.playlists }}
                     </td>
                   </tr>
+                  <tr>
+                    <td>
+                      <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `album:${object.id}`) }}">
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                      </router-link>
+                    </td>
+                    <td>
+                      {{ stats.reports }}
+                    </td>
+                  </tr>
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'album ' + object.id)}}">
diff --git a/front/src/views/admin/library/ArtistDetail.vue b/front/src/views/admin/library/ArtistDetail.vue
index 80c6e7585..2a0fab02b 100644
--- a/front/src/views/admin/library/ArtistDetail.vue
+++ b/front/src/views/admin/library/ArtistDetail.vue
@@ -171,6 +171,16 @@
                       {{ stats.playlists }}
                     </td>
                   </tr>
+                  <tr>
+                    <td>
+                      <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `artist:${object.id}`) }}">
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                      </router-link>
+                    </td>
+                    <td>
+                      {{ stats.reports }}
+                    </td>
+                  </tr>
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'artist ' + object.id)}}">
diff --git a/front/src/views/admin/library/LibraryDetail.vue b/front/src/views/admin/library/LibraryDetail.vue
index db53bd2b5..500bc7434 100644
--- a/front/src/views/admin/library/LibraryDetail.vue
+++ b/front/src/views/admin/library/LibraryDetail.vue
@@ -174,6 +174,16 @@
                       {{ stats.followers }}
                     </td>
                   </tr>
+                  <tr>
+                    <td>
+                      <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `library:${object.uuid}`) }}">
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                      </router-link>
+                    </td>
+                    <td>
+                      {{ stats.reports }}
+                    </td>
+                  </tr>
                 </tbody>
               </table>
             </section>
diff --git a/front/src/views/admin/library/TrackDetail.vue b/front/src/views/admin/library/TrackDetail.vue
index 17707c61a..8fd9b6a98 100644
--- a/front/src/views/admin/library/TrackDetail.vue
+++ b/front/src/views/admin/library/TrackDetail.vue
@@ -235,6 +235,16 @@
                       {{ stats.playlists }}
                     </td>
                   </tr>
+                  <tr>
+                    <td>
+                      <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `track:${object.id}`) }}">
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                      </router-link>
+                    </td>
+                    <td>
+                      {{ stats.reports }}
+                    </td>
+                  </tr>
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'track ' + object.id)}}">
diff --git a/front/src/views/admin/moderation/AccountsDetail.vue b/front/src/views/admin/moderation/AccountsDetail.vue
index 7825bfe0f..5bbf2e59a 100644
--- a/front/src/views/admin/moderation/AccountsDetail.vue
+++ b/front/src/views/admin/moderation/AccountsDetail.vue
@@ -264,6 +264,16 @@
                       {{ stats.emitted_library_follows}}
                     </td>
                   </tr>
+                  <tr>
+                    <td>
+                      <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `account:${object.full_username}`) }}">
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                      </router-link>
+                    </td>
+                    <td>
+                      {{ stats.reports }}
+                    </td>
+                  </tr>
                 </tbody>
               </table>
             </section>
diff --git a/front/src/views/admin/moderation/ReportsList.vue b/front/src/views/admin/moderation/ReportsList.vue
index 73acaf112..7313828f2 100644
--- a/front/src/views/admin/moderation/ReportsList.vue
+++ b/front/src/views/admin/moderation/ReportsList.vue
@@ -57,6 +57,15 @@
       <div v-else-if="mode === 'card'">
         <report-card @handled="fetchData" :obj="obj" v-for="obj in result.results" :key="obj.uuid" />
       </div>
+      <div class="ui center aligned basic segment">
+        <pagination
+          v-if="result && result.count > paginateBy"
+          @page-changed="selectPage"
+          :current="page"
+          :paginate-by="paginateBy"
+          :total="result.count"
+          ></pagination>
+      </div>
     </section>
   </main>
 </template>
-- 
GitLab