We now have a library browsable via activitypub
This commit is contained in:
parent
393110a7f0
commit
a03f0ffea5
|
@ -50,6 +50,11 @@ class SystemActor(object):
|
||||||
additional_attributes = {}
|
additional_attributes = {}
|
||||||
manually_approves_followers = False
|
manually_approves_followers = False
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
actor = self.get_actor_instance()
|
||||||
|
serializer = serializers.ActorSerializer()
|
||||||
|
return serializer.data
|
||||||
|
|
||||||
def get_actor_instance(self):
|
def get_actor_instance(self):
|
||||||
args = self.get_instance_argument(
|
args = self.get_instance_argument(
|
||||||
self.id,
|
self.id,
|
||||||
|
@ -172,6 +177,17 @@ class LibraryActor(SystemActor):
|
||||||
'manually_approves_followers': True
|
'manually_approves_followers': True
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
data = super().serialize()
|
||||||
|
urls = data.setdefault('url', [])
|
||||||
|
urls.append({
|
||||||
|
'type': 'Link',
|
||||||
|
'mediaType': 'application/activity+json',
|
||||||
|
'name': 'library',
|
||||||
|
'href': utils.full_url(reverse('federation:music:files-list'))
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def manually_approves_followers(self):
|
def manually_approves_followers(self):
|
||||||
return settings.FEDERATION_MUSIC_NEEDS_APPROVAL
|
return settings.FEDERATION_MUSIC_NEEDS_APPROVAL
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from rest_framework import routers
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
|
from rest_framework import routers
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
router = routers.SimpleRouter(trailing_slash=False)
|
router = routers.SimpleRouter(trailing_slash=False)
|
||||||
|
music_router = routers.SimpleRouter(trailing_slash=False)
|
||||||
router.register(
|
router.register(
|
||||||
r'federation/instance/actors',
|
r'federation/instance/actors',
|
||||||
views.InstanceActorViewSet,
|
views.InstanceActorViewSet,
|
||||||
|
@ -12,4 +14,11 @@ router.register(
|
||||||
views.WellKnownViewSet,
|
views.WellKnownViewSet,
|
||||||
'well-known')
|
'well-known')
|
||||||
|
|
||||||
urlpatterns = router.urls
|
music_router.register(
|
||||||
|
r'federation/files',
|
||||||
|
views.MusicFilesViewSet,
|
||||||
|
'files',
|
||||||
|
)
|
||||||
|
urlpatterns = router.urls + [
|
||||||
|
url('music/', include((music_router.urls, 'music'), namespace='music'))
|
||||||
|
]
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core import paginator
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework import views
|
from rest_framework import views
|
||||||
from rest_framework import response
|
from rest_framework import response
|
||||||
from rest_framework.decorators import list_route, detail_route
|
from rest_framework.decorators import list_route, detail_route
|
||||||
|
|
||||||
|
from funkwhale_api.music.models import TrackFile
|
||||||
|
from funkwhale_api.music.serializers import AudioSerializer
|
||||||
|
|
||||||
from . import actors
|
from . import actors
|
||||||
from . import authentication
|
from . import authentication
|
||||||
|
from . import permissions
|
||||||
from . import renderers
|
from . import renderers
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
from . import utils
|
||||||
from . import webfinger
|
from . import webfinger
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,8 +45,8 @@ class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
system_actor = self.get_object()
|
system_actor = self.get_object()
|
||||||
actor = system_actor.get_actor_instance()
|
actor = system_actor.get_actor_instance()
|
||||||
serializer = serializers.ActorSerializer(actor)
|
data = actor.system_conf.serialize()
|
||||||
return response.Response(serializer.data, status=200)
|
return response.Response(data, status=200)
|
||||||
|
|
||||||
@detail_route(methods=['get', 'post'])
|
@detail_route(methods=['get', 'post'])
|
||||||
def inbox(self, request, *args, **kwargs):
|
def inbox(self, request, *args, **kwargs):
|
||||||
|
@ -101,3 +108,47 @@ class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet):
|
||||||
username, hostname = clean_result
|
username, hostname = clean_result
|
||||||
actor = actors.SYSTEM_ACTORS[username].get_actor_instance()
|
actor = actors.SYSTEM_ACTORS[username].get_actor_instance()
|
||||||
return serializers.ActorWebfingerSerializer(actor).data
|
return serializers.ActorWebfingerSerializer(actor).data
|
||||||
|
|
||||||
|
|
||||||
|
class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
|
||||||
|
authentication_classes = [
|
||||||
|
authentication.SignatureAuthentication]
|
||||||
|
permission_classes = [permissions.LibraryFollower]
|
||||||
|
renderer_classes = [renderers.ActivityPubRenderer]
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
page = request.GET.get('page')
|
||||||
|
library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
|
||||||
|
qs = TrackFile.objects.order_by('-creation_date')
|
||||||
|
if page is None:
|
||||||
|
conf = {
|
||||||
|
'id': utils.full_url(reverse('federation:music:files-list')),
|
||||||
|
'page_size': settings.FEDERATION_COLLECTION_PAGE_SIZE,
|
||||||
|
'items': qs,
|
||||||
|
'item_serializer': AudioSerializer,
|
||||||
|
'actor': library,
|
||||||
|
}
|
||||||
|
serializer = serializers.PaginatedCollectionSerializer(conf)
|
||||||
|
data = serializer.data
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
page_number = int(page)
|
||||||
|
except:
|
||||||
|
return response.Response(
|
||||||
|
{'page': ['Invalid page number']}, status=400)
|
||||||
|
p = paginator.Paginator(
|
||||||
|
qs, settings.FEDERATION_COLLECTION_PAGE_SIZE)
|
||||||
|
try:
|
||||||
|
page = p.page(page_number)
|
||||||
|
except paginator.EmptyPage:
|
||||||
|
return response.Response(status=404)
|
||||||
|
conf = {
|
||||||
|
'id': utils.full_url(reverse('federation:music:files-list')),
|
||||||
|
'page': page,
|
||||||
|
'item_serializer': AudioSerializer,
|
||||||
|
'actor': library,
|
||||||
|
}
|
||||||
|
serializer = serializers.CollectionPageSerializer(conf)
|
||||||
|
data = serializer.data
|
||||||
|
|
||||||
|
return response.Response(data)
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from funkwhale_api.federation import actors
|
from funkwhale_api.federation import actors
|
||||||
from funkwhale_api.federation import serializers
|
from funkwhale_api.federation import serializers
|
||||||
|
from funkwhale_api.federation import utils
|
||||||
from funkwhale_api.federation import webfinger
|
from funkwhale_api.federation import webfinger
|
||||||
|
from funkwhale_api.music.serializers import AudioSerializer
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
|
@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
|
||||||
|
@ -62,3 +64,89 @@ def test_wellknown_webfinger_system(
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response['Content-Type'] == 'application/jrd+json'
|
assert response['Content-Type'] == 'application/jrd+json'
|
||||||
assert response.data == serializer.data
|
assert response.data == serializer.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_audio_file_list_requires_authenticated_actor(
|
||||||
|
db, settings, api_client):
|
||||||
|
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True
|
||||||
|
url = reverse('federation:music:files-list')
|
||||||
|
response = api_client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
def test_audio_file_list_actor_no_page(
|
||||||
|
db, settings, api_client, factories):
|
||||||
|
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False
|
||||||
|
settings.FEDERATION_COLLECTION_PAGE_SIZE = 2
|
||||||
|
library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
|
||||||
|
tfs = factories['music.TrackFile'].create_batch(size=5)
|
||||||
|
conf = {
|
||||||
|
'id': utils.full_url(reverse('federation:music:files-list')),
|
||||||
|
'page_size': 2,
|
||||||
|
'items': list(reversed(tfs)), # we order by -creation_date
|
||||||
|
'item_serializer': AudioSerializer,
|
||||||
|
'actor': library
|
||||||
|
}
|
||||||
|
expected = serializers.PaginatedCollectionSerializer(conf).data
|
||||||
|
url = reverse('federation:music:files-list')
|
||||||
|
response = api_client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.data == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_audio_file_list_actor_page(
|
||||||
|
db, settings, api_client, factories):
|
||||||
|
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False
|
||||||
|
settings.FEDERATION_COLLECTION_PAGE_SIZE = 2
|
||||||
|
library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
|
||||||
|
tfs = factories['music.TrackFile'].create_batch(size=5)
|
||||||
|
conf = {
|
||||||
|
'id': utils.full_url(reverse('federation:music:files-list')),
|
||||||
|
'page': Paginator(list(reversed(tfs)), 2).page(2),
|
||||||
|
'item_serializer': AudioSerializer,
|
||||||
|
'actor': library
|
||||||
|
}
|
||||||
|
expected = serializers.CollectionPageSerializer(conf).data
|
||||||
|
url = reverse('federation:music:files-list')
|
||||||
|
response = api_client.get(url, data={'page': 2})
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.data == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_audio_file_list_actor_page_error(
|
||||||
|
db, settings, api_client, factories):
|
||||||
|
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False
|
||||||
|
url = reverse('federation:music:files-list')
|
||||||
|
response = api_client.get(url, data={'page': 'nope'})
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_audio_file_list_actor_page_error_too_far(
|
||||||
|
db, settings, api_client, factories):
|
||||||
|
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False
|
||||||
|
url = reverse('federation:music:files-list')
|
||||||
|
response = api_client.get(url, data={'page': 5000})
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_actor_includes_library_link(db, settings, api_client):
|
||||||
|
actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
|
||||||
|
url = reverse(
|
||||||
|
'federation:instance-actors-detail',
|
||||||
|
kwargs={'actor': 'library'})
|
||||||
|
response = api_client.get(url)
|
||||||
|
expected_links = [
|
||||||
|
{
|
||||||
|
'type': 'Link',
|
||||||
|
'name': 'library',
|
||||||
|
'mediaType': 'application/activity+json',
|
||||||
|
'href': utils.full_url(reverse('federation:music:files-list'))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.data['url'] == expected_links
|
||||||
|
|
Loading…
Reference in New Issue