Fix OpenAPI generation
This commit is contained in:
parent
0e3a77bc0e
commit
d9cfa167c6
|
@ -1,4 +1,5 @@
|
||||||
from drf_spectacular.contrib.django_oauth_toolkit import OpenApiAuthenticationExtension
|
from drf_spectacular.contrib.django_oauth_toolkit import OpenApiAuthenticationExtension
|
||||||
|
from drf_spectacular.plumbing import build_bearer_security_scheme_object
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,11 +29,24 @@ class CustomOAuthExt(OpenApiAuthenticationExtension):
|
||||||
return {"type": "oauth2", "flows": flows}
|
return {"type": "oauth2", "flows": flows}
|
||||||
|
|
||||||
|
|
||||||
|
class CustomApplicationTokenExt(OpenApiAuthenticationExtension):
|
||||||
|
target_class = "funkwhale_api.common.authentication.ApplicationTokenAuthentication"
|
||||||
|
name = "ApplicationToken"
|
||||||
|
|
||||||
|
def get_security_definition(self, auto_schema):
|
||||||
|
return build_bearer_security_scheme_object(
|
||||||
|
header_name="Authorization",
|
||||||
|
token_prefix="Bearer",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def custom_preprocessing_hook(endpoints):
|
def custom_preprocessing_hook(endpoints):
|
||||||
filtered = []
|
filtered = []
|
||||||
# your modifications to the list of operations that are exposed in the schema
|
# your modifications to the list of operations that are exposed in the schema
|
||||||
api_type = os.environ["API_TYPE"]
|
api_type = os.environ.get("API_TYPE", "v1")
|
||||||
for (path, path_regex, method, callback) in endpoints:
|
for (path, path_regex, method, callback) in endpoints:
|
||||||
|
if path.startswith("/api/v1/providers"):
|
||||||
|
continue
|
||||||
if path.startswith(f"/api/{api_type}"):
|
if path.startswith(f"/api/{api_type}"):
|
||||||
filtered.append((path, path_regex, method, callback))
|
filtered.append((path, path_regex, method, callback))
|
||||||
return filtered
|
return filtered
|
||||||
|
|
|
@ -132,6 +132,12 @@ SPECTACULAR_SETTINGS = {
|
||||||
"OAUTH2_AUTHORIZATION_URL": "/authorize",
|
"OAUTH2_AUTHORIZATION_URL": "/authorize",
|
||||||
"OAUTH2_TOKEN_URL": "/api/v1/oauth/token/",
|
"OAUTH2_TOKEN_URL": "/api/v1/oauth/token/",
|
||||||
"PREPROCESSING_HOOKS": ["config.schema.custom_preprocessing_hook"],
|
"PREPROCESSING_HOOKS": ["config.schema.custom_preprocessing_hook"],
|
||||||
|
"ENUM_NAME_OVERRIDES": {
|
||||||
|
"FederationChoiceEnum": "funkwhale_api.federation.models.TYPE_CHOICES",
|
||||||
|
"ReportTypeEnum": "funkwhale_api.moderation.models.REPORT_TYPES",
|
||||||
|
"PrivacyLevelEnum": "funkwhale_api.common.fields.PRIVACY_LEVEL_CHOICES",
|
||||||
|
"LibraryPrivacyLevelEnum": "funkwhale_api.music.models.LIBRARY_PRIVACY_LEVEL_CHOICES",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if env.bool("WEAK_PASSWORDS", default=False):
|
if env.bool("WEAK_PASSWORDS", default=False):
|
||||||
|
|
|
@ -81,7 +81,7 @@ class Channel(models.Model):
|
||||||
return self.actor.fid
|
return self.actor.fid
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_local(self):
|
def is_local(self) -> bool:
|
||||||
return self.actor.is_local
|
return self.actor.is_local
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -28,11 +28,14 @@ from funkwhale_api.federation import serializers as federation_serializers
|
||||||
from funkwhale_api.federation import utils as federation_utils
|
from funkwhale_api.federation import utils as federation_utils
|
||||||
from funkwhale_api.moderation import mrf
|
from funkwhale_api.moderation import mrf
|
||||||
from funkwhale_api.music import models as music_models
|
from funkwhale_api.music import models as music_models
|
||||||
from funkwhale_api.music import serializers as music_serializers
|
from funkwhale_api.music.serializers import SimpleArtistSerializer, COVER_WRITE_FIELD
|
||||||
from funkwhale_api.tags import models as tags_models
|
from funkwhale_api.tags import models as tags_models
|
||||||
from funkwhale_api.tags import serializers as tags_serializers
|
from funkwhale_api.tags import serializers as tags_serializers
|
||||||
from funkwhale_api.users import serializers as users_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 categories
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
@ -84,7 +87,7 @@ class ChannelCreateSerializer(serializers.Serializer):
|
||||||
choices=music_models.ARTIST_CONTENT_CATEGORY_CHOICES
|
choices=music_models.ARTIST_CONTENT_CATEGORY_CHOICES
|
||||||
)
|
)
|
||||||
metadata = serializers.DictField(required=False)
|
metadata = serializers.DictField(required=False)
|
||||||
cover = music_serializers.COVER_WRITE_FIELD
|
cover = COVER_WRITE_FIELD
|
||||||
|
|
||||||
def validate(self, validated_data):
|
def validate(self, validated_data):
|
||||||
existing_channels = self.context["actor"].owned_channels.count()
|
existing_channels = self.context["actor"].owned_channels.count()
|
||||||
|
@ -163,7 +166,7 @@ class ChannelUpdateSerializer(serializers.Serializer):
|
||||||
choices=music_models.ARTIST_CONTENT_CATEGORY_CHOICES
|
choices=music_models.ARTIST_CONTENT_CATEGORY_CHOICES
|
||||||
)
|
)
|
||||||
metadata = serializers.DictField(required=False)
|
metadata = serializers.DictField(required=False)
|
||||||
cover = music_serializers.COVER_WRITE_FIELD
|
cover = COVER_WRITE_FIELD
|
||||||
|
|
||||||
def validate(self, validated_data):
|
def validate(self, validated_data):
|
||||||
validated_data = super().validate(validated_data)
|
validated_data = super().validate(validated_data)
|
||||||
|
@ -234,7 +237,7 @@ class ChannelUpdateSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
|
||||||
class ChannelSerializer(serializers.ModelSerializer):
|
class ChannelSerializer(serializers.ModelSerializer):
|
||||||
artist = serializers.SerializerMethodField()
|
artist = SimpleArtistSerializer()
|
||||||
actor = serializers.SerializerMethodField()
|
actor = serializers.SerializerMethodField()
|
||||||
downloads_count = serializers.SerializerMethodField()
|
downloads_count = serializers.SerializerMethodField()
|
||||||
attributed_to = federation_serializers.APIActorSerializer()
|
attributed_to = federation_serializers.APIActorSerializer()
|
||||||
|
@ -255,26 +258,25 @@ class ChannelSerializer(serializers.ModelSerializer):
|
||||||
"downloads_count",
|
"downloads_count",
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_artist(self, obj):
|
|
||||||
return music_serializers.serialize_artist_simple(obj.artist)
|
|
||||||
|
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
data = super().to_representation(obj)
|
data = super().to_representation(obj)
|
||||||
if self.context.get("subscriptions_count"):
|
if self.context.get("subscriptions_count"):
|
||||||
data["subscriptions_count"] = self.get_subscriptions_count(obj)
|
data["subscriptions_count"] = self.get_subscriptions_count(obj)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_subscriptions_count(self, obj):
|
def get_subscriptions_count(self, obj) -> int:
|
||||||
return obj.actor.received_follows.exclude(approved=False).count()
|
return obj.actor.received_follows.exclude(approved=False).count()
|
||||||
|
|
||||||
def get_downloads_count(self, obj):
|
def get_downloads_count(self, obj) -> int:
|
||||||
return getattr(obj, "_downloads_count", None) or 0
|
return getattr(obj, "_downloads_count", None) or 0
|
||||||
|
|
||||||
|
@extend_schema_field(federation_serializers.APIActorSerializer)
|
||||||
def get_actor(self, obj):
|
def get_actor(self, obj):
|
||||||
if obj.attributed_to == actors.get_service_actor():
|
if obj.attributed_to == actors.get_service_actor():
|
||||||
return None
|
return None
|
||||||
return federation_serializers.APIActorSerializer(obj.actor).data
|
return federation_serializers.APIActorSerializer(obj.actor).data
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.URI)
|
||||||
def get_url(self, obj):
|
def get_url(self, obj):
|
||||||
return obj.actor.url
|
return obj.actor.url
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,9 @@ from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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 models
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
@ -270,6 +273,7 @@ class APIMutationSerializer(serializers.ModelSerializer):
|
||||||
"previous_state",
|
"previous_state",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||||
def get_target(self, obj):
|
def get_target(self, obj):
|
||||||
target = obj.target
|
target = obj.target
|
||||||
if not target:
|
if not target:
|
||||||
|
@ -292,6 +296,7 @@ class AttachmentSerializer(serializers.Serializer):
|
||||||
file = StripExifImageField(write_only=True)
|
file = StripExifImageField(write_only=True)
|
||||||
urls = serializers.SerializerMethodField()
|
urls = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||||
def get_urls(self, o):
|
def get_urls(self, o):
|
||||||
urls = {}
|
urls = {}
|
||||||
urls["source"] = o.url
|
urls["source"] = o.url
|
||||||
|
@ -315,7 +320,7 @@ class ContentSerializer(serializers.Serializer):
|
||||||
)
|
)
|
||||||
html = serializers.SerializerMethodField()
|
html = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_html(self, o):
|
def get_html(self, o) -> str:
|
||||||
return utils.render_html(o.text, o.content_type)
|
return utils.render_html(o.text, o.content_type)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,14 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from funkwhale_api.activity import serializers as activity_serializers
|
from funkwhale_api.activity import serializers as activity_serializers
|
||||||
from funkwhale_api.federation import serializers as federation_serializers
|
from funkwhale_api.federation import serializers as federation_serializers
|
||||||
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
|
from funkwhale_api.music.serializers import (
|
||||||
|
TrackActivitySerializer,
|
||||||
|
TrackSerializer,
|
||||||
|
)
|
||||||
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
|
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
|
||||||
|
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,6 +40,7 @@ class UserTrackFavoriteSerializer(serializers.ModelSerializer):
|
||||||
fields = ("id", "user", "track", "creation_date", "actor")
|
fields = ("id", "user", "track", "creation_date", "actor")
|
||||||
actor = serializers.SerializerMethodField()
|
actor = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@extend_schema_field(federation_serializers.APIActorSerializer)
|
||||||
def get_actor(self, obj):
|
def get_actor(self, obj):
|
||||||
actor = obj.user.actor
|
actor = obj.user.actor
|
||||||
if actor:
|
if actor:
|
||||||
|
|
|
@ -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.music import models as music_models
|
||||||
from funkwhale_api.users import serializers as users_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 filters
|
from . import filters
|
||||||
from . import models
|
from . import models
|
||||||
from . import serializers as federation_serializers
|
from . import serializers as federation_serializers
|
||||||
|
@ -62,15 +65,18 @@ class LibrarySerializer(serializers.ModelSerializer):
|
||||||
"latest_scan",
|
"latest_scan",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_uploads_count(self, o):
|
def get_uploads_count(self, o):
|
||||||
return max(getattr(o, "_uploads_count", 0), o.uploads_count)
|
return max(getattr(o, "_uploads_count", 0), o.uploads_count)
|
||||||
|
|
||||||
|
@extend_schema_field(NestedLibraryFollowSerializer)
|
||||||
def get_follow(self, o):
|
def get_follow(self, o):
|
||||||
try:
|
try:
|
||||||
return NestedLibraryFollowSerializer(o._follows[0]).data
|
return NestedLibraryFollowSerializer(o._follows[0]).data
|
||||||
except (AttributeError, IndexError):
|
except (AttributeError, IndexError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@extend_schema_field(LibraryScanSerializer)
|
||||||
def get_latest_scan(self, o):
|
def get_latest_scan(self, o):
|
||||||
scan = o.scans.order_by("-creation_date").first()
|
scan = o.scans.order_by("-creation_date").first()
|
||||||
if scan:
|
if scan:
|
||||||
|
@ -95,6 +101,7 @@ class LibraryFollowSerializer(serializers.ModelSerializer):
|
||||||
raise serializers.ValidationError("You are already following this library")
|
raise serializers.ValidationError("You are already following this library")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
@extend_schema_field(federation_serializers.APIActorSerializer)
|
||||||
def get_actor(self, o):
|
def get_actor(self, o):
|
||||||
return federation_serializers.APIActorSerializer(o.actor).data
|
return federation_serializers.APIActorSerializer(o.actor).data
|
||||||
|
|
||||||
|
@ -135,14 +142,17 @@ class ActivitySerializer(serializers.ModelSerializer):
|
||||||
"type",
|
"type",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||||
def get_object(self, o):
|
def get_object(self, o):
|
||||||
if o.object:
|
if o.object:
|
||||||
return serialize_generic_relation(o, o.object)
|
return serialize_generic_relation(o, o.object)
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||||
def get_related_object(self, o):
|
def get_related_object(self, o):
|
||||||
if o.related_object:
|
if o.related_object:
|
||||||
return serialize_generic_relation(o, o.related_object)
|
return serialize_generic_relation(o, o.related_object)
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||||
def get_target(self, o):
|
def get_target(self, o):
|
||||||
if o.target:
|
if o.target:
|
||||||
return serialize_generic_relation(o, o.target)
|
return serialize_generic_relation(o, o.target)
|
||||||
|
@ -268,6 +278,7 @@ class FullActorSerializer(serializers.Serializer):
|
||||||
summary = common_serializers.ContentSerializer(source="summary_obj")
|
summary = common_serializers.ContentSerializer(source="summary_obj")
|
||||||
icon = common_serializers.AttachmentSerializer(source="attachment_icon")
|
icon = common_serializers.AttachmentSerializer(source="attachment_icon")
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.BOOL)
|
||||||
def get_is_channel(self, o):
|
def get_is_channel(self, o):
|
||||||
try:
|
try:
|
||||||
return bool(o.channel)
|
return bool(o.channel)
|
||||||
|
|
|
@ -48,7 +48,7 @@ class FederationMixin(models.Model):
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_local(self):
|
def is_local(self) -> bool:
|
||||||
return federation_utils.is_local(self.fid)
|
return federation_utils.is_local(self.fid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -172,7 +172,7 @@ class Domain(models.Model):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_local(self):
|
def is_local(self) -> bool:
|
||||||
return self.name == settings.FEDERATION_HOSTNAME
|
return self.name == settings.FEDERATION_HOSTNAME
|
||||||
|
|
||||||
|
|
||||||
|
@ -232,14 +232,14 @@ class Actor(models.Model):
|
||||||
return "{}#main-key".format(self.fid)
|
return "{}#main-key".format(self.fid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_username(self):
|
def full_username(self) -> str:
|
||||||
return "{}@{}".format(self.preferred_username, self.domain_id)
|
return "{}@{}".format(self.preferred_username, self.domain_id)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}@{}".format(self.preferred_username, self.domain_id)
|
return "{}@{}".format(self.preferred_username, self.domain_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_local(self):
|
def is_local(self) -> bool:
|
||||||
return self.domain_id == settings.FEDERATION_HOSTNAME
|
return self.domain_id == settings.FEDERATION_HOSTNAME
|
||||||
|
|
||||||
def get_approved_followers(self):
|
def get_approved_followers(self):
|
||||||
|
|
|
@ -139,7 +139,7 @@ def local_qs(queryset, url_field="fid", include=True):
|
||||||
return queryset.filter(query)
|
return queryset.filter(query)
|
||||||
|
|
||||||
|
|
||||||
def is_local(url):
|
def is_local(url) -> bool:
|
||||||
if not url:
|
if not url:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ from funkwhale_api.federation import serializers as federation_serializers
|
||||||
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
|
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
|
||||||
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
|
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
|
||||||
|
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@ class ListeningSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
@extend_schema_field(federation_serializers.APIActorSerializer)
|
||||||
def get_actor(self, obj):
|
def get_actor(self, obj):
|
||||||
actor = obj.user.actor
|
actor = obj.user.actor
|
||||||
if actor:
|
if actor:
|
||||||
|
|
|
@ -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.tags import models as tags_models
|
||||||
from funkwhale_api.users import models as users_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
|
from . import filters
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,6 +93,7 @@ class ManageUserSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||||
def get_actor(self, obj):
|
def get_actor(self, obj):
|
||||||
if obj.actor:
|
if obj.actor:
|
||||||
return ManageBaseActorSerializer(obj.actor).data
|
return ManageBaseActorSerializer(obj.actor).data
|
||||||
|
@ -151,10 +155,10 @@ class ManageDomainSerializer(serializers.ModelSerializer):
|
||||||
"nodeinfo_fetch_date",
|
"nodeinfo_fetch_date",
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_actors_count(self, o):
|
def get_actors_count(self, o) -> int:
|
||||||
return getattr(o, "actors_count", 0)
|
return getattr(o, "actors_count", 0)
|
||||||
|
|
||||||
def get_outbox_activities_count(self, o):
|
def get_outbox_activities_count(self, o) -> int:
|
||||||
return getattr(o, "outbox_activities_count", 0)
|
return getattr(o, "outbox_activities_count", 0)
|
||||||
|
|
||||||
|
|
||||||
|
@ -211,7 +215,7 @@ class ManageBaseActorSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
read_only_fields = ["creation_date", "instance_policy"]
|
read_only_fields = ["creation_date", "instance_policy"]
|
||||||
|
|
||||||
def get_is_local(self, o):
|
def get_is_local(self, o) -> bool:
|
||||||
return o.domain_id == settings.FEDERATION_HOSTNAME
|
return o.domain_id == settings.FEDERATION_HOSTNAME
|
||||||
|
|
||||||
|
|
||||||
|
@ -228,7 +232,7 @@ class ManageActorSerializer(ManageBaseActorSerializer):
|
||||||
]
|
]
|
||||||
read_only_fields = ["creation_date", "instance_policy"]
|
read_only_fields = ["creation_date", "instance_policy"]
|
||||||
|
|
||||||
def get_uploads_count(self, o):
|
def get_uploads_count(self, o) -> int:
|
||||||
return getattr(o, "uploads_count", 0)
|
return getattr(o, "uploads_count", 0)
|
||||||
|
|
||||||
|
|
||||||
|
@ -242,7 +246,7 @@ class ManageActorActionSerializer(common_serializers.ActionSerializer):
|
||||||
common_utils.on_commit(federation_tasks.purge_actors.delay, ids=list(ids))
|
common_utils.on_commit(federation_tasks.purge_actors.delay, ids=list(ids))
|
||||||
|
|
||||||
|
|
||||||
class TargetSerializer(serializers.Serializer):
|
class ManageTargetSerializer(serializers.Serializer):
|
||||||
type = serializers.ChoiceField(choices=["domain", "actor"])
|
type = serializers.ChoiceField(choices=["domain", "actor"])
|
||||||
id = serializers.CharField()
|
id = serializers.CharField()
|
||||||
|
|
||||||
|
@ -264,7 +268,7 @@ class TargetSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
|
||||||
class ManageInstancePolicySerializer(serializers.ModelSerializer):
|
class ManageInstancePolicySerializer(serializers.ModelSerializer):
|
||||||
target = TargetSerializer()
|
target = ManageTargetSerializer()
|
||||||
actor = federation_fields.ActorRelatedField(read_only=True)
|
actor = federation_fields.ActorRelatedField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -353,6 +357,7 @@ class ManageBaseAlbumSerializer(serializers.ModelSerializer):
|
||||||
"tracks_count",
|
"tracks_count",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_tracks_count(self, o):
|
def get_tracks_count(self, o):
|
||||||
return getattr(o, "_tracks_count", None)
|
return getattr(o, "_tracks_count", None)
|
||||||
|
|
||||||
|
@ -385,6 +390,7 @@ class ManageNestedAlbumSerializer(ManageBaseAlbumSerializer):
|
||||||
model = music_models.Album
|
model = music_models.Album
|
||||||
fields = ManageBaseAlbumSerializer.Meta.fields + ["tracks_count"]
|
fields = ManageBaseAlbumSerializer.Meta.fields + ["tracks_count"]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_tracks_count(self, obj):
|
def get_tracks_count(self, obj):
|
||||||
return getattr(obj, "tracks_count", None)
|
return getattr(obj, "tracks_count", None)
|
||||||
|
|
||||||
|
@ -411,16 +417,20 @@ class ManageArtistSerializer(
|
||||||
"content_category",
|
"content_category",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_tracks_count(self, obj):
|
def get_tracks_count(self, obj):
|
||||||
return getattr(obj, "_tracks_count", None)
|
return getattr(obj, "_tracks_count", None)
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_albums_count(self, obj):
|
def get_albums_count(self, obj):
|
||||||
return getattr(obj, "_albums_count", None)
|
return getattr(obj, "_albums_count", None)
|
||||||
|
|
||||||
|
@extend_schema_field({"type": "array", "items": {"type": "string"}})
|
||||||
def get_tags(self, obj):
|
def get_tags(self, obj):
|
||||||
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
||||||
return [ti.tag.name for ti in tagged_items]
|
return [ti.tag.name for ti in tagged_items]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_channel(self, obj):
|
def get_channel(self, obj):
|
||||||
if "channel" in obj._state.fields_cache and obj.get_channel():
|
if "channel" in obj._state.fields_cache and obj.get_channel():
|
||||||
return str(obj.channel.uuid)
|
return str(obj.channel.uuid)
|
||||||
|
@ -446,9 +456,10 @@ class ManageAlbumSerializer(
|
||||||
"tracks_count",
|
"tracks_count",
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_tracks_count(self, o):
|
def get_tracks_count(self, o) -> int:
|
||||||
return len(o.tracks.all())
|
return len(o.tracks.all())
|
||||||
|
|
||||||
|
@extend_schema_field({"type": "array", "items": {"type": "string"}})
|
||||||
def get_tags(self, obj):
|
def get_tags(self, obj):
|
||||||
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
||||||
return [ti.tag.name for ti in tagged_items]
|
return [ti.tag.name for ti in tagged_items]
|
||||||
|
@ -483,9 +494,11 @@ class ManageTrackSerializer(
|
||||||
"cover",
|
"cover",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_uploads_count(self, obj):
|
def get_uploads_count(self, obj):
|
||||||
return getattr(obj, "uploads_count", None)
|
return getattr(obj, "uploads_count", None)
|
||||||
|
|
||||||
|
@extend_schema_field({"type": "array", "items": {"type": "string"}})
|
||||||
def get_tags(self, obj):
|
def get_tags(self, obj):
|
||||||
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
||||||
return [ti.tag.name for ti in tagged_items]
|
return [ti.tag.name for ti in tagged_items]
|
||||||
|
@ -570,9 +583,10 @@ class ManageLibrarySerializer(serializers.ModelSerializer):
|
||||||
"creation_date",
|
"creation_date",
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_uploads_count(self, obj):
|
def get_uploads_count(self, obj) -> int:
|
||||||
return getattr(obj, "_uploads_count", obj.uploads_count)
|
return getattr(obj, "_uploads_count", int(obj.uploads_count))
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_followers_count(self, obj):
|
def get_followers_count(self, obj):
|
||||||
return getattr(obj, "followers_count", None)
|
return getattr(obj, "followers_count", None)
|
||||||
|
|
||||||
|
@ -652,12 +666,15 @@ class ManageTagSerializer(ManageBaseAlbumSerializer):
|
||||||
"artists_count",
|
"artists_count",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_tracks_count(self, obj):
|
def get_tracks_count(self, obj):
|
||||||
return getattr(obj, "_tracks_count", None)
|
return getattr(obj, "_tracks_count", None)
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_albums_count(self, obj):
|
def get_albums_count(self, obj):
|
||||||
return getattr(obj, "_albums_count", None)
|
return getattr(obj, "_albums_count", None)
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_artists_count(self, obj):
|
def get_artists_count(self, obj):
|
||||||
return getattr(obj, "_artists_count", None)
|
return getattr(obj, "_artists_count", None)
|
||||||
|
|
||||||
|
@ -728,6 +745,7 @@ class ManageReportSerializer(serializers.ModelSerializer):
|
||||||
"summary",
|
"summary",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(ManageBaseNoteSerializer)
|
||||||
def get_notes(self, o):
|
def get_notes(self, o):
|
||||||
notes = getattr(o, "_prefetched_notes", [])
|
notes = getattr(o, "_prefetched_notes", [])
|
||||||
return ManageBaseNoteSerializer(notes, many=True).data
|
return ManageBaseNoteSerializer(notes, many=True).data
|
||||||
|
@ -761,6 +779,7 @@ class ManageUserRequestSerializer(serializers.ModelSerializer):
|
||||||
"metadata",
|
"metadata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(ManageBaseNoteSerializer)
|
||||||
def get_notes(self, o):
|
def get_notes(self, o):
|
||||||
notes = getattr(o, "_prefetched_notes", [])
|
notes = getattr(o, "_prefetched_notes", [])
|
||||||
return ManageBaseNoteSerializer(notes, many=True).data
|
return ManageBaseNoteSerializer(notes, many=True).data
|
||||||
|
|
|
@ -24,7 +24,7 @@ class FilteredArtistSerializer(serializers.ModelSerializer):
|
||||||
fields = ["id", "name"]
|
fields = ["id", "name"]
|
||||||
|
|
||||||
|
|
||||||
class TargetSerializer(serializers.Serializer):
|
class ModerationTargetSerializer(serializers.Serializer):
|
||||||
type = serializers.ChoiceField(choices=["artist"])
|
type = serializers.ChoiceField(choices=["artist"])
|
||||||
id = serializers.CharField()
|
id = serializers.CharField()
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class TargetSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
|
||||||
class UserFilterSerializer(serializers.ModelSerializer):
|
class UserFilterSerializer(serializers.ModelSerializer):
|
||||||
target = TargetSerializer()
|
target = ModerationTargetSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.UserFilter
|
model = models.UserFilter
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-07-19 13:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('music', '0055_auto_20220627_1915'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='artist',
|
||||||
|
name='content_category',
|
||||||
|
field=models.CharField(choices=[('music', 'music'), ('podcast', 'podcast'), ('other', 'other')], db_index=True, default='music', max_length=30),
|
||||||
|
),
|
||||||
|
]
|
|
@ -137,7 +137,7 @@ class APIModelMixin(models.Model):
|
||||||
return super().save(**kwargs)
|
return super().save(**kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_local(self):
|
def is_local(self) -> bool:
|
||||||
return federation_utils.is_local(self.fid)
|
return federation_utils.is_local(self.fid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -251,7 +251,7 @@ class Artist(APIModelMixin):
|
||||||
db_index=True,
|
db_index=True,
|
||||||
default="music",
|
default="music",
|
||||||
choices=ARTIST_CONTENT_CATEGORY_CHOICES,
|
choices=ARTIST_CONTENT_CATEGORY_CHOICES,
|
||||||
null=True,
|
null=False,
|
||||||
)
|
)
|
||||||
modification_date = models.DateTimeField(default=timezone.now, db_index=True)
|
modification_date = models.DateTimeField(default=timezone.now, db_index=True)
|
||||||
api = musicbrainz.api.artists
|
api = musicbrainz.api.artists
|
||||||
|
@ -652,7 +652,7 @@ class Track(APIModelMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def listen_url(self):
|
def listen_url(self) -> str:
|
||||||
# Not using reverse because this is slow
|
# Not using reverse because this is slow
|
||||||
return "/api/v1/listen/{}/".format(self.uuid)
|
return "/api/v1/listen/{}/".format(self.uuid)
|
||||||
|
|
||||||
|
@ -782,7 +782,7 @@ class Upload(models.Model):
|
||||||
objects = UploadQuerySet.as_manager()
|
objects = UploadQuerySet.as_manager()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_local(self):
|
def is_local(self) -> bool:
|
||||||
return federation_utils.is_local(self.fid)
|
return federation_utils.is_local(self.fid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -834,7 +834,7 @@ class Upload(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filename(self):
|
def filename(self) -> str:
|
||||||
return "{}.{}".format(self.track.full_name, self.extension)
|
return "{}.{}".format(self.track.full_name, self.extension)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -910,10 +910,10 @@ class Upload(models.Model):
|
||||||
return metadata.Metadata(audio_file)
|
return metadata.Metadata(audio_file)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def listen_url(self):
|
def listen_url(self) -> str:
|
||||||
return self.track.listen_url + "?upload={}".format(self.uuid)
|
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
|
url = self.listen_url
|
||||||
if to:
|
if to:
|
||||||
url += "&to={}".format(to)
|
url += "&to={}".format(to)
|
||||||
|
@ -1019,7 +1019,7 @@ class UploadVersion(models.Model):
|
||||||
unique_together = ("upload", "mimetype", "bitrate")
|
unique_together = ("upload", "mimetype", "bitrate")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filename(self):
|
def filename(self) -> str:
|
||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
self.upload.track.full_name
|
self.upload.track.full_name
|
||||||
|
@ -1211,15 +1211,15 @@ class Library(federation_models.FederationMixin):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_moderation_url(self):
|
def get_moderation_url(self) -> str:
|
||||||
return "/manage/library/libraries/{}".format(self.uuid)
|
return "/manage/library/libraries/{}".format(self.uuid)
|
||||||
|
|
||||||
def get_federation_id(self):
|
def get_federation_id(self) -> str:
|
||||||
return federation_utils.full_url(
|
return federation_utils.full_url(
|
||||||
reverse("federation:music:libraries-detail", kwargs={"uuid": self.uuid})
|
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)
|
return "/library/{}".format(self.uuid)
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
|
@ -1229,7 +1229,7 @@ class Library(federation_models.FederationMixin):
|
||||||
|
|
||||||
return super().save(**kwargs)
|
return super().save(**kwargs)
|
||||||
|
|
||||||
def should_autoapprove_follow(self, actor):
|
def should_autoapprove_follow(self, actor) -> bool:
|
||||||
if self.privacy_level == "everyone":
|
if self.privacy_level == "everyone":
|
||||||
return True
|
return True
|
||||||
if self.privacy_level == "instance" and actor.get_user():
|
if self.privacy_level == "instance" and actor.get_user():
|
||||||
|
|
|
@ -11,12 +11,16 @@ from funkwhale_api.common import serializers as common_serializers
|
||||||
from funkwhale_api.common import utils as common_utils
|
from funkwhale_api.common import utils as common_utils
|
||||||
from funkwhale_api.federation import routes
|
from funkwhale_api.federation import routes
|
||||||
from funkwhale_api.federation import utils as federation_utils
|
from funkwhale_api.federation import utils as federation_utils
|
||||||
|
from funkwhale_api.federation.serializers import APIActorSerializer
|
||||||
from funkwhale_api.playlists import models as playlists_models
|
from funkwhale_api.playlists import models as playlists_models
|
||||||
from funkwhale_api.tags import models as tag_models
|
from funkwhale_api.tags import models as tag_models
|
||||||
from funkwhale_api.tags import serializers as tags_serializers
|
from funkwhale_api.tags import serializers as tags_serializers
|
||||||
|
|
||||||
from . import filters, models, tasks, utils
|
from . import filters, models, tasks, utils
|
||||||
|
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
|
||||||
NOOP = object()
|
NOOP = object()
|
||||||
|
|
||||||
COVER_WRITE_FIELD = common_serializers.RelatedField(
|
COVER_WRITE_FIELD = common_serializers.RelatedField(
|
||||||
|
@ -29,8 +33,6 @@ COVER_WRITE_FIELD = common_serializers.RelatedField(
|
||||||
write_only=True,
|
write_only=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
from funkwhale_api.audio import serializers as audio_serializers # NOQA
|
|
||||||
|
|
||||||
|
|
||||||
class CoverField(common_serializers.AttachmentSerializer):
|
class CoverField(common_serializers.AttachmentSerializer):
|
||||||
pass
|
pass
|
||||||
|
@ -39,16 +41,6 @@ class CoverField(common_serializers.AttachmentSerializer):
|
||||||
cover_field = CoverField()
|
cover_field = CoverField()
|
||||||
|
|
||||||
|
|
||||||
def serialize_attributed_to(self, obj):
|
|
||||||
# Import at runtime to avoid a circular import issue
|
|
||||||
from funkwhale_api.federation import serializers as federation_serializers
|
|
||||||
|
|
||||||
if not obj.attributed_to_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
return federation_serializers.APIActorSerializer(obj.attributed_to).data
|
|
||||||
|
|
||||||
|
|
||||||
class OptionalDescriptionMixin(object):
|
class OptionalDescriptionMixin(object):
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
repr = super().to_representation(obj)
|
repr = super().to_representation(obj)
|
||||||
|
@ -74,7 +66,7 @@ class LicenseSerializer(serializers.Serializer):
|
||||||
attribution = serializers.BooleanField()
|
attribution = serializers.BooleanField()
|
||||||
copyleft = serializers.BooleanField()
|
copyleft = serializers.BooleanField()
|
||||||
|
|
||||||
def get_id(self, obj):
|
def get_id(self, obj) -> str:
|
||||||
return obj["identifiers"][0]
|
return obj["identifiers"][0]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -94,13 +86,13 @@ class ArtistAlbumSerializer(serializers.Serializer):
|
||||||
release_date = serializers.DateField()
|
release_date = serializers.DateField()
|
||||||
creation_date = serializers.DateTimeField()
|
creation_date = serializers.DateTimeField()
|
||||||
|
|
||||||
def get_artist(self, o):
|
def get_artist(self, o) -> int:
|
||||||
return o.artist_id
|
return o.artist_id
|
||||||
|
|
||||||
def get_tracks_count(self, o):
|
def get_tracks_count(self, o) -> int:
|
||||||
return len(o.tracks.all())
|
return len(o.tracks.all())
|
||||||
|
|
||||||
def get_is_playable(self, obj):
|
def get_is_playable(self, obj) -> bool:
|
||||||
try:
|
try:
|
||||||
return bool(obj.is_playable_by_actor)
|
return bool(obj.is_playable_by_actor)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -113,7 +105,7 @@ DATETIME_FIELD = serializers.DateTimeField()
|
||||||
class ArtistWithAlbumsSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
class ArtistWithAlbumsSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||||
albums = ArtistAlbumSerializer(many=True)
|
albums = ArtistAlbumSerializer(many=True)
|
||||||
tags = serializers.SerializerMethodField()
|
tags = serializers.SerializerMethodField()
|
||||||
attributed_to = serializers.SerializerMethodField()
|
attributed_to = APIActorSerializer()
|
||||||
channel = serializers.SerializerMethodField()
|
channel = serializers.SerializerMethodField()
|
||||||
tracks_count = serializers.SerializerMethodField()
|
tracks_count = serializers.SerializerMethodField()
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
|
@ -125,16 +117,16 @@ class ArtistWithAlbumsSerializer(OptionalDescriptionMixin, serializers.Serialize
|
||||||
is_local = serializers.BooleanField()
|
is_local = serializers.BooleanField()
|
||||||
cover = cover_field
|
cover = cover_field
|
||||||
|
|
||||||
|
@extend_schema_field({"type": "array", "items": {"type": "string"}})
|
||||||
def get_tags(self, obj):
|
def get_tags(self, obj):
|
||||||
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
||||||
return [ti.tag.name for ti in tagged_items]
|
return [ti.tag.name for ti in tagged_items]
|
||||||
|
|
||||||
get_attributed_to = serialize_attributed_to
|
def get_tracks_count(self, o) -> int:
|
||||||
|
|
||||||
def get_tracks_count(self, o):
|
|
||||||
tracks = getattr(o, "_prefetched_tracks", None)
|
tracks = getattr(o, "_prefetched_tracks", None)
|
||||||
return len(tracks) if tracks else None
|
return len(tracks) if tracks else 0
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||||
def get_channel(self, o):
|
def get_channel(self, o):
|
||||||
channel = o.get_channel()
|
channel = o.get_channel()
|
||||||
if not channel:
|
if not channel:
|
||||||
|
@ -150,68 +142,47 @@ class ArtistWithAlbumsSerializer(OptionalDescriptionMixin, serializers.Serialize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def serialize_artist_simple(artist):
|
class SimpleArtistSerializer(serializers.ModelSerializer):
|
||||||
data = {
|
attachment_cover = cover_field
|
||||||
"id": artist.id,
|
description = common_serializers.ContentSerializer()
|
||||||
"fid": artist.fid,
|
|
||||||
"mbid": str(artist.mbid),
|
class Meta:
|
||||||
"name": artist.name,
|
model = models.Artist
|
||||||
"creation_date": DATETIME_FIELD.to_representation(artist.creation_date),
|
fields = (
|
||||||
"modification_date": DATETIME_FIELD.to_representation(artist.modification_date),
|
"id",
|
||||||
"is_local": artist.is_local,
|
"fid",
|
||||||
"content_category": artist.content_category,
|
"mbid",
|
||||||
}
|
"name",
|
||||||
if "description" in artist._state.fields_cache:
|
"creation_date",
|
||||||
data["description"] = (
|
"modification_date",
|
||||||
common_serializers.ContentSerializer(artist.description).data
|
"is_local",
|
||||||
if artist.description
|
"content_category",
|
||||||
else None
|
"description",
|
||||||
|
"attachment_cover",
|
||||||
|
"channel",
|
||||||
)
|
)
|
||||||
|
|
||||||
if "attachment_cover" in artist._state.fields_cache:
|
|
||||||
data["cover"] = (
|
|
||||||
cover_field.to_representation(artist.attachment_cover)
|
|
||||||
if artist.attachment_cover
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
if "channel" in artist._state.fields_cache and artist.get_channel():
|
|
||||||
data["channel"] = str(artist.channel.uuid)
|
|
||||||
|
|
||||||
if getattr(artist, "_tracks_count", None) is not None:
|
|
||||||
data["tracks_count"] = artist._tracks_count
|
|
||||||
|
|
||||||
if getattr(artist, "_prefetched_tagged_items", None) is not None:
|
|
||||||
data["tags"] = [ti.tag.name for ti in artist._prefetched_tagged_items]
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||||
artist = serializers.SerializerMethodField()
|
artist = SimpleArtistSerializer()
|
||||||
cover = cover_field
|
cover = cover_field
|
||||||
is_playable = serializers.SerializerMethodField()
|
is_playable = serializers.SerializerMethodField()
|
||||||
tags = serializers.SerializerMethodField()
|
tags = serializers.SerializerMethodField()
|
||||||
tracks_count = serializers.SerializerMethodField()
|
tracks_count = serializers.SerializerMethodField()
|
||||||
attributed_to = serializers.SerializerMethodField()
|
attributed_to = APIActorSerializer()
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
fid = serializers.URLField()
|
fid = serializers.URLField()
|
||||||
mbid = serializers.UUIDField()
|
mbid = serializers.UUIDField()
|
||||||
title = serializers.CharField()
|
title = serializers.CharField()
|
||||||
artist = serializers.SerializerMethodField()
|
|
||||||
release_date = serializers.DateField()
|
release_date = serializers.DateField()
|
||||||
creation_date = serializers.DateTimeField()
|
creation_date = serializers.DateTimeField()
|
||||||
is_local = serializers.BooleanField()
|
is_local = serializers.BooleanField()
|
||||||
duration = serializers.SerializerMethodField(read_only=True)
|
duration = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
get_attributed_to = serialize_attributed_to
|
def get_tracks_count(self, o) -> int:
|
||||||
|
|
||||||
def get_artist(self, o):
|
|
||||||
return serialize_artist_simple(o.artist)
|
|
||||||
|
|
||||||
def get_tracks_count(self, o):
|
|
||||||
return len(o.tracks.all())
|
return len(o.tracks.all())
|
||||||
|
|
||||||
def get_is_playable(self, obj):
|
def get_is_playable(self, obj) -> bool:
|
||||||
try:
|
try:
|
||||||
return any(
|
return any(
|
||||||
[
|
[
|
||||||
|
@ -222,11 +193,12 @@ class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@extend_schema_field({"type": "array", "items": {"type": "string"}})
|
||||||
def get_tags(self, obj):
|
def get_tags(self, obj):
|
||||||
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
||||||
return [ti.tag.name for ti in tagged_items]
|
return [ti.tag.name for ti in tagged_items]
|
||||||
|
|
||||||
def get_duration(self, obj):
|
def get_duration(self, obj) -> int:
|
||||||
try:
|
try:
|
||||||
return obj.duration
|
return obj.duration
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -235,11 +207,11 @@ class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||||
|
|
||||||
|
|
||||||
class TrackAlbumSerializer(serializers.ModelSerializer):
|
class TrackAlbumSerializer(serializers.ModelSerializer):
|
||||||
artist = serializers.SerializerMethodField()
|
artist = SimpleArtistSerializer()
|
||||||
cover = cover_field
|
cover = cover_field
|
||||||
tracks_count = serializers.SerializerMethodField()
|
tracks_count = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_tracks_count(self, o):
|
def get_tracks_count(self, o) -> int:
|
||||||
return getattr(o, "_prefetched_tracks_count", len(o.tracks.all()))
|
return getattr(o, "_prefetched_tracks_count", len(o.tracks.all()))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -257,11 +229,8 @@ class TrackAlbumSerializer(serializers.ModelSerializer):
|
||||||
"tracks_count",
|
"tracks_count",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_artist(self, o):
|
|
||||||
return serialize_artist_simple(o.artist)
|
|
||||||
|
|
||||||
|
def serialize_upload(upload) -> object:
|
||||||
def serialize_upload(upload):
|
|
||||||
return {
|
return {
|
||||||
"uuid": str(upload.uuid),
|
"uuid": str(upload.uuid),
|
||||||
"listen_url": upload.listen_url,
|
"listen_url": upload.listen_url,
|
||||||
|
@ -291,18 +260,17 @@ def sort_uploads_for_listen(uploads):
|
||||||
|
|
||||||
|
|
||||||
class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||||
artist = serializers.SerializerMethodField()
|
artist = SimpleArtistSerializer()
|
||||||
album = TrackAlbumSerializer(read_only=True)
|
album = TrackAlbumSerializer(read_only=True)
|
||||||
uploads = serializers.SerializerMethodField()
|
uploads = serializers.SerializerMethodField()
|
||||||
listen_url = serializers.SerializerMethodField()
|
listen_url = serializers.SerializerMethodField()
|
||||||
tags = serializers.SerializerMethodField()
|
tags = serializers.SerializerMethodField()
|
||||||
attributed_to = serializers.SerializerMethodField()
|
attributed_to = APIActorSerializer()
|
||||||
|
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
fid = serializers.URLField()
|
fid = serializers.URLField()
|
||||||
mbid = serializers.UUIDField()
|
mbid = serializers.UUIDField()
|
||||||
title = serializers.CharField()
|
title = serializers.CharField()
|
||||||
artist = serializers.SerializerMethodField()
|
|
||||||
creation_date = serializers.DateTimeField()
|
creation_date = serializers.DateTimeField()
|
||||||
is_local = serializers.BooleanField()
|
is_local = serializers.BooleanField()
|
||||||
position = serializers.IntegerField()
|
position = serializers.IntegerField()
|
||||||
|
@ -311,15 +279,13 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||||
copyright = serializers.CharField()
|
copyright = serializers.CharField()
|
||||||
license = serializers.SerializerMethodField()
|
license = serializers.SerializerMethodField()
|
||||||
cover = cover_field
|
cover = cover_field
|
||||||
get_attributed_to = serialize_attributed_to
|
|
||||||
is_playable = serializers.SerializerMethodField()
|
is_playable = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_artist(self, o):
|
@extend_schema_field(OpenApiTypes.URI)
|
||||||
return serialize_artist_simple(o.artist)
|
|
||||||
|
|
||||||
def get_listen_url(self, obj):
|
def get_listen_url(self, obj):
|
||||||
return obj.listen_url
|
return obj.listen_url
|
||||||
|
|
||||||
|
@extend_schema_field({"type": "array", "items": {"type": "object"}})
|
||||||
def get_uploads(self, obj):
|
def get_uploads(self, obj):
|
||||||
uploads = getattr(obj, "playable_uploads", [])
|
uploads = getattr(obj, "playable_uploads", [])
|
||||||
# we put local uploads first
|
# we put local uploads first
|
||||||
|
@ -327,14 +293,15 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||||
uploads = sorted(uploads, key=lambda u: u["is_local"], reverse=True)
|
uploads = sorted(uploads, key=lambda u: u["is_local"], reverse=True)
|
||||||
return list(uploads)
|
return list(uploads)
|
||||||
|
|
||||||
|
@extend_schema_field({"type": "array", "items": {"type": "str"}})
|
||||||
def get_tags(self, obj):
|
def get_tags(self, obj):
|
||||||
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
tagged_items = getattr(obj, "_prefetched_tagged_items", [])
|
||||||
return [ti.tag.name for ti in tagged_items]
|
return [ti.tag.name for ti in tagged_items]
|
||||||
|
|
||||||
def get_license(self, o):
|
def get_license(self, o) -> str:
|
||||||
return o.license_id
|
return o.license_id
|
||||||
|
|
||||||
def get_is_playable(self, obj):
|
def get_is_playable(self, obj) -> bool:
|
||||||
return bool(getattr(obj, "playable_uploads", []))
|
return bool(getattr(obj, "playable_uploads", []))
|
||||||
|
|
||||||
|
|
||||||
|
@ -359,10 +326,10 @@ class LibraryForOwnerSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
read_only_fields = ["fid", "uuid", "creation_date", "actor"]
|
read_only_fields = ["fid", "uuid", "creation_date", "actor"]
|
||||||
|
|
||||||
def get_uploads_count(self, o):
|
def get_uploads_count(self, o) -> int:
|
||||||
return getattr(o, "_uploads_count", o.uploads_count)
|
return getattr(o, "_uploads_count", int(o.uploads_count))
|
||||||
|
|
||||||
def get_size(self, o):
|
def get_size(self, o) -> int:
|
||||||
return getattr(o, "_size", 0)
|
return getattr(o, "_size", 0)
|
||||||
|
|
||||||
def on_updated_fields(self, obj, before, after):
|
def on_updated_fields(self, obj, before, after):
|
||||||
|
@ -370,14 +337,14 @@ class LibraryForOwnerSerializer(serializers.ModelSerializer):
|
||||||
{"type": "Update", "object": {"type": "Library"}}, context={"library": obj}
|
{"type": "Update", "object": {"type": "Library"}}, context={"library": obj}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema_field(APIActorSerializer)
|
||||||
def get_actor(self, o):
|
def get_actor(self, o):
|
||||||
# Import at runtime to avoid a circular import issue
|
return APIActorSerializer(o.actor).data
|
||||||
from funkwhale_api.federation import serializers as federation_serializers
|
|
||||||
|
|
||||||
return federation_serializers.APIActorSerializer(o.actor).data
|
|
||||||
|
|
||||||
|
|
||||||
class UploadSerializer(serializers.ModelSerializer):
|
class UploadSerializer(serializers.ModelSerializer):
|
||||||
|
from funkwhale_api.audio.serializers import ChannelSerializer
|
||||||
|
|
||||||
track = TrackSerializer(required=False, allow_null=True)
|
track = TrackSerializer(required=False, allow_null=True)
|
||||||
library = common_serializers.RelatedField(
|
library = common_serializers.RelatedField(
|
||||||
"uuid",
|
"uuid",
|
||||||
|
@ -387,7 +354,7 @@ class UploadSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
channel = common_serializers.RelatedField(
|
channel = common_serializers.RelatedField(
|
||||||
"uuid",
|
"uuid",
|
||||||
audio_serializers.ChannelSerializer(),
|
ChannelSerializer(),
|
||||||
required=False,
|
required=False,
|
||||||
filters=lambda context: {"attributed_to": context["user"].actor},
|
filters=lambda context: {"attributed_to": context["user"].actor},
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,9 @@ from funkwhale_api.music.models import Track
|
||||||
from funkwhale_api.music.serializers import TrackSerializer
|
from funkwhale_api.music.serializers import TrackSerializer
|
||||||
from funkwhale_api.users.serializers import UserBasicSerializer
|
from funkwhale_api.users.serializers import UserBasicSerializer
|
||||||
|
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,31 +49,34 @@ class PlaylistSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
read_only_fields = ["id", "modification_date", "creation_date"]
|
read_only_fields = ["id", "modification_date", "creation_date"]
|
||||||
|
|
||||||
|
@extend_schema_field(federation_serializers.APIActorSerializer)
|
||||||
def get_actor(self, obj):
|
def get_actor(self, obj):
|
||||||
actor = obj.user.actor
|
actor = obj.user.actor
|
||||||
if actor:
|
if actor:
|
||||||
return federation_serializers.APIActorSerializer(actor).data
|
return federation_serializers.APIActorSerializer(actor).data
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.BOOL)
|
||||||
def get_is_playable(self, obj):
|
def get_is_playable(self, obj):
|
||||||
try:
|
try:
|
||||||
return bool(obj.playable_plts)
|
return bool(obj.playable_plts)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_tracks_count(self, obj):
|
def get_tracks_count(self, obj) -> int:
|
||||||
try:
|
try:
|
||||||
return obj.tracks_count
|
return obj.tracks_count
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# no annotation?
|
# no annotation?
|
||||||
return obj.playlist_tracks.count()
|
return obj.playlist_tracks.count()
|
||||||
|
|
||||||
def get_duration(self, obj):
|
def get_duration(self, obj) -> int:
|
||||||
try:
|
try:
|
||||||
return obj.duration
|
return obj.duration
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# no annotation?
|
# no annotation?
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@extend_schema_field({"type": "array", "items": {"type": "uri"}})
|
||||||
def get_album_covers(self, obj):
|
def get_album_covers(self, obj):
|
||||||
try:
|
try:
|
||||||
plts = obj.plts_for_cover
|
plts = obj.plts_for_cover
|
||||||
|
|
|
@ -307,7 +307,7 @@ class User(AbstractUser):
|
||||||
|
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
def full_username(self):
|
def full_username(self) -> str:
|
||||||
return "{}@{}".format(self.username, settings.FEDERATION_HOSTNAME)
|
return "{}@{}".format(self.username, settings.FEDERATION_HOSTNAME)
|
||||||
|
|
||||||
def get_avatar(self):
|
def get_avatar(self):
|
||||||
|
|
|
@ -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 tasks as moderation_tasks
|
||||||
from funkwhale_api.moderation import utils as moderation_utils
|
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 adapters
|
||||||
from . import models
|
from . import models
|
||||||
from . import authentication as users_authentication
|
from . import authentication as users_authentication
|
||||||
|
@ -205,6 +208,7 @@ class UserReadSerializer(serializers.ModelSerializer):
|
||||||
def get_permissions(self, o):
|
def get_permissions(self, o):
|
||||||
return o.get_permissions()
|
return o.get_permissions()
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_full_username(self, o):
|
def get_full_username(self, o):
|
||||||
if o.actor:
|
if o.actor:
|
||||||
return o.actor.full_username
|
return o.actor.full_username
|
||||||
|
|
|
@ -216,7 +216,7 @@ def test_channel_serializer_representation(factories, to_api_date):
|
||||||
channel = factories["audio.Channel"](artist__description=content)
|
channel = factories["audio.Channel"](artist__description=content)
|
||||||
setattr(channel, "_downloads_count", 12)
|
setattr(channel, "_downloads_count", 12)
|
||||||
expected = {
|
expected = {
|
||||||
"artist": music_serializers.serialize_artist_simple(channel.artist),
|
"artist": music_serializers.SimpleArtistSerializer(channel.artist).data,
|
||||||
"uuid": str(channel.uuid),
|
"uuid": str(channel.uuid),
|
||||||
"creation_date": to_api_date(channel.creation_date),
|
"creation_date": to_api_date(channel.creation_date),
|
||||||
"actor": federation_serializers.APIActorSerializer(channel.actor).data,
|
"actor": federation_serializers.APIActorSerializer(channel.actor).data,
|
||||||
|
@ -240,7 +240,7 @@ def test_channel_serializer_external_representation(factories, to_api_date):
|
||||||
channel = factories["audio.Channel"](artist__description=content, external=True)
|
channel = factories["audio.Channel"](artist__description=content, external=True)
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
"artist": music_serializers.serialize_artist_simple(channel.artist),
|
"artist": music_serializers.SimpleArtistSerializer(channel.artist).data,
|
||||||
"uuid": str(channel.uuid),
|
"uuid": str(channel.uuid),
|
||||||
"creation_date": to_api_date(channel.creation_date),
|
"creation_date": to_api_date(channel.creation_date),
|
||||||
"actor": None,
|
"actor": None,
|
||||||
|
|
|
@ -179,7 +179,7 @@ def test_album_serializer(factories, to_api_date):
|
||||||
"fid": album.fid,
|
"fid": album.fid,
|
||||||
"mbid": str(album.mbid),
|
"mbid": str(album.mbid),
|
||||||
"title": album.title,
|
"title": album.title,
|
||||||
"artist": serializers.serialize_artist_simple(album.artist),
|
"artist": serializers.SimpleArtistSerializer(album.artist).data,
|
||||||
"creation_date": to_api_date(album.creation_date),
|
"creation_date": to_api_date(album.creation_date),
|
||||||
"is_playable": False,
|
"is_playable": False,
|
||||||
"duration": 0,
|
"duration": 0,
|
||||||
|
@ -209,7 +209,7 @@ def test_track_album_serializer(factories, to_api_date):
|
||||||
"fid": album.fid,
|
"fid": album.fid,
|
||||||
"mbid": str(album.mbid),
|
"mbid": str(album.mbid),
|
||||||
"title": album.title,
|
"title": album.title,
|
||||||
"artist": serializers.serialize_artist_simple(album.artist),
|
"artist": serializers.SimpleArtistSerializer(album.artist).data,
|
||||||
"creation_date": to_api_date(album.creation_date),
|
"creation_date": to_api_date(album.creation_date),
|
||||||
"is_playable": False,
|
"is_playable": False,
|
||||||
"cover": common_serializers.AttachmentSerializer(album.attachment_cover).data,
|
"cover": common_serializers.AttachmentSerializer(album.attachment_cover).data,
|
||||||
|
@ -241,7 +241,7 @@ def test_track_serializer(factories, to_api_date):
|
||||||
expected = {
|
expected = {
|
||||||
"id": track.id,
|
"id": track.id,
|
||||||
"fid": track.fid,
|
"fid": track.fid,
|
||||||
"artist": serializers.serialize_artist_simple(track.artist),
|
"artist": serializers.SimpleArtistSerializer(track.artist).data,
|
||||||
"album": serializers.TrackAlbumSerializer(track.album).data,
|
"album": serializers.TrackAlbumSerializer(track.album).data,
|
||||||
"mbid": str(track.mbid),
|
"mbid": str(track.mbid),
|
||||||
"title": track.title,
|
"title": track.title,
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Added type hints to the API.
|
Loading…
Reference in New Issue