diff --git a/api/config/urls/api_v2.py b/api/config/urls/api_v2.py index a1d0befc8..c7e751b8e 100644 --- a/api/config/urls/api_v2.py +++ b/api/config/urls/api_v2.py @@ -7,6 +7,10 @@ router = common_routers.OptionalSlashRouter() v2_patterns = router.urls v2_patterns += [ + re_path( + r"^artists/", + include(("funkwhale_api.music.urls_v2", "artists"), namespace="artists"), + ), re_path( r"^instance/", include(("funkwhale_api.instance.urls_v2", "instance"), namespace="instance"), diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index fca044a16..86c2c3212 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -202,6 +202,7 @@ class ArtistQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet): class Artist(APIModelMixin): + guid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) name = models.TextField() federation_namespace = "artists" musicbrainz_model = "artist" diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py index 5b6abf21d..0b26c7499 100644 --- a/api/funkwhale_api/music/serializers.py +++ b/api/funkwhale_api/music/serializers.py @@ -1,4 +1,5 @@ import urllib.parse +import uuid from django import urls from django.conf import settings @@ -854,3 +855,30 @@ class SearchResultSerializer(serializers.Serializer): tracks = TrackSerializer(many=True) albums = AlbumSerializer(many=True) tags = tags_serializers.TagSerializer(many=True) + + +class V2_BaseArtistSerializer(serializers.ModelSerializer): + + """ + A simple serializer for artist information. + All other serializers that reference an artist should use this serializer. + """ + + guid = models.UUIDField(default=uuid.uuid4, editable=False) + fid = serializers.URLField() + mbid = serializers.UUIDField() + name = serializers.CharField() + contentCategory = serializers.CharField() + local = serializers.BooleanField() + cover = CoverField(allow_null=True, required=False) + tags = serializers.SerializerMethodField() + + # Fetch all tags associated with the artist. + + @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] + + def get_local(artist, upload) -> bool: + return federation_utils.is_local(artist.fid) diff --git a/api/funkwhale_api/music/urls_v2.py b/api/funkwhale_api/music/urls_v2.py new file mode 100644 index 000000000..994baf260 --- /dev/null +++ b/api/funkwhale_api/music/urls_v2.py @@ -0,0 +1,10 @@ +from funkwhale_api.common import routers + +from . import views + +router = routers.OptionalSlashRouter() + +router.register(r"artists", views.V2_ArtistsViewSet, "artists") + + +urlpatterns = router.urls diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 282ceca24..cd8ab96be 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -175,6 +175,69 @@ class ArtistViewSet( ) +class V2_ArtistsViewSet( + HandleInvalidSearch, + common_views.SkipFilterForGetObject, + viewsets.ReadOnlyModelViewSet, +): + queryset = ( + models.Artist.objects.all() + .prefetch_related("attributed_to", "attachment_cover") + .prefetch_related( + "channel__actor", + Prefetch( + "tracks", + queryset=models.Track.objects.all(), + to_attr="_prefetched_tracks", + ), + ) + .order_by("-id") + ) + serializer_class = serializers.V2_BaseArtistSerializer + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" + filterset_class = filters.ArtistFilter + + fetches = federation_decorators.fetches_route() + mutations = common_decorators.mutations_route(types=["update"]) + + def get_object(self): + obj = super().get_object() + + if ( + self.action == "retrieve" + and self.request.GET.get("refresh", "").lower() == "true" + ): + obj = refetch_obj(obj, self.get_queryset()) + return obj + + def get_serializer_context(self): + context = super().get_serializer_context() + context["description"] = self.action in ["retrieve", "create", "update"] + return context + + def get_queryset(self): + queryset = super().get_queryset() + albums = ( + models.Album.objects.with_tracks_count() + .select_related("attachment_cover") + .prefetch_related("tracks") + ) + albums = albums.annotate_playable_by_actor( + utils.get_actor_from_request(self.request) + ) + return queryset.prefetch_related( + Prefetch("albums", queryset=albums), TAG_PREFETCH + ) + + libraries = get_libraries( + lambda o, uploads: uploads.filter( + Q(track__artist=o) | Q(track__album__artist=o) + ) + ) + + class AlbumViewSet( HandleInvalidSearch, common_views.SkipFilterForGetObject,