Skip to content
Snippets Groups Projects
Commit c4da7b8a authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch '689-serve-artists-albums-tracks-uploads' into 'develop'

See #689: now serve AP representations for uploads, tracks, albums and artists

See merge request funkwhale/funkwhale!632
parents 8e4320d1 d243d6a2
No related branches found
No related tags found
No related merge requests found
...@@ -3,6 +3,7 @@ import uuid ...@@ -3,6 +3,7 @@ import uuid
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.conf import settings
from django.db import models, transaction from django.db import models, transaction
from django.utils import timezone from django.utils import timezone
from django.urls import reverse from django.urls import reverse
...@@ -10,6 +11,18 @@ from django.urls import reverse ...@@ -10,6 +11,18 @@ from django.urls import reverse
from funkwhale_api.federation import utils as federation_utils from funkwhale_api.federation import utils as federation_utils
class LocalFromFidQuerySet:
def local(self, include=True):
host = settings.FEDERATION_HOSTNAME
query = models.Q(fid__startswith="http://{}/".format(host)) | models.Q(
fid__startswith="https://{}/".format(host)
)
if include:
return self.filter(query)
else:
return self.filter(~query)
class MutationQuerySet(models.QuerySet): class MutationQuerySet(models.QuerySet):
def get_for_target(self, target): def get_for_target(self, target):
content_type = ContentType.objects.get_for_model(target) content_type = ContentType.objects.get_for_model(target)
......
...@@ -7,6 +7,7 @@ from rest_framework.decorators import action ...@@ -7,6 +7,7 @@ from rest_framework.decorators import action
from funkwhale_api.common import preferences from funkwhale_api.common import preferences
from funkwhale_api.music import models as music_models from funkwhale_api.music import models as music_models
from funkwhale_api.music import utils as music_utils
from . import activity, authentication, models, renderers, serializers, utils, webfinger from . import activity, authentication, models, renderers, serializers, utils, webfinger
...@@ -202,9 +203,17 @@ class MusicUploadViewSet( ...@@ -202,9 +203,17 @@ class MusicUploadViewSet(
authentication_classes = [authentication.SignatureAuthentication] authentication_classes = [authentication.SignatureAuthentication]
permission_classes = [] permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer] renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Upload.objects.none() queryset = music_models.Upload.objects.local().select_related(
"library__actor", "track__artist", "track__album__artist"
)
serializer_class = serializers.UploadSerializer
lookup_field = "uuid" lookup_field = "uuid"
def get_queryset(self):
queryset = super().get_queryset()
actor = music_utils.get_actor_from_request(self.request)
return queryset.playable_by(actor)
class MusicArtistViewSet( class MusicArtistViewSet(
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
...@@ -212,7 +221,8 @@ class MusicArtistViewSet( ...@@ -212,7 +221,8 @@ class MusicArtistViewSet(
authentication_classes = [authentication.SignatureAuthentication] authentication_classes = [authentication.SignatureAuthentication]
permission_classes = [] permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer] renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Artist.objects.none() queryset = music_models.Artist.objects.local()
serializer_class = serializers.ArtistSerializer
lookup_field = "uuid" lookup_field = "uuid"
...@@ -222,7 +232,8 @@ class MusicAlbumViewSet( ...@@ -222,7 +232,8 @@ class MusicAlbumViewSet(
authentication_classes = [authentication.SignatureAuthentication] authentication_classes = [authentication.SignatureAuthentication]
permission_classes = [] permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer] renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Album.objects.none() queryset = music_models.Album.objects.local().select_related("artist")
serializer_class = serializers.AlbumSerializer
lookup_field = "uuid" lookup_field = "uuid"
...@@ -232,5 +243,8 @@ class MusicTrackViewSet( ...@@ -232,5 +243,8 @@ class MusicTrackViewSet(
authentication_classes = [authentication.SignatureAuthentication] authentication_classes = [authentication.SignatureAuthentication]
permission_classes = [] permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer] renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Track.objects.none() queryset = music_models.Track.objects.local().select_related(
"album__artist", "artist"
)
serializer_class = serializers.TrackSerializer
lookup_field = "uuid" lookup_field = "uuid"
...@@ -24,6 +24,7 @@ from versatileimagefield.image_warmer import VersatileImageFieldWarmer ...@@ -24,6 +24,7 @@ from versatileimagefield.image_warmer import VersatileImageFieldWarmer
from funkwhale_api import musicbrainz from funkwhale_api import musicbrainz
from funkwhale_api.common import fields from funkwhale_api.common import fields
from funkwhale_api.common import models as common_models
from funkwhale_api.common import session from funkwhale_api.common import session
from funkwhale_api.common import utils as common_utils from funkwhale_api.common import utils as common_utils
from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import models as federation_models
...@@ -141,7 +142,7 @@ class License(models.Model): ...@@ -141,7 +142,7 @@ class License(models.Model):
logger.warning("%s do not match any registered license", self.code) logger.warning("%s do not match any registered license", self.code)
class ArtistQuerySet(models.QuerySet): class ArtistQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet):
def with_albums_count(self): def with_albums_count(self):
return self.annotate(_albums_count=models.Count("albums")) return self.annotate(_albums_count=models.Count("albums"))
...@@ -215,7 +216,7 @@ def import_tracks(instance, cleaned_data, raw_data): ...@@ -215,7 +216,7 @@ def import_tracks(instance, cleaned_data, raw_data):
importers.load(Track, track_cleaned_data, track_data, Track.import_hooks) importers.load(Track, track_cleaned_data, track_data, Track.import_hooks)
class AlbumQuerySet(models.QuerySet): class AlbumQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet):
def with_tracks_count(self): def with_tracks_count(self):
return self.annotate(_tracks_count=models.Count("tracks")) return self.annotate(_tracks_count=models.Count("tracks"))
...@@ -416,7 +417,7 @@ class Lyrics(models.Model): ...@@ -416,7 +417,7 @@ class Lyrics(models.Model):
) )
class TrackQuerySet(models.QuerySet): class TrackQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet):
def for_nested_serialization(self): def for_nested_serialization(self):
return self.select_related().select_related("album__artist", "artist") return self.select_related().select_related("album__artist", "artist")
......
...@@ -174,3 +174,75 @@ def test_music_library_retrieve_page_follow( ...@@ -174,3 +174,75 @@ def test_music_library_retrieve_page_follow(
response = api_client.get(url, {"page": 1}) response = api_client.get(url, {"page": 1})
assert response.status_code == expected assert response.status_code == expected
@pytest.mark.parametrize(
"factory, serializer_class, namespace",
[
("music.Artist", serializers.ArtistSerializer, "artists"),
("music.Album", serializers.AlbumSerializer, "albums"),
("music.Track", serializers.TrackSerializer, "tracks"),
],
)
def test_music_local_entity_detail(
factories, api_client, factory, serializer_class, namespace, settings
):
obj = factories[factory](fid="http://{}/1".format(settings.FEDERATION_HOSTNAME))
url = reverse(
"federation:music:{}-detail".format(namespace), kwargs={"uuid": obj.uuid}
)
response = api_client.get(url)
assert response.status_code == 200
assert response.data == serializer_class(obj).data
@pytest.mark.parametrize(
"factory, namespace",
[("music.Artist", "artists"), ("music.Album", "albums"), ("music.Track", "tracks")],
)
def test_music_non_local_entity_detail(
factories, api_client, factory, namespace, settings
):
obj = factories[factory](fid="http://wrong-domain/1")
url = reverse(
"federation:music:{}-detail".format(namespace), kwargs={"uuid": obj.uuid}
)
response = api_client.get(url)
assert response.status_code == 404
@pytest.mark.parametrize(
"privacy_level, expected", [("me", 404), ("instance", 404), ("everyone", 200)]
)
def test_music_upload_detail(factories, api_client, privacy_level, expected):
upload = factories["music.Upload"](
library__privacy_level=privacy_level,
library__actor__local=True,
import_status="finished",
)
url = reverse("federation:music:uploads-detail", kwargs={"uuid": upload.uuid})
response = api_client.get(url)
assert response.status_code == expected
if expected == 200:
assert response.data == serializers.UploadSerializer(upload).data
@pytest.mark.parametrize("privacy_level", ["me", "instance"])
def test_music_upload_detail_private_approved_follow(
factories, api_client, authenticated_actor, privacy_level
):
upload = factories["music.Upload"](
library__privacy_level=privacy_level,
library__actor__local=True,
import_status="finished",
)
factories["federation.LibraryFollow"](
actor=authenticated_actor, target=upload.library, approved=True
)
url = reverse("federation:music:uploads-detail", kwargs={"uuid": upload.uuid})
response = api_client.get(url)
assert response.status_code == 200
...@@ -522,3 +522,14 @@ def test_track_order_for_album(factories): ...@@ -522,3 +522,14 @@ def test_track_order_for_album(factories):
t4 = factories["music.Track"](album=album, position=2, disc_number=2) t4 = factories["music.Track"](album=album, position=2, disc_number=2)
assert list(models.Track.objects.order_for_album()) == [t1, t3, t2, t4] assert list(models.Track.objects.order_for_album()) == [t1, t3, t2, t4]
@pytest.mark.parametrize("factory", ["music.Artist", "music.Album", "music.Track"])
def test_queryset_local_entities(factories, settings, factory):
settings.FEDERATION_HOSTNAME = "test.com"
obj1 = factories[factory](fid="http://test.com/1")
obj2 = factories[factory](fid="https://test.com/2")
factories[factory](fid="https://test.coma/3")
factories[factory](fid="https://noope/3")
assert list(obj1.__class__.objects.local().order_by("id")) == [obj1, obj2]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment