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:
Petitminion 2025-03-18 01:36:32 +01:00
parent 0c2be3e576
commit 988937f34d
9 changed files with 43 additions and 20 deletions

View File

@ -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
# to do : if we implement the followers privacy_level this will become a problem
no_user_query = models.Q(**{f"{user_field}__isnull": True})
return (

View File

@ -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 serializers as common_serializers
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 . import filters, models
@ -200,6 +201,7 @@ FETCH_OBJECT_CONFIG = {
"upload": {"queryset": music_models.Upload.objects.all(), "id_attr": "uuid"},
"account": {"queryset": models.Actor.objects.all(), "id_attr": "full_username"},
"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)

View File

@ -405,6 +405,7 @@ class Fetch(models.Model):
serializers.ChannelUploadSerializer,
],
contexts.FW.Library: [serializers.LibrarySerializer],
contexts.FW.Playlist: [serializers.PlaylistSerializer],
contexts.AS.Group: [serializers.ActorSerializer],
contexts.AS.Person: [serializers.ActorSerializer],
contexts.AS.Organization: [serializers.ActorSerializer],

View File

@ -940,7 +940,7 @@ OBJECT_SERIALIZERS = {t: ObjectSerializer for t in activity.OBJECT_TYPES}
def get_additional_fields(data):
UNSET = object()
additional_fields = {}
for field in ["name", "summary"]:
for field in ["name", "summary", "library", "audience", "published"]:
v = data.get(field, UNSET)
if v == UNSET:
continue
@ -2399,7 +2399,6 @@ class PlaylistSerializer(jsonld.JsonLdSerializer):
"name": playlist.name,
"attributedTo": playlist.actor.fid,
"published": playlist.creation_date.isoformat(),
"audience": playlist.privacy_level,
"library": playlist.library.fid,
}
payload["audience"] = (
@ -2430,9 +2429,15 @@ class PlaylistSerializer(jsonld.JsonLdSerializer):
"actor": actor,
"name": validated_data["name"],
"creation_date": validated_data["published"],
"privacy_level": validated_data["audience"],
"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(
defaults=ap_to_fw_data,
**{
@ -2445,16 +2450,8 @@ class PlaylistSerializer(jsonld.JsonLdSerializer):
return playlist
def validate(self, data):
validated_data = super().validate(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
def update(self, instance, validated_data):
return self.create(validated_data)
class PlaylistCollectionSerializer(PaginatedCollectionSerializer):
@ -2474,6 +2471,8 @@ class PlaylistCollectionSerializer(PaginatedCollectionSerializer):
"tracks",
),
"type": "Playlist",
"library": playlist.library.fid,
"published": playlist.creation_date.isoformat(),
}
r = super().to_representation(conf)
return r

View File

@ -365,6 +365,20 @@ def has_library_access(request, library):
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(
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
@ -731,11 +745,13 @@ class PlaylistViewSet(
"track",
),
"item_serializer": serializers.PlaylistTrackSerializer,
"library": playlist.library.fid,
}
return get_collection_response(
conf=conf,
querystring=request.GET,
collection_serializer=serializers.PlaylistCollectionSerializer(playlist),
page_access_check=lambda: has_playlist_access(request, playlist),
)

View File

@ -100,7 +100,7 @@ class Playlist(federation_models.FederationMixin):
return self.name
def get_absolute_url(self):
return f"/library/playlists/{self.pk}"
return f"/library/playlists/{self.uuid}"
def get_federation_id(self):
if self.fid:

View File

@ -39,7 +39,8 @@ class PlaylistSerializer(serializers.ModelSerializer):
class Meta:
model = models.Playlist
fields = (
"id",
"uuid",
"fid",
"name",
"actor",
"modification_date",
@ -51,7 +52,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
"is_playable",
"library",
)
read_only_fields = ["id", "modification_date", "creation_date"]
read_only_fields = ["uuid", "fid", "modification_date", "creation_date"]
@extend_schema_field(OpenApiTypes.URI)
def get_library(self, obj):

View File

@ -30,6 +30,7 @@ class PlaylistViewSet(
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
lookup_field = "uuid"
serializer_class = serializers.PlaylistSerializer
queryset = (
models.Playlist.objects.all()

View File

@ -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] 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] 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
- [ ] Add the frontend playlist button in the new ui
- [ ] Finish library drop (delete libraries endpoints)
- [ ] 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)
- [ ] Playlist discovery : display playlist fid in the frontend
- [ ] 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
- [ ] Playlist discovery : add the playlist to my playlist collection = follow request to playlist
- [ ] Playlist Track activity (to avoid having to refetch the whole playlist)