diff --git a/api/funkwhale_api/audio/models.py b/api/funkwhale_api/audio/models.py index 2955bc3fa..3fe34f2b1 100644 --- a/api/funkwhale_api/audio/models.py +++ b/api/funkwhale_api/audio/models.py @@ -15,6 +15,8 @@ from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import utils as federation_utils from funkwhale_api.users import models as user_models +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes def empty_dict(): return {} @@ -81,6 +83,7 @@ class Channel(models.Model): return self.actor.fid @property + @extend_schema_field(OpenApiTypes.BOOL) def is_local(self): return self.actor.is_local diff --git a/api/funkwhale_api/audio/serializers.py b/api/funkwhale_api/audio/serializers.py index 50e4fc01f..bf5bef513 100644 --- a/api/funkwhale_api/audio/serializers.py +++ b/api/funkwhale_api/audio/serializers.py @@ -33,6 +33,9 @@ from funkwhale_api.tags import models as tags_models from funkwhale_api.tags import serializers as tags_serializers from funkwhale_api.users import serializers as users_serializers +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from . import categories from . import models @@ -255,6 +258,7 @@ class ChannelSerializer(serializers.ModelSerializer): "downloads_count", ] + @extend_schema_field(OpenApiTypes.OBJECT) def get_artist(self, obj): return music_serializers.serialize_artist_simple(obj.artist) @@ -264,17 +268,21 @@ class ChannelSerializer(serializers.ModelSerializer): data["subscriptions_count"] = self.get_subscriptions_count(obj) return data + @extend_schema_field(OpenApiTypes.INT) def get_subscriptions_count(self, obj): return obj.actor.received_follows.exclude(approved=False).count() + @extend_schema_field(OpenApiTypes.INT) def get_downloads_count(self, obj): return getattr(obj, "_downloads_count", None) or 0 + @extend_schema_field(federation_serializers.APIActorSerializer) def get_actor(self, obj): if obj.attributed_to == actors.get_service_actor(): return None return federation_serializers.APIActorSerializer(obj.actor).data + @extend_schema_field(OpenApiTypes.URI) def get_url(self, obj): return obj.actor.url diff --git a/api/funkwhale_api/common/models.py b/api/funkwhale_api/common/models.py index 9dd19c2ec..29eb4696a 100644 --- a/api/funkwhale_api/common/models.py +++ b/api/funkwhale_api/common/models.py @@ -18,6 +18,9 @@ from django.urls import reverse from versatileimagefield.fields import VersatileImageField from versatileimagefield.image_warmer import VersatileImageFieldWarmer +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from funkwhale_api.federation import utils as federation_utils from . import utils @@ -240,6 +243,7 @@ class Attachment(models.Model): return super().save() @property + @extend_schema_field(OpenApiTypes.BOOL) def is_local(self): return federation_utils.is_local(self.fid) diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py index 7735c0f72..9aae6a9fd 100644 --- a/api/funkwhale_api/common/serializers.py +++ b/api/funkwhale_api/common/serializers.py @@ -10,6 +10,9 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from . import models from . import utils @@ -270,6 +273,7 @@ class APIMutationSerializer(serializers.ModelSerializer): "previous_state", ] + @extend_schema_field(OpenApiTypes.OBJECT) def get_target(self, obj): target = obj.target if not target: @@ -292,6 +296,7 @@ class AttachmentSerializer(serializers.Serializer): file = StripExifImageField(write_only=True) urls = serializers.SerializerMethodField() + @extend_schema_field(OpenApiTypes.OBJECT) def get_urls(self, o): urls = {} urls["source"] = o.url @@ -315,6 +320,7 @@ class ContentSerializer(serializers.Serializer): ) html = serializers.SerializerMethodField() + @extend_schema_field(OpenApiTypes.STR) def get_html(self, o): return utils.render_html(o.text, o.content_type) diff --git a/api/funkwhale_api/favorites/serializers.py b/api/funkwhale_api/favorites/serializers.py index dd28dcd07..f54f3fe95 100644 --- a/api/funkwhale_api/favorites/serializers.py +++ b/api/funkwhale_api/favorites/serializers.py @@ -5,6 +5,8 @@ from funkwhale_api.federation import serializers as federation_serializers from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer +from drf_spectacular.utils import extend_schema_field + from . import models @@ -35,6 +37,7 @@ class UserTrackFavoriteSerializer(serializers.ModelSerializer): fields = ("id", "user", "track", "creation_date", "actor") actor = serializers.SerializerMethodField() + @extend_schema_field(federation_serializers.APIActorSerializer) def get_actor(self, obj): actor = obj.user.actor if actor: diff --git a/api/funkwhale_api/federation/api_serializers.py b/api/funkwhale_api/federation/api_serializers.py index 6ff1e1b2b..ab39124d6 100644 --- a/api/funkwhale_api/federation/api_serializers.py +++ b/api/funkwhale_api/federation/api_serializers.py @@ -13,6 +13,9 @@ 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 drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from . import filters from . import models from . import serializers as federation_serializers @@ -62,15 +65,18 @@ class LibrarySerializer(serializers.ModelSerializer): "latest_scan", ] + @extend_schema_field(OpenApiTypes.INT) def get_uploads_count(self, o): return max(getattr(o, "_uploads_count", 0), o.uploads_count) + @extend_schema_field(NestedLibraryFollowSerializer) def get_follow(self, o): try: return NestedLibraryFollowSerializer(o._follows[0]).data except (AttributeError, IndexError): return None + @extend_schema_field(LibraryScanSerializer) def get_latest_scan(self, o): scan = o.scans.order_by("-creation_date").first() if scan: @@ -95,6 +101,7 @@ class LibraryFollowSerializer(serializers.ModelSerializer): raise serializers.ValidationError("You are already following this library") return v + @extend_schema_field(federation_serializers.APIActorSerializer) def get_actor(self, o): return federation_serializers.APIActorSerializer(o.actor).data @@ -135,14 +142,17 @@ class ActivitySerializer(serializers.ModelSerializer): "type", ] + @extend_schema_field(OpenApiTypes.OBJECT) def get_object(self, o): if o.object: return serialize_generic_relation(o, o.object) + @extend_schema_field(OpenApiTypes.OBJECT) def get_related_object(self, o): if o.related_object: return serialize_generic_relation(o, o.related_object) + @extend_schema_field(OpenApiTypes.OBJECT) def get_target(self, o): if o.target: return serialize_generic_relation(o, o.target) @@ -268,6 +278,7 @@ class FullActorSerializer(serializers.Serializer): summary = common_serializers.ContentSerializer(source="summary_obj") icon = common_serializers.AttachmentSerializer(source="attachment_icon") + @extend_schema_field(OpenApiTypes.BOOL) def get_is_channel(self, o): try: return bool(o.channel) diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index d9ea20180..3ca50ed12 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -19,6 +19,9 @@ from funkwhale_api.common import utils as common_utils from funkwhale_api.common import validators as common_validators from funkwhale_api.music import utils as music_utils +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from . import utils as federation_utils TYPE_CHOICES = [ @@ -48,6 +51,7 @@ class FederationMixin(models.Model): abstract = True @property + @extend_schema_field(OpenApiTypes.BOOL) def is_local(self): return federation_utils.is_local(self.fid) @@ -172,6 +176,7 @@ class Domain(models.Model): return data @property + @extend_schema_field(OpenApiTypes.BOOL) def is_local(self): return self.name == settings.FEDERATION_HOSTNAME @@ -232,6 +237,7 @@ class Actor(models.Model): return "{}#main-key".format(self.fid) @property + @extend_schema_field(OpenApiTypes.STR) def full_username(self): return "{}@{}".format(self.preferred_username, self.domain_id) @@ -239,13 +245,16 @@ class Actor(models.Model): return "{}@{}".format(self.preferred_username, self.domain_id) @property - def is_local(self): + @extend_schema_field(OpenApiTypes.BOOL) + def is_local(self) -> bool: return self.domain_id == settings.FEDERATION_HOSTNAME + @extend_schema_field({'type': 'array', 'items': {'type': 'object'}}) def get_approved_followers(self): follows = self.received_follows.filter(approved=True) return self.followers.filter(pk__in=follows.values_list("actor", flat=True)) + @extend_schema_field(OpenApiTypes.BOOL) def should_autoapprove_follow(self, actor): if self.get_channel(): return True diff --git a/api/funkwhale_api/federation/utils.py b/api/funkwhale_api/federation/utils.py index 9dce78246..7bbc231a1 100644 --- a/api/funkwhale_api/federation/utils.py +++ b/api/funkwhale_api/federation/utils.py @@ -11,6 +11,9 @@ from django.db.models import CharField, Q, Value from funkwhale_api.common import session from funkwhale_api.moderation import mrf +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from . import exceptions from . import signing @@ -138,7 +141,7 @@ def local_qs(queryset, url_field="fid", include=True): query = ~query return queryset.filter(query) - +@extend_schema_field(OpenApiTypes.BOOL) def is_local(url): if not url: return True diff --git a/api/funkwhale_api/history/serializers.py b/api/funkwhale_api/history/serializers.py index c894ec59a..b275ff806 100644 --- a/api/funkwhale_api/history/serializers.py +++ b/api/funkwhale_api/history/serializers.py @@ -5,6 +5,8 @@ from funkwhale_api.federation import serializers as federation_serializers from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer +from drf_spectacular.utils import extend_schema_field + from . import models @@ -39,6 +41,7 @@ class ListeningSerializer(serializers.ModelSerializer): return super().create(validated_data) + @extend_schema_field(federation_serializers.APIActorSerializer) def get_actor(self, obj): actor = obj.user.actor if actor: diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py index de7d7d63b..882fdb114 100644 --- a/api/funkwhale_api/manage/serializers.py +++ b/api/funkwhale_api/manage/serializers.py @@ -18,6 +18,9 @@ from funkwhale_api.music import serializers as music_serializers from funkwhale_api.tags import models as tags_models from funkwhale_api.users import models as users_models +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from . import filters @@ -90,6 +93,7 @@ class ManageUserSerializer(serializers.ModelSerializer): ) return instance + @extend_schema_field(OpenApiTypes.OBJECT) def get_actor(self, obj): if obj.actor: return ManageBaseActorSerializer(obj.actor).data @@ -151,9 +155,11 @@ class ManageDomainSerializer(serializers.ModelSerializer): "nodeinfo_fetch_date", ] + @extend_schema_field(OpenApiTypes.INT) def get_actors_count(self, o): return getattr(o, "actors_count", 0) + @extend_schema_field(OpenApiTypes.INT) def get_outbox_activities_count(self, o): return getattr(o, "outbox_activities_count", 0) @@ -211,6 +217,7 @@ class ManageBaseActorSerializer(serializers.ModelSerializer): ] read_only_fields = ["creation_date", "instance_policy"] + @extend_schema_field(OpenApiTypes.BOOL) def get_is_local(self, o): return o.domain_id == settings.FEDERATION_HOSTNAME @@ -228,6 +235,7 @@ class ManageActorSerializer(ManageBaseActorSerializer): ] read_only_fields = ["creation_date", "instance_policy"] + @extend_schema_field(OpenApiTypes.INT) def get_uploads_count(self, o): return getattr(o, "uploads_count", 0) @@ -353,6 +361,7 @@ class ManageBaseAlbumSerializer(serializers.ModelSerializer): "tracks_count", ] + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, o): return getattr(o, "_tracks_count", None) @@ -385,6 +394,7 @@ class ManageNestedAlbumSerializer(ManageBaseAlbumSerializer): model = music_models.Album fields = ManageBaseAlbumSerializer.Meta.fields + ["tracks_count"] + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, obj): return getattr(obj, "tracks_count", None) @@ -411,16 +421,20 @@ class ManageArtistSerializer( "content_category", ] + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, obj): return getattr(obj, "_tracks_count", None) + @extend_schema_field(OpenApiTypes.INT) def get_albums_count(self, obj): return getattr(obj, "_albums_count", None) + @extend_schema_field({'type': 'array', 'items': {'type': 'string'}}) def get_tags(self, obj): tagged_items = getattr(obj, "_prefetched_tagged_items", []) return [ti.tag.name for ti in tagged_items] + @extend_schema_field(OpenApiTypes.STR) def get_channel(self, obj): if "channel" in obj._state.fields_cache and obj.get_channel(): return str(obj.channel.uuid) @@ -446,9 +460,11 @@ class ManageAlbumSerializer( "tracks_count", ] + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, o): return len(o.tracks.all()) + @extend_schema_field({'type': 'array', 'items': {'type': 'string'}}) def get_tags(self, obj): tagged_items = getattr(obj, "_prefetched_tagged_items", []) return [ti.tag.name for ti in tagged_items] @@ -483,9 +499,11 @@ class ManageTrackSerializer( "cover", ] + @extend_schema_field(OpenApiTypes.INT) def get_uploads_count(self, obj): return getattr(obj, "uploads_count", None) + @extend_schema_field({'type': 'array', 'items': {'type': 'string'}}) def get_tags(self, obj): tagged_items = getattr(obj, "_prefetched_tagged_items", []) return [ti.tag.name for ti in tagged_items] @@ -570,9 +588,11 @@ class ManageLibrarySerializer(serializers.ModelSerializer): "creation_date", ] + @extend_schema_field(OpenApiTypes.INT) def get_uploads_count(self, obj): return getattr(obj, "_uploads_count", obj.uploads_count) + @extend_schema_field(OpenApiTypes.INT) def get_followers_count(self, obj): return getattr(obj, "followers_count", None) @@ -652,12 +672,15 @@ class ManageTagSerializer(ManageBaseAlbumSerializer): "artists_count", ] + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, obj): return getattr(obj, "_tracks_count", None) + @extend_schema_field(OpenApiTypes.INT) def get_albums_count(self, obj): return getattr(obj, "_albums_count", None) + @extend_schema_field(OpenApiTypes.INT) def get_artists_count(self, obj): return getattr(obj, "_artists_count", None) @@ -728,6 +751,7 @@ class ManageReportSerializer(serializers.ModelSerializer): "summary", ] + @extend_schema_field(ManageBaseNoteSerializer) def get_notes(self, o): notes = getattr(o, "_prefetched_notes", []) return ManageBaseNoteSerializer(notes, many=True).data @@ -761,6 +785,7 @@ class ManageUserRequestSerializer(serializers.ModelSerializer): "metadata", ] + @extend_schema_field(ManageBaseNoteSerializer) def get_notes(self, o): notes = getattr(o, "_prefetched_notes", []) return ManageBaseNoteSerializer(notes, many=True).data diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index 82464e40a..c98c48b41 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -35,6 +35,9 @@ from funkwhale_api.federation import utils as federation_utils from funkwhale_api.tags import models as tags_models from . import importers, metadata, utils +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + logger = logging.getLogger(__name__) MAX_LENGTHS = { @@ -137,6 +140,7 @@ class APIModelMixin(models.Model): return super().save(**kwargs) @property + @extend_schema_field(OpenApiTypes.BOOL) def is_local(self): return federation_utils.is_local(self.fid) @@ -148,6 +152,7 @@ class APIModelMixin(models.Model): parsed = urllib.parse.urlparse(self.fid) return parsed.hostname + @extend_schema_field({'type': 'array', 'items': {'type': 'string'}}) def get_tags(self): return list(sorted(self.tagged_items.values_list("tag__name", flat=True))) @@ -652,7 +657,7 @@ class Track(APIModelMixin): ) @property - def listen_url(self): + def listen_url(self) -> str: # Not using reverse because this is slow return "/api/v1/listen/{}/".format(self.uuid) @@ -782,6 +787,7 @@ class Upload(models.Model): objects = UploadQuerySet.as_manager() @property + @extend_schema_field(OpenApiTypes.BOOL) def is_local(self): return federation_utils.is_local(self.fid) @@ -834,7 +840,7 @@ class Upload(models.Model): ) @property - def filename(self): + def filename(self) -> str: return "{}.{}".format(self.track.full_name, self.extension) @property @@ -910,10 +916,10 @@ class Upload(models.Model): return metadata.Metadata(audio_file) @property - def listen_url(self): + def listen_url(self) -> str: return self.track.listen_url + "?upload={}".format(self.uuid) - def get_listen_url(self, to=None, download=True): + def get_listen_url(self, to=None, download=True) -> str: url = self.listen_url if to: url += "&to={}".format(to) @@ -1019,7 +1025,7 @@ class UploadVersion(models.Model): unique_together = ("upload", "mimetype", "bitrate") @property - def filename(self): + def filename(self) -> str: try: return ( self.upload.track.full_name @@ -1211,15 +1217,15 @@ class Library(federation_models.FederationMixin): def __str__(self): return self.name - def get_moderation_url(self): + def get_moderation_url(self) -> str: return "/manage/library/libraries/{}".format(self.uuid) - def get_federation_id(self): + def get_federation_id(self) -> str: return federation_utils.full_url( reverse("federation:music:libraries-detail", kwargs={"uuid": self.uuid}) ) - def get_absolute_url(self): + def get_absolute_url(self) -> str: return "/library/{}".format(self.uuid) def save(self, **kwargs): @@ -1229,7 +1235,7 @@ class Library(federation_models.FederationMixin): return super().save(**kwargs) - def should_autoapprove_follow(self, actor): + def should_autoapprove_follow(self, actor) -> bool: if self.privacy_level == "everyone": return True if self.privacy_level == "instance" and actor.get_user(): diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py index df4e70133..944d5801b 100644 --- a/api/funkwhale_api/music/serializers.py +++ b/api/funkwhale_api/music/serializers.py @@ -17,6 +17,9 @@ from funkwhale_api.tags import serializers as tags_serializers from . import filters, models, tasks, utils +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + NOOP = object() COVER_WRITE_FIELD = common_serializers.RelatedField( @@ -38,7 +41,7 @@ class CoverField(common_serializers.AttachmentSerializer): cover_field = CoverField() - +@extend_schema_field(OpenApiTypes.OBJECT) def serialize_attributed_to(self, obj): # Import at runtime to avoid a circular import issue from funkwhale_api.federation import serializers as federation_serializers @@ -74,6 +77,7 @@ class LicenseSerializer(serializers.Serializer): attribution = serializers.BooleanField() copyleft = serializers.BooleanField() + @extend_schema_field(OpenApiTypes.STR) def get_id(self, obj): return obj["identifiers"][0] @@ -94,12 +98,15 @@ class ArtistAlbumSerializer(serializers.Serializer): release_date = serializers.DateField() creation_date = serializers.DateTimeField() + @extend_schema_field(OpenApiTypes.INT) def get_artist(self, o): return o.artist_id + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, o): return len(o.tracks.all()) + @extend_schema_field(OpenApiTypes.BOOL) def get_is_playable(self, obj): try: return bool(obj.is_playable_by_actor) @@ -125,16 +132,19 @@ class ArtistWithAlbumsSerializer(OptionalDescriptionMixin, serializers.Serialize is_local = serializers.BooleanField() cover = cover_field + @extend_schema_field({'type': 'array', 'items': {'type': 'string'}}) def get_tags(self, obj): tagged_items = getattr(obj, "_prefetched_tagged_items", []) return [ti.tag.name for ti in tagged_items] get_attributed_to = serialize_attributed_to + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, o): tracks = getattr(o, "_prefetched_tracks", None) return len(tracks) if tracks else None + @extend_schema_field(OpenApiTypes.OBJECT) def get_channel(self, o): channel = o.get_channel() if not channel: @@ -205,12 +215,11 @@ class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer): get_attributed_to = serialize_attributed_to - def get_artist(self, o): - return serialize_artist_simple(o.artist) - + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, o): return len(o.tracks.all()) + @extend_schema_field(OpenApiTypes.BOOL) def get_is_playable(self, obj): try: return any( @@ -222,10 +231,12 @@ class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer): except AttributeError: return None + @extend_schema_field({'type': 'array', 'items': {'type': 'string'}}) def get_tags(self, obj): tagged_items = getattr(obj, "_prefetched_tagged_items", []) return [ti.tag.name for ti in tagged_items] + @extend_schema_field(OpenApiTypes.INT) def get_duration(self, obj): try: return obj.duration @@ -239,6 +250,7 @@ class TrackAlbumSerializer(serializers.ModelSerializer): cover = cover_field tracks_count = serializers.SerializerMethodField() + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, o): return getattr(o, "_prefetched_tracks_count", len(o.tracks.all())) @@ -257,10 +269,7 @@ class TrackAlbumSerializer(serializers.ModelSerializer): "tracks_count", ) - def get_artist(self, o): - return serialize_artist_simple(o.artist) - - +@extend_schema_field(OpenApiTypes.OBJECT) def serialize_upload(upload): return { "uuid": str(upload.uuid), @@ -314,12 +323,11 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer): get_attributed_to = serialize_attributed_to is_playable = serializers.SerializerMethodField() - def get_artist(self, o): - return serialize_artist_simple(o.artist) - + @extend_schema_field(OpenApiTypes.URI) def get_listen_url(self, obj): return obj.listen_url + @extend_schema_field({'type': 'array', 'items': {'type': 'object'}}) def get_uploads(self, obj): uploads = getattr(obj, "playable_uploads", []) # we put local uploads first @@ -327,13 +335,16 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer): uploads = sorted(uploads, key=lambda u: u["is_local"], reverse=True) return list(uploads) + @extend_schema_field({'type': 'array', 'items': {'type': 'str'}}) def get_tags(self, obj): tagged_items = getattr(obj, "_prefetched_tagged_items", []) return [ti.tag.name for ti in tagged_items] + @extend_schema_field(OpenApiTypes.STR) def get_license(self, o): return o.license_id + @extend_schema_field(OpenApiTypes.BOOL) def get_is_playable(self, obj): return bool(getattr(obj, "playable_uploads", [])) @@ -359,9 +370,11 @@ class LibraryForOwnerSerializer(serializers.ModelSerializer): ] read_only_fields = ["fid", "uuid", "creation_date", "actor"] + @extend_schema_field(OpenApiTypes.INT) def get_uploads_count(self, o): return getattr(o, "_uploads_count", o.uploads_count) + @extend_schema_field(OpenApiTypes.INT) def get_size(self, o): return getattr(o, "_size", 0) @@ -370,6 +383,7 @@ class LibraryForOwnerSerializer(serializers.ModelSerializer): {"type": "Update", "object": {"type": "Library"}}, context={"library": obj} ) + @extend_schema_field(OpenApiTypes.OBJECT) def get_actor(self, o): # Import at runtime to avoid a circular import issue from funkwhale_api.federation import serializers as federation_serializers diff --git a/api/funkwhale_api/playlists/serializers.py b/api/funkwhale_api/playlists/serializers.py index 07a7298a7..e75fbec2b 100644 --- a/api/funkwhale_api/playlists/serializers.py +++ b/api/funkwhale_api/playlists/serializers.py @@ -5,6 +5,9 @@ from funkwhale_api.music.models import Track from funkwhale_api.music.serializers import TrackSerializer from funkwhale_api.users.serializers import UserBasicSerializer +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from . import models @@ -46,17 +49,20 @@ class PlaylistSerializer(serializers.ModelSerializer): ) read_only_fields = ["id", "modification_date", "creation_date"] + @extend_schema_field(federation_serializers.APIActorSerializer) def get_actor(self, obj): actor = obj.user.actor if actor: return federation_serializers.APIActorSerializer(actor).data + @extend_schema_field(OpenApiTypes.BOOL) def get_is_playable(self, obj): try: return bool(obj.playable_plts) except AttributeError: return None + @extend_schema_field(OpenApiTypes.INT) def get_tracks_count(self, obj): try: return obj.tracks_count @@ -64,6 +70,7 @@ class PlaylistSerializer(serializers.ModelSerializer): # no annotation? return obj.playlist_tracks.count() + @extend_schema_field(OpenApiTypes.INT) def get_duration(self, obj): try: return obj.duration @@ -71,6 +78,7 @@ class PlaylistSerializer(serializers.ModelSerializer): # no annotation? return 0 + @extend_schema_field({'type': 'array', 'items': {'type': 'uri'}}) def get_album_covers(self, obj): try: plts = obj.plts_for_cover diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py index cb431e1aa..36f0583ac 100644 --- a/api/funkwhale_api/users/models.py +++ b/api/funkwhale_api/users/models.py @@ -29,6 +29,8 @@ from funkwhale_api.federation import keys from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import utils as federation_utils +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes def get_token(length=5): wordlist_path = os.path.join( @@ -307,6 +309,7 @@ class User(AbstractUser): return groups + @extend_schema_field(OpenApiTypes.STR) def full_username(self): return "{}@{}".format(self.username, settings.FEDERATION_HOSTNAME) diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py index 317327459..23c2da462 100644 --- a/api/funkwhale_api/users/serializers.py +++ b/api/funkwhale_api/users/serializers.py @@ -21,6 +21,9 @@ from funkwhale_api.moderation import models as moderation_models from funkwhale_api.moderation import tasks as moderation_tasks from funkwhale_api.moderation import utils as moderation_utils +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes + from . import adapters from . import models from . import authentication as users_authentication @@ -205,6 +208,7 @@ class UserReadSerializer(serializers.ModelSerializer): def get_permissions(self, o): return o.get_permissions() + @extend_schema_field(OpenApiTypes.STR) def get_full_username(self, o): if o.actor: return o.actor.full_username