Verified Commit f0220c81 authored by Agate's avatar Agate 💬

See #170: can now retrieve actor information via API

parent b48a4cd0
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.music import models as music_models
from funkwhale_api.users import serializers as users_serializers
from . import filters
from . import models
......@@ -169,3 +172,27 @@ class FetchSerializer(serializers.ModelSerializer):
"creation_date",
"fetch_date",
]
class FullActorSerializer(serializers.Serializer):
fid = serializers.URLField()
url = serializers.URLField()
domain = serializers.CharField(source="domain_id")
creation_date = serializers.DateTimeField()
last_fetch_date = serializers.DateTimeField()
name = serializers.CharField()
preferred_username = serializers.CharField()
full_username = serializers.CharField()
type = serializers.CharField()
is_local = serializers.BooleanField()
is_channel = serializers.SerializerMethodField()
manually_approves_followers = serializers.BooleanField()
user = users_serializers.UserBasicSerializer()
summary = common_serializers.ContentSerializer(source="summary_obj")
icon = common_serializers.AttachmentSerializer(source="attachment_icon")
def get_is_channel(self, o):
try:
return bool(o.channel)
except ObjectDoesNotExist:
return False
......@@ -8,5 +8,6 @@ router.register(r"follows/library", api_views.LibraryFollowViewSet, "library-fol
router.register(r"inbox", api_views.InboxItemViewSet, "inbox")
router.register(r"libraries", api_views.LibraryViewSet, "libraries")
router.register(r"domains", api_views.DomainViewSet, "domains")
router.register(r"actors", api_views.ActorViewSet, "actors")
urlpatterns = router.urls
......@@ -218,3 +218,28 @@ class DomainViewSet(
if preferences.get("moderation__allow_list_enabled"):
qs = qs.filter(allowed=True)
return qs
class ActorViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = models.Actor.objects.select_related(
"user", "channel", "summary_obj", "attachment_icon"
)
permission_classes = [ConditionalAuthentication]
serializer_class = api_serializers.FullActorSerializer
lookup_field = "full_username"
lookup_value_regex = r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"
def get_object(self):
queryset = self.get_queryset()
username, domain = self.kwargs["full_username"].split("@", 1)
return queryset.get(preferred_username=username, domain_id=domain)
def get_queryset(self):
qs = super().get_queryset()
qs = qs.exclude(
domain__instance_policy__is_active=True,
domain__instance_policy__block_all=True,
)
if preferences.get("moderation__allow_list_enabled"):
qs = qs.filter(domain__allowed=True)
return qs
......@@ -224,7 +224,6 @@ class APIActorSerializer(serializers.ModelSerializer):
class Meta:
model = models.Actor
fields = [
"id",
"fid",
"url",
"creation_date",
......
......@@ -229,8 +229,8 @@ class User(AbstractUser):
self.last_activity = now
self.save(update_fields=["last_activity"])
def create_actor(self):
self.actor = create_actor(self)
def create_actor(self, **kwargs):
self.actor = create_actor(self, **kwargs)
self.save(update_fields=["actor"])
return self.actor
......@@ -399,8 +399,9 @@ def get_actor_data(username, **kwargs):
}
def create_actor(user):
def create_actor(user, **kwargs):
args = get_actor_data(user.username)
args.update(kwargs)
private, public = keys.get_key_pair()
args["private_key"] = private.decode("utf-8")
args["public_key"] = public.decode("utf-8")
......
......@@ -90,17 +90,12 @@ class UserActivitySerializer(activity_serializers.ModelSerializer):
class UserBasicSerializer(serializers.ModelSerializer):
avatar = serializers.SerializerMethodField()
avatar = common_serializers.AttachmentSerializer(source="actor.attachment_icon")
class Meta:
model = models.User
fields = ["id", "username", "name", "date_joined", "avatar"]
def get_avatar(self, o):
return common_serializers.AttachmentSerializer(
o.actor.attachment_icon if o.actor else None
).data
class UserWriteSerializer(serializers.ModelSerializer):
summary = common_serializers.ContentSerializer(required=False, allow_null=True)
......@@ -113,6 +108,9 @@ class UserWriteSerializer(serializers.ModelSerializer):
),
write_only=True,
)
avatar = common_serializers.AttachmentSerializer(
source="actor.attachment_icon", read_only=True
)
class Meta:
model = models.User
......@@ -140,19 +138,12 @@ class UserWriteSerializer(serializers.ModelSerializer):
obj.actor.save(update_fields=["attachment_icon"])
return obj
def to_representation(self, obj):
repr = super().to_representation(obj)
repr["avatar"] = common_serializers.AttachmentSerializer(
obj.actor.attachment_icon
).data
return repr
class UserReadSerializer(serializers.ModelSerializer):
permissions = serializers.SerializerMethodField()
full_username = serializers.SerializerMethodField()
avatar = serializers.SerializerMethodField()
avatar = common_serializers.AttachmentSerializer(source="actor.attachment_icon")
class Meta:
model = models.User
......@@ -170,9 +161,6 @@ class UserReadSerializer(serializers.ModelSerializer):
"avatar",
]
def get_avatar(self, o):
return common_serializers.AttachmentSerializer(o.actor.attachment_icon).data
def get_permissions(self, o):
return o.get_permissions()
......
import pytest
from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.federation import api_serializers
from funkwhale_api.federation import serializers
from funkwhale_api.users import serializers as users_serializers
def test_library_serializer(factories, to_api_date):
......@@ -111,3 +113,31 @@ def test_serialize_generic_relation(factory_name, factory_kwargs, expected, fact
obj = factories[factory_name](**factory_kwargs)
expected["type"] = factory_name
assert api_serializers.serialize_generic_relation({}, obj) == expected
def test_api_full_actor_serializer(factories, to_api_date):
summary = factories["common.Content"]()
icon = factories["common.Attachment"]()
user = factories["users.User"]()
actor = user.create_actor(summary_obj=summary, attachment_icon=icon)
expected = {
"fid": actor.fid,
"url": actor.url,
"creation_date": to_api_date(actor.creation_date),
"last_fetch_date": to_api_date(actor.last_fetch_date),
"user": users_serializers.UserBasicSerializer(user).data,
"is_channel": False,
"domain": actor.domain_id,
"type": actor.type,
"manually_approves_followers": actor.manually_approves_followers,
"full_username": actor.full_username,
"name": actor.name,
"preferred_username": actor.preferred_username,
"is_local": actor.is_local,
"summary": common_serializers.ContentSerializer(summary).data,
"icon": common_serializers.AttachmentSerializer(icon).data,
}
serializer = api_serializers.FullActorSerializer(actor)
assert serializer.data == expected
......@@ -197,3 +197,15 @@ def test_user_can_list_domains(factories, api_client, preferences):
"results": [api_serializers.DomainSerializer(allowed).data],
}
assert response.data == expected
def test_can_retrieve_actor(factories, api_client, preferences):
preferences["common__api_authentication_required"] = False
actor = factories["federation.Actor"]()
url = reverse(
"api:v1:federation:actors-detail", kwargs={"full_username": actor.full_username}
)
response = api_client.get(url)
expected = api_serializers.FullActorSerializer(actor).data
assert response.data == expected
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment