From 47209ee5aeeda9cf6b1b02ceea89d36230f70fd3 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Thu, 3 Jan 2019 11:47:29 +0100 Subject: [PATCH] Added API to list and detail actors --- api/funkwhale_api/federation/models.py | 17 +++++++++- api/funkwhale_api/manage/filters.py | 24 ++++++++++++++ api/funkwhale_api/manage/serializers.py | 37 +++++++++++++++++++++ api/funkwhale_api/manage/urls.py | 5 ++- api/funkwhale_api/manage/views.py | 43 +++++++++++++++++++++++++ api/tests/manage/test_serializers.py | 29 +++++++++++++++++ api/tests/manage/test_views.py | 21 ++++++++++++ 7 files changed, 174 insertions(+), 2 deletions(-) diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index 5e81143cb..ad88422e4 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -61,6 +61,21 @@ class ActorQuerySet(models.QuerySet): return qs + def with_outbox_activities_count(self): + return self.annotate( + outbox_activities_count=models.Count("outbox_activities", distinct=True) + ) + + def with_followers_count(self): + return self.annotate( + followers_count=models.Count("received_follows", distinct=True) + ) + + def with_uploads_count(self): + return self.annotate( + uploads_count=models.Count("libraries__uploads", distinct=True) + ) + class DomainQuerySet(models.QuerySet): def external(self): @@ -71,7 +86,7 @@ class DomainQuerySet(models.QuerySet): def with_outbox_activities_count(self): return self.annotate( - outbox_activities_count=models.Count("actors__outbox_activities") + outbox_activities_count=models.Count("actors__outbox_activities", distinct=True) ) diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py index d9b9bfc1d..dfb901924 100644 --- a/api/funkwhale_api/manage/filters.py +++ b/api/funkwhale_api/manage/filters.py @@ -1,6 +1,8 @@ from django_filters import rest_framework as filters from funkwhale_api.common import fields +from funkwhale_api.common import search + from funkwhale_api.federation import models as federation_models from funkwhale_api.music import models as music_models from funkwhale_api.users import models as users_models @@ -29,6 +31,28 @@ class ManageDomainFilterSet(filters.FilterSet): fields = ["name"] +class ManageActorFilterSet(filters.FilterSet): + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "name": {"to": "name"}, + "username": {"to": "preferred_username"}, + "bio": {"to": "summary"}, + "type": {"to": "type"}, + }, + filter_fields={"domain": {"to": "domain_id__iexact"}}, + ) + ) + local = filters.BooleanFilter(name="_", method="filter_local") + + class Meta: + model = federation_models.Actor + fields = ["q", "domain", "type", "manually_approves_followers", "local"] + + def filter_local(self, queryset, name, value): + return queryset.local(value) + + class ManageUserFilterSet(filters.FilterSet): q = fields.SearchFilter(search_fields=["username", "email", "name"]) diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py index 710d3d62b..3b06fb848 100644 --- a/api/funkwhale_api/manage/serializers.py +++ b/api/funkwhale_api/manage/serializers.py @@ -191,3 +191,40 @@ class ManageDomainSerializer(serializers.ModelSerializer): def get_outbox_activities_count(self, o): return getattr(o, "outbox_activities_count", 0) + + +class ManageActorSerializer(serializers.ModelSerializer): + outbox_activities_count = serializers.SerializerMethodField() + uploads_count = serializers.SerializerMethodField() + followers_count = serializers.SerializerMethodField() + + class Meta: + model = federation_models.Actor + fields = [ + "id", + "url", + "fid", + "preferred_username", + "domain", + "name", + "summary", + "type", + "creation_date", + "last_fetch_date", + "inbox_url", + "outbox_url", + "shared_inbox_url", + "manually_approves_followers", + "outbox_activities_count", + "uploads_count", + "followers_count", + ] + + def get_uploads_count(self, o): + return getattr(o, "uploads_count", 0) + + def get_followers_count(self, o): + return getattr(o, "followers_count", 0) + + def get_outbox_activities_count(self, o): + return getattr(o, "outbox_activities_count", 0) diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py index 26832f946..232b88711 100644 --- a/api/funkwhale_api/manage/urls.py +++ b/api/funkwhale_api/manage/urls.py @@ -11,6 +11,9 @@ users_router = routers.SimpleRouter() users_router.register(r"users", views.ManageUserViewSet, "users") users_router.register(r"invitations", views.ManageInvitationViewSet, "invitations") +other_router = routers.SimpleRouter() +other_router.register(r"accounts", views.ManageActorViewSet, "accounts") + urlpatterns = [ url( r"^federation/", @@ -18,4 +21,4 @@ urlpatterns = [ ), url(r"^library/", include((library_router.urls, "instance"), namespace="library")), url(r"^users/", include((users_router.urls, "instance"), namespace="users")), -] +] + other_router.urls diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py index c3d87be52..ddd4fe571 100644 --- a/api/funkwhale_api/manage/views.py +++ b/api/funkwhale_api/manage/views.py @@ -1,5 +1,6 @@ from rest_framework import mixins, response, viewsets from rest_framework.decorators import detail_route, list_route +from django.shortcuts import get_object_or_404 from funkwhale_api.common import preferences from funkwhale_api.federation import models as federation_models @@ -129,3 +130,45 @@ class ManageDomainViewSet( def stats(self, request, *args, **kwargs): domain = self.get_object() return response.Response(domain.get_stats(), status=200) + + +class ManageActorViewSet( + mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet +): + lookup_value_regex = r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)" + queryset = ( + federation_models.Actor.objects.all() + .with_outbox_activities_count() + .with_followers_count() + .with_uploads_count() + .order_by("-creation_date") + ) + serializer_class = serializers.ManageActorSerializer + filter_class = filters.ManageActorFilterSet + permission_classes = (HasUserPermission,) + required_permissions = ["moderation"] + ordering_fields = [ + "name", + "preferred_username", + "domain", + "fid", + "creation_date", + "last_fetch_date", + "uploads_count", + "followers_count", + "outbox_activities_count", + ] + + def get_object(self): + queryset = self.filter_queryset(self.get_queryset()) + username, domain = self.kwargs["pk"].split("@") + filter_kwargs = {"domain_id": domain, "preferred_username": username} + obj = get_object_or_404(queryset, **filter_kwargs) + self.check_object_permissions(self.request, obj) + + return obj + + @detail_route(methods=["get"]) + def stats(self, request, *args, **kwargs): + domain = self.get_object() + return response.Response(domain.get_stats(), status=200) diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py index 8a9f2ac8e..83d49cd66 100644 --- a/api/tests/manage/test_serializers.py +++ b/api/tests/manage/test_serializers.py @@ -51,3 +51,32 @@ def test_manage_domain_serializer(factories, now): s = serializers.ManageDomainSerializer(domain) assert s.data == expected + + +def test_manage_actor_serializer(factories, now): + actor = factories["federation.Actor"]() + setattr(actor, "outbox_activities_count", 23) + setattr(actor, "followers_count", 42) + setattr(actor, "uploads_count", 66) + expected = { + "id": actor.id, + "name": actor.name, + "creation_date": actor.creation_date.isoformat().split("+")[0] + "Z", + "last_fetch_date": actor.last_fetch_date.isoformat().split("+")[0] + "Z", + "outbox_activities_count": 23, + "followers_count": 42, + "uploads_count": 66, + "fid": actor.fid, + "url": actor.url, + "outbox_url": actor.outbox_url, + "shared_inbox_url": actor.shared_inbox_url, + "inbox_url": actor.inbox_url, + "domain": actor.domain_id, + "type": actor.type, + "summary": actor.summary, + "preferred_username": actor.preferred_username, + "manually_approves_followers": actor.manually_approves_followers, + } + s = serializers.ManageActorSerializer(actor) + + assert s.data == expected diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py index d47a231e8..72e945bca 100644 --- a/api/tests/manage/test_views.py +++ b/api/tests/manage/test_views.py @@ -12,6 +12,7 @@ from funkwhale_api.manage import serializers, views (views.ManageUserViewSet, ["settings"], "and"), (views.ManageInvitationViewSet, ["settings"], "and"), (views.ManageDomainViewSet, ["moderation"], "and"), + (views.ManageActorViewSet, ["moderation"], "and"), ], ) def test_permissions(assert_user_permission, view, permissions, operator): @@ -112,3 +113,23 @@ def test_domain_stats(factories, superuser_api_client, mocker): response = superuser_api_client.get(url) assert response.status_code == 200 assert response.data == {"hello": "world"} + + +def test_actor_list(factories, superuser_api_client, settings): + actor = factories["federation.Actor"]() + url = reverse("api:v1:manage:accounts-list") + response = superuser_api_client.get(url) + + assert response.status_code == 200 + + assert response.data["count"] == 1 + assert response.data["results"][0]["id"] == actor.id + + +def test_actor_detail(factories, superuser_api_client): + actor = factories["federation.Actor"]() + url = reverse("api:v1:manage:accounts-detail", kwargs={"pk": actor.full_username}) + response = superuser_api_client.get(url) + + assert response.status_code == 200 + assert response.data["id"] == actor.id