Skip to content
Snippets Groups Projects
serializers.py 51.1 KiB
Newer Older
  • Learn to ignore specific revisions
  •             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["url"],
    
                actor=instance.attributed_to,
            )
    
            return validated_data
    
    
        def validate(self, data):
            validated_data = super().validate(data)
            if data.get("content"):
                validated_data["description"] = {
                    "content_type": data["mediaType"],
                    "text": data["content"],
                }
            return validated_data
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    class ArtistSerializer(MusicEntitySerializer):
    
        image = ImageSerializer(
    
            allowed_mimetypes=["image/*"], allow_null=True, required=False
        )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        updateable_fields = [
            ("name", "name"),
            ("musicbrainzId", "mbid"),
            ("attributedTo", "attributed_to"),
    
            ("image", "attachment_cover"),
    
            model = music_models.Artist
    
            jsonld_mapping = common_utils.concat_dicts(
                MUSIC_ENTITY_JSONLD_MAPPING,
                {
                    "released": jsonld.first_val(contexts.FW.released),
                    "artists": jsonld.first_attr(contexts.FW.artists, "@list"),
                    "image": jsonld.first_obj(contexts.AS.image),
                },
            )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def to_representation(self, instance):
            d = {
                "type": "Artist",
                "id": instance.fid,
                "name": instance.name,
                "published": instance.creation_date.isoformat(),
                "musicbrainzId": str(instance.mbid) if instance.mbid else None,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "attributedTo": instance.attributed_to.fid
                if instance.attributed_to
                else None,
    
                "tag": self.get_tags_repr(instance),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            }
    
            include_content(d, instance.description)
    
            include_image(d, instance.attachment_cover)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if self.context.get("include_ap_context", self.parent is None):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                d["@context"] = jsonld.get_default_context()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return d
    
    
    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 = ImageSerializer(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            allowed_mimetypes=["image/*"], allow_null=True, required=False
        )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        updateable_fields = [
            ("name", "title"),
            ("musicbrainzId", "mbid"),
            ("attributedTo", "attributed_to"),
            ("released", "release_date"),
    
            ("cover", "attachment_cover"),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        ]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
            model = music_models.Album
    
            jsonld_mapping = common_utils.concat_dicts(
    
                MUSIC_ENTITY_JSONLD_MAPPING,
                {
                    "released": jsonld.first_val(contexts.FW.released),
                    "artists": jsonld.first_attr(contexts.FW.artists, "@list"),
                    "cover": jsonld.first_obj(contexts.FW.cover),
                },
            )
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def to_representation(self, instance):
            d = {
                "type": "Album",
                "id": instance.fid,
                "name": instance.title,
                "published": instance.creation_date.isoformat(),
                "musicbrainzId": str(instance.mbid) if instance.mbid else None,
                "released": instance.release_date.isoformat()
                if instance.release_date
                else None,
                "artists": [
                    ArtistSerializer(
                        instance.artist, context={"include_ap_context": False}
                    ).data
                ],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "attributedTo": instance.attributed_to.fid
                if instance.attributed_to
                else None,
    
                "tag": self.get_tags_repr(instance),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            }
    
            include_content(d, instance.description)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if instance.attachment_cover:
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                d["cover"] = {
                    "type": "Link",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    "href": instance.attachment_cover.download_url_original,
                    "mediaType": instance.attachment_cover.mimetype or "image/jpeg",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                }
    
                include_image(d, instance.attachment_cover)
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if self.context.get("include_ap_context", self.parent is None):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                d["@context"] = jsonld.get_default_context()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return d
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    class TrackSerializer(MusicEntitySerializer):
        position = serializers.IntegerField(min_value=0, allow_null=True, required=False)
    
        disc = serializers.IntegerField(min_value=1, allow_null=True, required=False)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        artists = serializers.ListField(child=ArtistSerializer(), min_length=1)
        album = AlbumSerializer()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        license = serializers.URLField(allow_null=True, required=False)
        copyright = serializers.CharField(allow_null=True, required=False)
    
        image = ImageSerializer(
    
            allowed_mimetypes=["image/*"], allow_null=True, required=False
        )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        updateable_fields = [
            ("name", "title"),
            ("musicbrainzId", "mbid"),
            ("attributedTo", "attributed_to"),
            ("disc", "disc_number"),
            ("position", "position"),
            ("copyright", "copyright"),
            ("license", "license"),
    
            ("image", "attachment_cover"),
    
            model = music_models.Track
    
            jsonld_mapping = common_utils.concat_dicts(
    
                MUSIC_ENTITY_JSONLD_MAPPING,
                {
                    "album": jsonld.first_obj(contexts.FW.album),
                    "artists": jsonld.first_attr(contexts.FW.artists, "@list"),
                    "copyright": jsonld.first_val(contexts.FW.copyright),
                    "disc": jsonld.first_val(contexts.FW.disc),
                    "license": jsonld.first_id(contexts.FW.license),
                    "position": jsonld.first_val(contexts.FW.position),
    
                    "image": jsonld.first_obj(contexts.AS.image),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def to_representation(self, instance):
            d = {
                "type": "Track",
                "id": instance.fid,
                "name": instance.title,
                "published": instance.creation_date.isoformat(),
                "musicbrainzId": str(instance.mbid) if instance.mbid else None,
                "position": instance.position,
    
                "disc": instance.disc_number,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "license": instance.local_license["identifiers"][0]
                if instance.local_license
                else None,
                "copyright": instance.copyright if instance.copyright else None,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "artists": [
                    ArtistSerializer(
                        instance.artist, context={"include_ap_context": False}
                    ).data
                ],
                "album": AlbumSerializer(
                    instance.album, context={"include_ap_context": False}
                ).data,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "attributedTo": instance.attributed_to.fid
                if instance.attributed_to
                else None,
    
                "tag": self.get_tags_repr(instance),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            }
    
            include_content(d, instance.description)
    
            include_image(d, instance.attachment_cover)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if self.context.get("include_ap_context", self.parent is None):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                d["@context"] = jsonld.get_default_context()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return d
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def create(self, validated_data):
            from funkwhale_api.music import tasks as music_tasks
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            references = {}
            actors_to_fetch = set()
            actors_to_fetch.add(
    
                common_utils.recursive_getattr(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    validated_data, "attributedTo", permissive=True
                )
            )
            actors_to_fetch.add(
    
                common_utils.recursive_getattr(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    validated_data, "album.attributedTo", permissive=True
                )
            )
            artists = (
    
                common_utils.recursive_getattr(validated_data, "artists", permissive=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                or []
            )
            album_artists = (
    
                common_utils.recursive_getattr(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    validated_data, "album.artists", permissive=True
                )
                or []
            )
            for artist in artists + album_artists:
                actors_to_fetch.add(artist.get("attributedTo"))
    
            for url in actors_to_fetch:
                if not url:
                    continue
                references[url] = actors.get_actor(url)
            metadata = music_tasks.federation_audio_track_to_metadata(
                validated_data, references
            )
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            from_activity = self.context.get("activity")
            if from_activity:
                metadata["from_activity_id"] = from_activity.pk
    
            track = music_tasks.get_track_from_import_metadata(metadata, update_cover=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return track
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def update(self, obj, validated_data):
            if validated_data.get("license"):
                validated_data["license"] = licenses.match(validated_data["license"])
            return super().update(obj, validated_data)
    
    
    class UploadSerializer(jsonld.JsonLdSerializer):
        type = serializers.ChoiceField(choices=[contexts.AS.Audio])
    
        id = serializers.URLField(max_length=500)
    
        library = serializers.URLField(max_length=500)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        url = LinkSerializer(allowed_mimetypes=["audio/*"])
    
        published = serializers.DateTimeField()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        updated = serializers.DateTimeField(required=False, allow_null=True)
        bitrate = serializers.IntegerField(min_value=0)
        size = serializers.IntegerField(min_value=0)
        duration = serializers.IntegerField(min_value=0)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        track = TrackSerializer(required=True)
    
            model = music_models.Upload
    
            jsonld_mapping = {
                "track": jsonld.first_obj(contexts.FW.track),
                "library": jsonld.first_id(contexts.FW.library),
                "url": jsonld.first_obj(contexts.AS.url),
                "published": jsonld.first_val(contexts.AS.published),
                "updated": jsonld.first_val(contexts.AS.updated),
                "duration": jsonld.first_val(contexts.AS.duration),
                "bitrate": jsonld.first_val(contexts.FW.bitrate),
                "size": jsonld.first_val(contexts.FW.size),
            }
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                raise serializers.ValidationError("Missing href")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                media_type = v["mediaType"]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                raise serializers.ValidationError("Missing mediaType")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if not media_type or not media_type.startswith("audio/"):
                raise serializers.ValidationError("Invalid mediaType")
    
        def validate_library(self, v):
            lb = self.context.get("library")
            if lb:
                if lb.fid != v:
                    raise serializers.ValidationError("Invalid library")
                return lb
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
            actor = self.context.get("actor")
            kwargs = {}
            if actor:
                kwargs["actor"] = actor
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                return music_models.Library.objects.get(fid=v, **kwargs)
    
            except music_models.Library.DoesNotExist:
                raise serializers.ValidationError("Invalid library")
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            try:
                return music_models.Upload.objects.get(fid=validated_data["id"])
            except music_models.Upload.DoesNotExist:
                pass
    
            track = TrackSerializer(
                context={"activity": self.context.get("activity")}
            ).create(validated_data["track"])
    
            data = {
                "fid": validated_data["id"],
    
                "mimetype": validated_data["url"]["mediaType"],
                "source": validated_data["url"]["href"],
                "creation_date": validated_data["published"],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "modification_date": validated_data.get("updated"),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "track": track,
                "duration": validated_data["duration"],
                "size": validated_data["size"],
                "bitrate": validated_data["bitrate"],
                "library": validated_data["library"],
                "from_activity": self.context.get("activity"),
                "import_status": "finished",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return music_models.Upload.objects.create(**data)
    
    
        def to_representation(self, instance):
            track = instance.track
            d = {
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "type": "Audio",
    
                "id": instance.get_federation_id(),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "library": instance.library.fid,
                "name": track.full_name,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "published": instance.creation_date.isoformat(),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "bitrate": instance.bitrate,
                "size": instance.size,
                "duration": instance.duration,
    
                        "href": utils.full_url(instance.listen_url_no_download),
    
                        "type": "Link",
                        "mediaType": instance.mimetype,
                    },
                    {
                        "type": "Link",
                        "mediaType": "text/html",
                        "href": utils.full_url(instance.track.get_absolute_url()),
                    },
                ],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "track": TrackSerializer(track, context={"include_ap_context": False}).data,
    
                "to": contexts.AS.Public
                if instance.library.privacy_level == "everyone"
                else "",
                "attributedTo": instance.library.actor.fid,
    
            if instance.modification_date:
                d["updated"] = instance.modification_date.isoformat()
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if self.context.get("include_ap_context", self.parent is None):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                d["@context"] = jsonld.get_default_context()
    
    class ActorDeleteSerializer(jsonld.JsonLdSerializer):
        fid = serializers.URLField(max_length=500)
    
        class Meta:
            jsonld_mapping = {"fid": jsonld.first_id(contexts.AS.object)}
    
    
    
    class NodeInfoLinkSerializer(serializers.Serializer):
        href = serializers.URLField()
        rel = serializers.URLField()
    
    
    class NodeInfoSerializer(serializers.Serializer):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        links = serializers.ListField(child=NodeInfoLinkSerializer(), min_length=1)
    
    
    
    class ChannelOutboxSerializer(PaginatedCollectionSerializer):
        type = serializers.ChoiceField(choices=[contexts.AS.OrderedCollection])
    
        class Meta:
            jsonld_mapping = PAGINATED_COLLECTION_JSONLD_MAPPING
    
        def to_representation(self, channel):
            conf = {
                "id": channel.actor.outbox_url,
                "page_size": 100,
                "attributedTo": channel.actor,
                "actor": channel.actor,
                "items": channel.library.uploads.for_federation()
                .order_by("-creation_date")
                .filter(track__artist=channel.artist),
                "type": "OrderedCollection",
            }
            r = super().to_representation(conf)
            return r
    
    
    class ChannelUploadSerializer(serializers.Serializer):
        def to_representation(self, upload):
            data = {
                "id": upload.fid,
                "type": "Audio",
                "name": upload.track.full_name,
                "attributedTo": upload.library.channel.actor.fid,
                "published": upload.creation_date.isoformat(),
                "to": contexts.AS.Public
                if upload.library.privacy_level == "everyone"
                else "",
                "url": [
                    {
                        "type": "Link",
                        "mimeType": upload.mimetype,
    
                        "href": utils.full_url(upload.listen_url_no_download),
    
                    },
                    {
                        "type": "Link",
                        "mimeType": "text/html",
                        "href": utils.full_url(upload.track.get_absolute_url()),
                    },
                ],
            }
    
            include_content(data, upload.track.description)
    
            tags = [item.tag.name for item in upload.get_all_tagged_items()]
            if tags:
                data["tag"] = [repr_tag(name) for name in tags]
                data["summary"] = " ".join(["#{}".format(name) for name in tags])
    
            if self.context.get("include_ap_context", True):
                data["@context"] = jsonld.get_default_context()
    
            return data
    
    
    class ChannelCreateUploadSerializer(serializers.Serializer):
        def to_representation(self, upload):
            return {
                "@context": jsonld.get_default_context(),
                "type": "Create",
                "actor": upload.library.channel.actor.fid,
                "object": ChannelUploadSerializer(
                    upload, context={"include_ap_context": False}
                ).data,
            }