diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index 96ff2fcd3..c057799a6 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -31,17 +31,9 @@ class TruncatedCharField(serializers.CharField): return v -class LinkSerializer(jsonld.JsonLdSerializer): - type = serializers.ChoiceField(choices=[contexts.AS.Link, contexts.AS.Image]) - href = serializers.URLField(max_length=500) +class MediaSerializer(jsonld.JsonLdSerializer): mediaType = serializers.CharField() - class Meta: - jsonld_mapping = { - "href": jsonld.first_id(contexts.AS.href), - "mediaType": jsonld.first_val(contexts.AS.mediaType), - } - def __init__(self, *args, **kwargs): self.allowed_mimetypes = kwargs.pop("allowed_mimetypes", []) super().__init__(*args, **kwargs) @@ -62,6 +54,43 @@ class LinkSerializer(jsonld.JsonLdSerializer): ) +class LinkSerializer(MediaSerializer): + type = serializers.ChoiceField(choices=[contexts.AS.Link]) + href = serializers.URLField(max_length=500) + + class Meta: + jsonld_mapping = { + "href": jsonld.first_id(contexts.AS.href), + "mediaType": jsonld.first_val(contexts.AS.mediaType), + } + + +class ImageSerializer(MediaSerializer): + type = serializers.ChoiceField(choices=[contexts.AS.Image, contexts.AS.Link]) + href = serializers.URLField(max_length=500, required=False) + url = serializers.URLField(max_length=500, required=False) + + class Meta: + jsonld_mapping = { + "url": jsonld.first_id(contexts.AS.url), + "href": jsonld.first_id(contexts.AS.href), + "mediaType": jsonld.first_val(contexts.AS.mediaType), + } + + def validate(self, data): + validated_data = super().validate(data) + if "url" not in validated_data: + try: + validated_data["url"] = validated_data["href"] + except KeyError: + if self.required: + raise serializers.ValidationError( + "You need to provide a url or href" + ) + + return validated_data + + class EndpointsSerializer(jsonld.JsonLdSerializer): sharedInbox = serializers.URLField(max_length=500, required=False) @@ -95,7 +124,7 @@ class ActorSerializer(jsonld.JsonLdSerializer): following = serializers.URLField(max_length=500, required=False, allow_null=True) publicKey = PublicKeySerializer(required=False) endpoints = EndpointsSerializer(required=False) - icon = LinkSerializer( + icon = ImageSerializer( allowed_mimetypes=["image/*"], allow_null=True, required=False ) @@ -202,7 +231,7 @@ class ActorSerializer(jsonld.JsonLdSerializer): common_utils.attach_file( actor, "attachment_icon", - {"url": new_value["href"], "mimetype": new_value["mediaType"]} + {"url": new_value["url"], "mimetype": new_value["mediaType"]} if new_value else None, ) @@ -853,7 +882,7 @@ def include_image(repr, attachment, field="image"): if attachment: repr[field] = { "type": "Image", - "href": attachment.download_url_original, + "url": attachment.download_url_original, "mediaType": attachment.mimetype or "image/jpeg", } else: @@ -915,14 +944,14 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer): if ( instance.attachment_cover - and instance.attachment_cover.url == attachment_cover["href"] + and instance.attachment_cover.url == attachment_cover["url"] ): # we already have the proper attachment return validated_data # create the attachment by hand so it can be attached as the cover validated_data["attachment_cover"] = common_models.Attachment.objects.create( mimetype=attachment_cover["mediaType"], - url=attachment_cover["href"], + url=attachment_cover["url"], actor=instance.attributed_to, ) return validated_data @@ -938,7 +967,7 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer): class ArtistSerializer(MusicEntitySerializer): - image = LinkSerializer( + image = ImageSerializer( allowed_mimetypes=["image/*"], allow_null=True, required=False ) updateable_fields = [ @@ -982,7 +1011,7 @@ class AlbumSerializer(MusicEntitySerializer): released = serializers.DateField(allow_null=True, required=False) artists = serializers.ListField(child=ArtistSerializer(), min_length=1) # XXX: 1.0 rename to image - cover = LinkSerializer( + cover = ImageSerializer( allowed_mimetypes=["image/*"], allow_null=True, required=False ) updateable_fields = [ @@ -1045,7 +1074,7 @@ class TrackSerializer(MusicEntitySerializer): album = AlbumSerializer() license = serializers.URLField(allow_null=True, required=False) copyright = serializers.CharField(allow_null=True, required=False) - image = LinkSerializer( + image = ImageSerializer( allowed_mimetypes=["image/*"], allow_null=True, required=False ) diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py index 636a54cf4..dce014f15 100644 --- a/api/funkwhale_api/music/tasks.py +++ b/api/funkwhale_api/music/tasks.py @@ -302,7 +302,12 @@ def process_upload(upload, update_denormalization=True): def get_cover(obj, field): cover = obj.get(field) if cover: - return {"mimetype": cover["mediaType"], "url": cover["href"]} + try: + url = cover["url"] + except KeyError: + url = cover["href"] + + return {"mimetype": cover["mediaType"], "url": url} def federation_audio_track_to_metadata(payload, references): diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py index 555684e07..d5a781bd6 100644 --- a/api/tests/federation/test_serializers.py +++ b/api/tests/federation/test_serializers.py @@ -39,7 +39,7 @@ def test_actor_serializer_from_ap(db): "icon": { "type": "Image", "mediaType": "image/jpeg", - "href": "https://image.example/image.png", + "url": "https://image.example/image.png", }, } @@ -65,7 +65,7 @@ def test_actor_serializer_from_ap(db): assert actor.private_key is None assert actor.public_key == payload["publicKey"]["publicKeyPem"] assert actor.domain_id == "test.federation" - assert actor.attachment_icon.url == payload["icon"]["href"] + assert actor.attachment_icon.url == payload["icon"]["url"] assert actor.attachment_icon.mimetype == payload["icon"]["mediaType"] @@ -139,7 +139,7 @@ def test_actor_serializer_to_ap(factories): expected["icon"] = { "type": "Image", "mediaType": "image/jpeg", - "href": utils.full_url(ac.attachment_icon.file.url), + "url": utils.full_url(ac.attachment_icon.file.url), } serializer = serializers.ActorSerializer(ac) @@ -595,7 +595,7 @@ def test_activity_pub_artist_serializer_to_ap(factories): "image": { "type": "Image", "mediaType": "image/jpeg", - "href": utils.full_url(artist.attachment_cover.file.url), + "url": utils.full_url(artist.attachment_cover.file.url), }, "tag": [ {"type": "Hashtag", "name": "#Punk"}, @@ -626,7 +626,7 @@ def test_activity_pub_album_serializer_to_ap(factories): "image": { "type": "Image", "mediaType": "image/jpeg", - "href": utils.full_url(album.attachment_cover.file.url), + "url": utils.full_url(album.attachment_cover.file.url), }, "musicbrainzId": album.mbid, "published": album.creation_date.isoformat(), @@ -661,7 +661,7 @@ def test_activity_pub_artist_serializer_from_ap_update(factories, faker): "attributedTo": artist.attributed_to.fid, "mediaType": "text/html", "content": common_utils.render_html(faker.sentence(), "text/html"), - "image": {"type": "Image", "mediaType": "image/jpeg", "href": faker.url()}, + "image": {"type": "Image", "mediaType": "image/jpeg", "url": faker.url()}, "tag": [ {"type": "Hashtag", "name": "#Punk"}, {"type": "Hashtag", "name": "#Rock"}, @@ -677,7 +677,7 @@ def test_activity_pub_artist_serializer_from_ap_update(factories, faker): assert artist.name == payload["name"] assert str(artist.mbid) == payload["musicbrainzId"] - assert artist.attachment_cover.url == payload["image"]["href"] + assert artist.attachment_cover.url == payload["image"]["url"] assert artist.attachment_cover.mimetype == payload["image"]["mediaType"] assert artist.description.text == payload["content"] assert artist.description.content_type == "text/html" @@ -767,7 +767,7 @@ def test_activity_pub_track_serializer_to_ap(factories): "image": { "type": "Image", "mediaType": "image/jpeg", - "href": utils.full_url(track.attachment_cover.file.url), + "url": utils.full_url(track.attachment_cover.file.url), }, } serializer = serializers.TrackSerializer(track) @@ -797,8 +797,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker): "content": "Hello there", "attributedTo": track_attributed_to.fid, "image": { - "type": "Link", - "href": "https://cover.image/track.png", + "type": "Image", + "url": "https://cover.image/track.png", "mediaType": "image/png", }, "album": { @@ -829,8 +829,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker): "attributedTo": album_artist_attributed_to.fid, "tag": [{"type": "Hashtag", "name": "AlbumArtistTag"}], "image": { - "type": "Link", - "href": "https://cover.image/album-artist.png", + "type": "Image", + "url": "https://cover.image/album-artist.png", "mediaType": "image/png", }, } @@ -848,8 +848,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker): "published": published.isoformat(), "tag": [{"type": "Hashtag", "name": "ArtistTag"}], "image": { - "type": "Link", - "href": "https://cover.image/artist.png", + "type": "Image", + "url": "https://cover.image/artist.png", "mediaType": "image/png", }, } @@ -877,7 +877,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker): assert str(track.mbid) == data["musicbrainzId"] assert track.description.text == data["content"] assert track.description.content_type == "text/html" - assert track.attachment_cover.url == data["image"]["href"] + assert track.attachment_cover.url == data["image"]["url"] assert track.attachment_cover.mimetype == data["image"]["mediaType"] assert album.from_activity == activity @@ -900,7 +900,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker): assert artist.attributed_to == artist_attributed_to assert artist.description.text == data["artists"][0]["content"] assert artist.description.content_type == data["artists"][0]["mediaType"] - assert artist.attachment_cover.url == data["artists"][0]["image"]["href"] + assert artist.attachment_cover.url == data["artists"][0]["image"]["url"] assert artist.attachment_cover.mimetype == data["artists"][0]["image"]["mediaType"] assert album_artist.from_activity == activity @@ -915,8 +915,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker): == data["album"]["artists"][0]["mediaType"] ) assert ( - album_artist.attachment_cover.url - == data["album"]["artists"][0]["image"]["href"] + album_artist.attachment_cover.url == data["album"]["artists"][0]["image"]["url"] ) assert ( album_artist.attachment_cover.mimetype @@ -949,7 +948,7 @@ def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker, "attributedTo": track_attributed_to.fid, "album": serializers.AlbumSerializer(track.album).data, "artists": [serializers.ArtistSerializer(track.artist).data], - "image": {"type": "Link", "mediaType": "image/jpeg", "href": faker.url()}, + "image": {"type": "Image", "mediaType": "image/jpeg", "url": faker.url()}, "tag": [ {"type": "Hashtag", "name": "#Hello"}, # Ensure we can handle tags without a leading # @@ -970,7 +969,7 @@ def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker, assert track.description.content_type == "text/html" assert track.description.text == "hello there" assert str(track.mbid) == data["musicbrainzId"] - assert track.attachment_cover.url == data["image"]["href"] + assert track.attachment_cover.url == data["image"]["url"] assert track.attachment_cover.mimetype == data["image"]["mediaType"] set_tags.assert_called_once_with(track, *["Hello", "World"]) @@ -1159,7 +1158,7 @@ def test_local_actor_serializer_to_ap(factories): expected["icon"] = { "type": "Image", "mediaType": "image/jpeg", - "href": utils.full_url(ac.attachment_icon.file.url), + "url": utils.full_url(ac.attachment_icon.file.url), } serializer = serializers.ActorSerializer(ac)