Added API to list and detail actors
This commit is contained in:
parent
bbc36201c8
commit
47209ee5ae
|
@ -61,6 +61,21 @@ class ActorQuerySet(models.QuerySet):
|
||||||
|
|
||||||
return qs
|
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):
|
class DomainQuerySet(models.QuerySet):
|
||||||
def external(self):
|
def external(self):
|
||||||
|
@ -71,7 +86,7 @@ class DomainQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def with_outbox_activities_count(self):
|
def with_outbox_activities_count(self):
|
||||||
return self.annotate(
|
return self.annotate(
|
||||||
outbox_activities_count=models.Count("actors__outbox_activities")
|
outbox_activities_count=models.Count("actors__outbox_activities", distinct=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
from funkwhale_api.common import fields
|
from funkwhale_api.common import fields
|
||||||
|
from funkwhale_api.common import search
|
||||||
|
|
||||||
from funkwhale_api.federation import models as federation_models
|
from funkwhale_api.federation import models as federation_models
|
||||||
from funkwhale_api.music import models as music_models
|
from funkwhale_api.music import models as music_models
|
||||||
from funkwhale_api.users import models as users_models
|
from funkwhale_api.users import models as users_models
|
||||||
|
@ -29,6 +31,28 @@ class ManageDomainFilterSet(filters.FilterSet):
|
||||||
fields = ["name"]
|
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):
|
class ManageUserFilterSet(filters.FilterSet):
|
||||||
q = fields.SearchFilter(search_fields=["username", "email", "name"])
|
q = fields.SearchFilter(search_fields=["username", "email", "name"])
|
||||||
|
|
||||||
|
|
|
@ -191,3 +191,40 @@ class ManageDomainSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def get_outbox_activities_count(self, o):
|
def get_outbox_activities_count(self, o):
|
||||||
return getattr(o, "outbox_activities_count", 0)
|
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)
|
||||||
|
|
|
@ -11,6 +11,9 @@ users_router = routers.SimpleRouter()
|
||||||
users_router.register(r"users", views.ManageUserViewSet, "users")
|
users_router.register(r"users", views.ManageUserViewSet, "users")
|
||||||
users_router.register(r"invitations", views.ManageInvitationViewSet, "invitations")
|
users_router.register(r"invitations", views.ManageInvitationViewSet, "invitations")
|
||||||
|
|
||||||
|
other_router = routers.SimpleRouter()
|
||||||
|
other_router.register(r"accounts", views.ManageActorViewSet, "accounts")
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
url(
|
||||||
r"^federation/",
|
r"^federation/",
|
||||||
|
@ -18,4 +21,4 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
url(r"^library/", include((library_router.urls, "instance"), namespace="library")),
|
url(r"^library/", include((library_router.urls, "instance"), namespace="library")),
|
||||||
url(r"^users/", include((users_router.urls, "instance"), namespace="users")),
|
url(r"^users/", include((users_router.urls, "instance"), namespace="users")),
|
||||||
]
|
] + other_router.urls
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from rest_framework import mixins, response, viewsets
|
from rest_framework import mixins, response, viewsets
|
||||||
from rest_framework.decorators import detail_route, list_route
|
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.common import preferences
|
||||||
from funkwhale_api.federation import models as federation_models
|
from funkwhale_api.federation import models as federation_models
|
||||||
|
@ -129,3 +130,45 @@ class ManageDomainViewSet(
|
||||||
def stats(self, request, *args, **kwargs):
|
def stats(self, request, *args, **kwargs):
|
||||||
domain = self.get_object()
|
domain = self.get_object()
|
||||||
return response.Response(domain.get_stats(), status=200)
|
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)
|
||||||
|
|
|
@ -51,3 +51,32 @@ def test_manage_domain_serializer(factories, now):
|
||||||
s = serializers.ManageDomainSerializer(domain)
|
s = serializers.ManageDomainSerializer(domain)
|
||||||
|
|
||||||
assert s.data == expected
|
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
|
||||||
|
|
|
@ -12,6 +12,7 @@ from funkwhale_api.manage import serializers, views
|
||||||
(views.ManageUserViewSet, ["settings"], "and"),
|
(views.ManageUserViewSet, ["settings"], "and"),
|
||||||
(views.ManageInvitationViewSet, ["settings"], "and"),
|
(views.ManageInvitationViewSet, ["settings"], "and"),
|
||||||
(views.ManageDomainViewSet, ["moderation"], "and"),
|
(views.ManageDomainViewSet, ["moderation"], "and"),
|
||||||
|
(views.ManageActorViewSet, ["moderation"], "and"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_permissions(assert_user_permission, view, permissions, operator):
|
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)
|
response = superuser_api_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.data == {"hello": "world"}
|
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
|
||||||
|
|
Loading…
Reference in New Issue