kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
				
				
				
			See #689: now serve AP representations for uploads, tracks, albums and artists
							rodzic
							
								
									8e4320d14a
								
							
						
					
					
						commit
						d243d6a2f5
					
				|  | @ -3,6 +3,7 @@ import uuid | |||
| from django.contrib.postgres.fields import JSONField | ||||
| from django.contrib.contenttypes.fields import GenericForeignKey | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.conf import settings | ||||
| from django.db import models, transaction | ||||
| from django.utils import timezone | ||||
| from django.urls import reverse | ||||
|  | @ -10,6 +11,18 @@ from django.urls import reverse | |||
| 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): | ||||
|     def get_for_target(self, target): | ||||
|         content_type = ContentType.objects.get_for_model(target) | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ from rest_framework.decorators import action | |||
| 
 | ||||
| from funkwhale_api.common import preferences | ||||
| 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 | ||||
| 
 | ||||
|  | @ -202,9 +203,17 @@ class MusicUploadViewSet( | |||
|     authentication_classes = [authentication.SignatureAuthentication] | ||||
|     permission_classes = [] | ||||
|     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" | ||||
| 
 | ||||
|     def get_queryset(self): | ||||
|         queryset = super().get_queryset() | ||||
|         actor = music_utils.get_actor_from_request(self.request) | ||||
|         return queryset.playable_by(actor) | ||||
| 
 | ||||
| 
 | ||||
| class MusicArtistViewSet( | ||||
|     FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet | ||||
|  | @ -212,7 +221,8 @@ class MusicArtistViewSet( | |||
|     authentication_classes = [authentication.SignatureAuthentication] | ||||
|     permission_classes = [] | ||||
|     renderer_classes = [renderers.ActivityPubRenderer] | ||||
|     queryset = music_models.Artist.objects.none() | ||||
|     queryset = music_models.Artist.objects.local() | ||||
|     serializer_class = serializers.ArtistSerializer | ||||
|     lookup_field = "uuid" | ||||
| 
 | ||||
| 
 | ||||
|  | @ -222,7 +232,8 @@ class MusicAlbumViewSet( | |||
|     authentication_classes = [authentication.SignatureAuthentication] | ||||
|     permission_classes = [] | ||||
|     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" | ||||
| 
 | ||||
| 
 | ||||
|  | @ -232,5 +243,8 @@ class MusicTrackViewSet( | |||
|     authentication_classes = [authentication.SignatureAuthentication] | ||||
|     permission_classes = [] | ||||
|     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" | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ from versatileimagefield.image_warmer import VersatileImageFieldWarmer | |||
| 
 | ||||
| from funkwhale_api import musicbrainz | ||||
| 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 utils as common_utils | ||||
| from funkwhale_api.federation import models as federation_models | ||||
|  | @ -141,7 +142,7 @@ class License(models.Model): | |||
|         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): | ||||
|         return self.annotate(_albums_count=models.Count("albums")) | ||||
| 
 | ||||
|  | @ -215,7 +216,7 @@ def import_tracks(instance, cleaned_data, raw_data): | |||
|         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): | ||||
|         return self.annotate(_tracks_count=models.Count("tracks")) | ||||
| 
 | ||||
|  | @ -416,7 +417,7 @@ class Lyrics(models.Model): | |||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class TrackQuerySet(models.QuerySet): | ||||
| class TrackQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet): | ||||
|     def for_nested_serialization(self): | ||||
|         return self.select_related().select_related("album__artist", "artist") | ||||
| 
 | ||||
|  |  | |||
|  | @ -174,3 +174,75 @@ def test_music_library_retrieve_page_follow( | |||
|     response = api_client.get(url, {"page": 1}) | ||||
| 
 | ||||
|     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): | |||
|     t4 = factories["music.Track"](album=album, position=2, disc_number=2) | ||||
| 
 | ||||
|     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] | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Eliot Berriot
						Eliot Berriot