From 393110a7f04b920bfdb882600ddfaae1298116b5 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Fri, 6 Apr 2018 17:58:43 +0200 Subject: [PATCH] Serializers for paginated collections --- api/config/settings/common.py | 3 + api/funkwhale_api/federation/serializers.py | 66 +++++++++++++++++++ api/tests/federation/test_serializers.py | 72 +++++++++++++++++++++ 3 files changed, 141 insertions(+) diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 6a85a934..e45f6c25 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -30,6 +30,9 @@ FUNKWHALE_HOSTNAME = urlsplit(FUNKWHALE_URL).netloc FEDERATION_ENABLED = env.bool('FEDERATION_ENABLED', default=True) FEDERATION_HOSTNAME = env('FEDERATION_HOSTNAME', default=FUNKWHALE_HOSTNAME) +FEDERATION_COLLECTION_PAGE_SIZE = env.int( + 'FEDERATION_COLLECTION_PAGE_SIZE', default=50 +) FEDERATION_MUSIC_NEEDS_APPROVAL = env.bool( 'FEDERATION_MUSIC_NEEDS_APPROVAL', default=True ) diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index 075e253d..05e9c7c8 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -2,10 +2,13 @@ import urllib.parse from django.urls import reverse from django.conf import settings +from django.core.paginator import Paginator from rest_framework import serializers from dynamic_preferences.registries import global_preferences_registry +from funkwhale_api.common.utils import set_query_parameter + from . import activity from . import models from . import utils @@ -199,3 +202,66 @@ OBJECT_SERIALIZERS = { t: ObjectSerializer for t in activity.OBJECT_TYPES } + + +class PaginatedCollectionSerializer(serializers.Serializer): + + def to_representation(self, conf): + paginator = Paginator( + conf['items'], + conf.get('page_size', 20) + ) + first = set_query_parameter(conf['id'], page=1) + current = first + last = set_query_parameter(conf['id'], page=paginator.num_pages) + d = { + 'id': conf['id'], + 'actor': conf['actor'].url, + 'totalItems': paginator.count, + 'type': 'Collection', + 'current': current, + 'first': first, + 'last': last, + } + if self.context.get('include_ap_context', True): + d['@context'] = AP_CONTEXT + return d + + +class CollectionPageSerializer(serializers.Serializer): + + def to_representation(self, conf): + page = conf['page'] + first = set_query_parameter(conf['id'], page=1) + last = set_query_parameter(conf['id'], page=page.paginator.num_pages) + id = set_query_parameter(conf['id'], page=page.number) + d = { + 'id': id, + 'partOf': conf['id'], + 'actor': conf['actor'].url, + 'totalItems': page.paginator.count, + 'type': 'CollectionPage', + 'first': first, + 'last': last, + 'items': [ + conf['item_serializer']( + i, + context={ + 'actor': conf['actor'], + 'include_ap_context': False} + ).data + for i in page.object_list + ] + } + + if page.has_previous(): + d['prev'] = set_query_parameter( + conf['id'], page=page.previous_page_number()) + + if page.has_previous(): + d['next'] = set_query_parameter( + conf['id'], page=page.next_page_number()) + + if self.context.get('include_ap_context', True): + d['@context'] = AP_CONTEXT + return d diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py index 6d027ec9..1e580040 100644 --- a/api/tests/federation/test_serializers.py +++ b/api/tests/federation/test_serializers.py @@ -1,8 +1,10 @@ from django.urls import reverse +from django.core.paginator import Paginator from funkwhale_api.federation import keys from funkwhale_api.federation import models from funkwhale_api.federation import serializers +from funkwhale_api.music.serializers import AudioSerializer def test_actor_serializer_from_ap(db): @@ -163,3 +165,73 @@ def test_follow_serializer_to_ap(factories): } assert serializer.data == expected + + +def test_paginated_collection_serializer(factories): + tfs = factories['music.TrackFile'].create_batch(size=5) + actor = factories['federation.Actor'](local=True) + + conf = { + 'id': 'https://test.federation/test', + 'items': tfs, + 'item_serializer': AudioSerializer, + 'actor': actor, + 'page_size': 2, + } + expected = { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + {}, + ], + 'type': 'Collection', + 'id': conf['id'], + 'actor': actor.url, + 'totalItems': len(tfs), + 'current': conf['id'] + '?page=1', + 'last': conf['id'] + '?page=3', + 'first': conf['id'] + '?page=1', + } + + serializer = serializers.PaginatedCollectionSerializer(conf) + + assert serializer.data == expected + + +def test_collection_page_serializer(factories): + tfs = factories['music.TrackFile'].create_batch(size=5) + actor = factories['federation.Actor'](local=True) + + conf = { + 'id': 'https://test.federation/test', + 'item_serializer': AudioSerializer, + 'actor': actor, + 'page': Paginator(tfs, 2).page(2), + } + expected = { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + {}, + ], + 'type': 'CollectionPage', + 'id': conf['id'] + '?page=2', + 'actor': actor.url, + 'totalItems': len(tfs), + 'partOf': conf['id'], + 'prev': conf['id'] + '?page=1', + 'next': conf['id'] + '?page=3', + 'first': conf['id'] + '?page=1', + 'last': conf['id'] + '?page=3', + 'items': [ + conf['item_serializer']( + i, + context={'actor': actor, 'include_ap_context': False} + ).data + for i in conf['page'].object_list + ] + } + + serializer = serializers.CollectionPageSerializer(conf) + + assert serializer.data == expected -- GitLab