Support playlist fetch, this ill break the frontend (need to update playlist.uuid for endpoint instead of playlist.id)
Done locally but will commit after discussion, we can also delete lookup_field = "uuid" from playlistviewset
This commit is contained in:
parent
0c2be3e576
commit
988937f34d
|
@ -31,6 +31,7 @@ def privacy_level_query(user, lookup_field="privacy_level", user_field="user"):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# Federated TrackFavorite don't have an user associated with the trackfavorite.actor
|
# Federated TrackFavorite don't have an user associated with the trackfavorite.actor
|
||||||
|
# to do : if we implement the followers privacy_level this will become a problem
|
||||||
no_user_query = models.Q(**{f"{user_field}__isnull": True})
|
no_user_query = models.Q(**{f"{user_field}__isnull": True})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -13,6 +13,7 @@ from funkwhale_api.audio import models as audio_models
|
||||||
from funkwhale_api.common import fields as common_fields
|
from funkwhale_api.common import fields as common_fields
|
||||||
from funkwhale_api.common import serializers as common_serializers
|
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.playlists import models as playlist_models
|
||||||
from funkwhale_api.users import serializers as users_serializers
|
from funkwhale_api.users import serializers as users_serializers
|
||||||
|
|
||||||
from . import filters, models
|
from . import filters, models
|
||||||
|
@ -200,6 +201,7 @@ FETCH_OBJECT_CONFIG = {
|
||||||
"upload": {"queryset": music_models.Upload.objects.all(), "id_attr": "uuid"},
|
"upload": {"queryset": music_models.Upload.objects.all(), "id_attr": "uuid"},
|
||||||
"account": {"queryset": models.Actor.objects.all(), "id_attr": "full_username"},
|
"account": {"queryset": models.Actor.objects.all(), "id_attr": "full_username"},
|
||||||
"channel": {"queryset": audio_models.Channel.objects.all(), "id_attr": "uuid"},
|
"channel": {"queryset": audio_models.Channel.objects.all(), "id_attr": "uuid"},
|
||||||
|
"playlist": {"queryset": playlist_models.Playlist.objects.all(), "id_attr": "uuid"},
|
||||||
}
|
}
|
||||||
FETCH_OBJECT_FIELD = common_fields.GenericRelation(FETCH_OBJECT_CONFIG)
|
FETCH_OBJECT_FIELD = common_fields.GenericRelation(FETCH_OBJECT_CONFIG)
|
||||||
|
|
||||||
|
|
|
@ -405,6 +405,7 @@ class Fetch(models.Model):
|
||||||
serializers.ChannelUploadSerializer,
|
serializers.ChannelUploadSerializer,
|
||||||
],
|
],
|
||||||
contexts.FW.Library: [serializers.LibrarySerializer],
|
contexts.FW.Library: [serializers.LibrarySerializer],
|
||||||
|
contexts.FW.Playlist: [serializers.PlaylistSerializer],
|
||||||
contexts.AS.Group: [serializers.ActorSerializer],
|
contexts.AS.Group: [serializers.ActorSerializer],
|
||||||
contexts.AS.Person: [serializers.ActorSerializer],
|
contexts.AS.Person: [serializers.ActorSerializer],
|
||||||
contexts.AS.Organization: [serializers.ActorSerializer],
|
contexts.AS.Organization: [serializers.ActorSerializer],
|
||||||
|
|
|
@ -940,7 +940,7 @@ OBJECT_SERIALIZERS = {t: ObjectSerializer for t in activity.OBJECT_TYPES}
|
||||||
def get_additional_fields(data):
|
def get_additional_fields(data):
|
||||||
UNSET = object()
|
UNSET = object()
|
||||||
additional_fields = {}
|
additional_fields = {}
|
||||||
for field in ["name", "summary"]:
|
for field in ["name", "summary", "library", "audience", "published"]:
|
||||||
v = data.get(field, UNSET)
|
v = data.get(field, UNSET)
|
||||||
if v == UNSET:
|
if v == UNSET:
|
||||||
continue
|
continue
|
||||||
|
@ -2399,7 +2399,6 @@ class PlaylistSerializer(jsonld.JsonLdSerializer):
|
||||||
"name": playlist.name,
|
"name": playlist.name,
|
||||||
"attributedTo": playlist.actor.fid,
|
"attributedTo": playlist.actor.fid,
|
||||||
"published": playlist.creation_date.isoformat(),
|
"published": playlist.creation_date.isoformat(),
|
||||||
"audience": playlist.privacy_level,
|
|
||||||
"library": playlist.library.fid,
|
"library": playlist.library.fid,
|
||||||
}
|
}
|
||||||
payload["audience"] = (
|
payload["audience"] = (
|
||||||
|
@ -2430,9 +2429,15 @@ class PlaylistSerializer(jsonld.JsonLdSerializer):
|
||||||
"actor": actor,
|
"actor": actor,
|
||||||
"name": validated_data["name"],
|
"name": validated_data["name"],
|
||||||
"creation_date": validated_data["published"],
|
"creation_date": validated_data["published"],
|
||||||
"privacy_level": validated_data["audience"],
|
|
||||||
"library": library,
|
"library": library,
|
||||||
}
|
}
|
||||||
|
if not actor.is_local:
|
||||||
|
ap_to_fw_data["privacy_level"] = (
|
||||||
|
contexts.AS.Public
|
||||||
|
if validated_data["privacy_level"] == "everyone"
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
|
||||||
playlist, created = playlists_models.Playlist.objects.update_or_create(
|
playlist, created = playlists_models.Playlist.objects.update_or_create(
|
||||||
defaults=ap_to_fw_data,
|
defaults=ap_to_fw_data,
|
||||||
**{
|
**{
|
||||||
|
@ -2445,16 +2450,8 @@ class PlaylistSerializer(jsonld.JsonLdSerializer):
|
||||||
|
|
||||||
return playlist
|
return playlist
|
||||||
|
|
||||||
def validate(self, data):
|
def update(self, instance, validated_data):
|
||||||
validated_data = super().validate(data)
|
return self.create(validated_data)
|
||||||
if validated_data["audience"] not in [
|
|
||||||
"https://www.w3.org/ns/activitystreams#Public",
|
|
||||||
"everyone",
|
|
||||||
]:
|
|
||||||
raise serializers.ValidationError("Privacy_level must be everyone")
|
|
||||||
|
|
||||||
validated_data["audience"] = "everyone"
|
|
||||||
return validated_data
|
|
||||||
|
|
||||||
|
|
||||||
class PlaylistCollectionSerializer(PaginatedCollectionSerializer):
|
class PlaylistCollectionSerializer(PaginatedCollectionSerializer):
|
||||||
|
@ -2474,6 +2471,8 @@ class PlaylistCollectionSerializer(PaginatedCollectionSerializer):
|
||||||
"tracks",
|
"tracks",
|
||||||
),
|
),
|
||||||
"type": "Playlist",
|
"type": "Playlist",
|
||||||
|
"library": playlist.library.fid,
|
||||||
|
"published": playlist.creation_date.isoformat(),
|
||||||
}
|
}
|
||||||
r = super().to_representation(conf)
|
r = super().to_representation(conf)
|
||||||
return r
|
return r
|
||||||
|
|
|
@ -365,6 +365,20 @@ def has_library_access(request, library):
|
||||||
return library.received_follows.filter(actor=actor, approved=True).exists()
|
return library.received_follows.filter(actor=actor, approved=True).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def has_playlist_access(request, playlist):
|
||||||
|
if playlist.privacy_level == "everyone":
|
||||||
|
return True
|
||||||
|
if request.user.is_authenticated and request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
actor = request.actor
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return playlist.library.received_follows.filter(actor=actor, approved=True).exists()
|
||||||
|
|
||||||
|
|
||||||
class MusicLibraryViewSet(
|
class MusicLibraryViewSet(
|
||||||
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
|
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
|
||||||
):
|
):
|
||||||
|
@ -731,11 +745,13 @@ class PlaylistViewSet(
|
||||||
"track",
|
"track",
|
||||||
),
|
),
|
||||||
"item_serializer": serializers.PlaylistTrackSerializer,
|
"item_serializer": serializers.PlaylistTrackSerializer,
|
||||||
|
"library": playlist.library.fid,
|
||||||
}
|
}
|
||||||
return get_collection_response(
|
return get_collection_response(
|
||||||
conf=conf,
|
conf=conf,
|
||||||
querystring=request.GET,
|
querystring=request.GET,
|
||||||
collection_serializer=serializers.PlaylistCollectionSerializer(playlist),
|
collection_serializer=serializers.PlaylistCollectionSerializer(playlist),
|
||||||
|
page_access_check=lambda: has_playlist_access(request, playlist),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ class Playlist(federation_models.FederationMixin):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return f"/library/playlists/{self.pk}"
|
return f"/library/playlists/{self.uuid}"
|
||||||
|
|
||||||
def get_federation_id(self):
|
def get_federation_id(self):
|
||||||
if self.fid:
|
if self.fid:
|
||||||
|
|
|
@ -39,7 +39,8 @@ class PlaylistSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Playlist
|
model = models.Playlist
|
||||||
fields = (
|
fields = (
|
||||||
"id",
|
"uuid",
|
||||||
|
"fid",
|
||||||
"name",
|
"name",
|
||||||
"actor",
|
"actor",
|
||||||
"modification_date",
|
"modification_date",
|
||||||
|
@ -51,7 +52,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
|
||||||
"is_playable",
|
"is_playable",
|
||||||
"library",
|
"library",
|
||||||
)
|
)
|
||||||
read_only_fields = ["id", "modification_date", "creation_date"]
|
read_only_fields = ["uuid", "fid", "modification_date", "creation_date"]
|
||||||
|
|
||||||
@extend_schema_field(OpenApiTypes.URI)
|
@extend_schema_field(OpenApiTypes.URI)
|
||||||
def get_library(self, obj):
|
def get_library(self, obj):
|
||||||
|
|
|
@ -30,6 +30,7 @@ class PlaylistViewSet(
|
||||||
mixins.ListModelMixin,
|
mixins.ListModelMixin,
|
||||||
viewsets.GenericViewSet,
|
viewsets.GenericViewSet,
|
||||||
):
|
):
|
||||||
|
lookup_field = "uuid"
|
||||||
serializer_class = serializers.PlaylistSerializer
|
serializer_class = serializers.PlaylistSerializer
|
||||||
queryset = (
|
queryset = (
|
||||||
models.Playlist.objects.all()
|
models.Playlist.objects.all()
|
||||||
|
|
|
@ -51,13 +51,15 @@ There is no other reason to share the playlit.library to remote.
|
||||||
- [x] Support library.playlist_uploads in library scan -> add playlist_uploads in items in library federation viewset
|
- [x] Support library.playlist_uploads in library scan -> add playlist_uploads in items in library federation viewset
|
||||||
- [x] investigate library scan bug : don't delete old content of the lib (local cache?): we need to empty the playlist before the scan(not ideal but less work)
|
- [x] investigate library scan bug : don't delete old content of the lib (local cache?): we need to empty the playlist before the scan(not ideal but less work)
|
||||||
- [x] check actor has only have three built-in libs and upload.playlist_libraries is private after migration
|
- [x] check actor has only have three built-in libs and upload.playlist_libraries is private after migration
|
||||||
|
- [x] Playlist discovery : fetch federation endpoint for playlists
|
||||||
|
- [ ] Seem like the federation fetch (either with fetch endpoint or retreive_ap_obj) is deleting the `privacy_level` since `audience` can only be public or null. Avoid `privacy_level` to be updated if it a local playlist.
|
||||||
|
|
||||||
### Follow up
|
### Follow up
|
||||||
|
|
||||||
- [ ] Add the frontend playlist button in the new ui
|
- [ ] Add the frontend playlist button in the new ui
|
||||||
- [ ] Finish library drop (delete libraries endpoints)
|
- [ ] Playlist discovery : display playlist fid in the frontend
|
||||||
- [ ] Playlist discovery : fetch federation endpoint for playlists
|
|
||||||
- [ ] Playlist discovery : add the playlist to my playlist collection = follow request to playlist
|
|
||||||
- [ ] Playlist Track activity (to avoid having to refetch the whole playlist)
|
|
||||||
- [ ] Document : The user that want to federate need to activate remote activities in it's user settings. Even if the library is public the playlist activities will not be sended to remote -> We need to implement a followers activity setting (#2362)
|
- [ ] Document : The user that want to federate need to activate remote activities in it's user settings. Even if the library is public the playlist activities will not be sended to remote -> We need to implement a followers activity setting (#2362)
|
||||||
- [ ] allow users to change the upload to another built-in lib, make sure the upload is not delete (we would loose the playlist_library relation) but only updated
|
- [ ] allow users to change the upload to another built-in lib, make sure the upload is not delete (we would loose the playlist_library relation) but only updated
|
||||||
|
|
||||||
|
- [ ] Playlist discovery : add the playlist to my playlist collection = follow request to playlist
|
||||||
|
- [ ] Playlist Track activity (to avoid having to refetch the whole playlist)
|
||||||
|
|
Loading…
Reference in New Issue