Skip to content
Snippets Groups Projects
serializers.py 69.3 KiB
Newer Older
  • Learn to ignore specific revisions
  •     def create(self, validated_data):
    
            if self.instance:
                actor = self.instance.actor
            else:
                actor = utils.retrieve_ap_object(
                    validated_data["attributedTo"],
                    actor=self.context.get("fetch_actor"),
                    queryset=models.Actor,
                    serializer_class=ActorSerializer,
                )
    
            privacy = {"": "me", "./": "me", None: "me", contexts.AS.Public: "everyone"}
    
            library, created = music_models.Library.objects.update_or_create(
                fid=validated_data["id"],
                actor=actor,
                defaults={
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    "uploads_count": validated_data["totalItems"],
    
                    "name": validated_data["name"],
    
                    "description": validated_data.get("summary"),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    "followers_url": validated_data["followers"],
    
                    "privacy_level": privacy[validated_data["audience"]],
    
        def update(self, instance, validated_data):
            return self.create(validated_data)
    
    
    class CollectionPageSerializer(jsonld.JsonLdSerializer):
        type = serializers.ChoiceField(choices=[contexts.AS.CollectionPage])
    
        totalItems = serializers.IntegerField(min_value=0)
        items = serializers.ListField()
    
        actor = serializers.URLField(max_length=500, required=False)
        attributedTo = serializers.URLField(max_length=500, required=False)
    
        id = serializers.URLField(max_length=500)
        first = serializers.URLField(max_length=500)
        last = serializers.URLField(max_length=500)
        next = serializers.URLField(max_length=500, required=False)
        prev = serializers.URLField(max_length=500, required=False)
        partOf = serializers.URLField(max_length=500)
    
        class Meta:
            jsonld_mapping = {
                "totalItems": jsonld.first_val(contexts.AS.totalItems),
                "items": jsonld.raw(contexts.AS.items),
                "actor": jsonld.first_id(contexts.AS.actor),
    
                "attributedTo": jsonld.first_id(contexts.AS.attributedTo),
    
                "first": jsonld.first_id(contexts.AS.first),
                "last": jsonld.first_id(contexts.AS.last),
                "next": jsonld.first_id(contexts.AS.next),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "prev": jsonld.first_id(contexts.AS.prev),
    
                "partOf": jsonld.first_id(contexts.AS.partOf),
            }
    
    
        def validate_items(self, v):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            item_serializer = self.context.get("item_serializer")
    
            if not item_serializer:
                return v
            raw_items = [item_serializer(data=i, context=self.context) for i in v]
    
            for i in raw_items:
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                try:
                    i.is_valid(raise_exception=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                except serializers.ValidationError:
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    logger.debug("Invalid item %s: %s", i.data, i.errors)
    
        def to_representation(self, conf):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            page = conf["page"]
    
            first = common_utils.set_query_parameter(conf["id"], page=1)
            last = common_utils.set_query_parameter(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                conf["id"], page=page.paginator.num_pages
            )
    
            id = common_utils.set_query_parameter(conf["id"], page=page.number)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "id": id,
                "partOf": conf["id"],
    
                # XXX Stable release: remove the obsolete actor field
    
                "actor": conf["actor"].fid,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "totalItems": page.paginator.count,
                "type": "CollectionPage",
                "first": first,
                "last": last,
                "items": [
                    conf["item_serializer"](
                        i, context={"actor": conf["actor"], "include_ap_context": False}
    
                    ).data
                    for i in page.object_list
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                ],
    
            }
    
            if page.has_previous():
    
                d["prev"] = common_utils.set_query_parameter(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    conf["id"], page=page.previous_page_number()
                )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if page.has_next():
    
                d["next"] = common_utils.set_query_parameter(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    conf["id"], page=page.next_page_number()
                )
    
            d.update(get_additional_fields(conf))
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if self.context.get("include_ap_context", True):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                d["@context"] = jsonld.get_default_context()
    
    MUSIC_ENTITY_JSONLD_MAPPING = {
        "name": jsonld.first_val(contexts.AS.name),
        "published": jsonld.first_val(contexts.AS.published),
        "musicbrainzId": jsonld.first_val(contexts.FW.musicbrainzId),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        "attributedTo": jsonld.first_id(contexts.AS.attributedTo),
    
        "tags": jsonld.raw(contexts.AS.tag),
    
        "mediaType": jsonld.first_val(contexts.AS.mediaType),
        "content": jsonld.first_val(contexts.AS.content),
    
    def repr_tag(tag_name):
        return {"type": "Hashtag", "name": "#{}".format(tag_name)}
    
    
    
    def include_content(repr, content_obj):
        if not content_obj:
            return
    
        repr["content"] = common_utils.render_html(
            content_obj.text, content_obj.content_type
        )
        repr["mediaType"] = "text/html"
    
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def include_image(repr, attachment, field="image"):
    
        if attachment:
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            repr[field] = {
    
                "type": "Image",
    
                "url": attachment.download_url_original,
    
                "mediaType": attachment.mimetype or "image/jpeg",
            }
        else:
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            repr[field] = None
    
    class MusicEntitySerializer(jsonld.JsonLdSerializer):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        id = serializers.URLField(max_length=500)
        published = serializers.DateTimeField()
        musicbrainzId = serializers.UUIDField(allow_null=True, required=False)
        name = serializers.CharField(max_length=1000)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        attributedTo = serializers.URLField(max_length=500, allow_null=True, required=False)
        updateable_fields = []
    
        tags = serializers.ListField(
            child=TagSerializer(), min_length=0, required=False, allow_null=True
        )
    
        mediaType = serializers.ChoiceField(
            choices=common_models.CONTENT_TEXT_SUPPORTED_TYPES,
            default="text/html",
            required=False,
        )
        content = TruncatedCharField(
            truncate_length=common_models.CONTENT_TEXT_MAX_LENGTH,
            required=False,
            allow_null=True,
        )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
        def update(self, instance, validated_data):
    
            return self.update_or_create(validated_data)
    
        @transaction.atomic
        def update_or_create(self, validated_data):
            instance = self.instance or self.Meta.model(fid=validated_data["id"])
            creating = instance.pk is None
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            attributed_to_fid = validated_data.get("attributedTo")
            if attributed_to_fid:
                validated_data["attributedTo"] = actors.get_actor(attributed_to_fid)
    
            updated_fields = common_utils.get_updated_fields(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                self.updateable_fields, validated_data, instance
            )
    
            updated_fields = self.validate_updated_data(instance, updated_fields)
    
            if creating:
                instance, created = self.Meta.model.objects.get_or_create(
                    fid=validated_data["id"], defaults=updated_fields
                )
            else:
    
                music_tasks.update_library_entity(instance, updated_fields)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
            tags = [t["name"] for t in validated_data.get("tags", []) or []]
            tags_models.set_tags(instance, *tags)
    
            common_utils.attach_content(
                instance, "description", validated_data.get("description")
            )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return instance
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
        def get_tags_repr(self, instance):
    
            return tag_list(instance.tagged_items.all())
    
        def validate_updated_data(self, instance, validated_data):
    
            try:
                attachment_cover = validated_data.pop("attachment_cover")
            except KeyError:
                return validated_data
    
            if (
                instance.attachment_cover
    
                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.get("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,
            allow_empty_mimetype=True,
    
    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
    
    
        create = MusicEntitySerializer.update_or_create
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    class AlbumSerializer(MusicEntitySerializer):
        released = serializers.DateField(allow_null=True, required=False)
    
        artists = serializers.ListField(
            child=MultipleSerializer(allowed=[BasicActorSerializer, ArtistSerializer]),
            min_length=1,
        )
    
        # XXX: 1.0 rename to image
    
        cover = ImageSerializer(
    
            allowed_mimetypes=["image/*"],
            allow_null=True,
            required=False,
            allow_empty_mimetype=True,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        updateable_fields = [
            ("name", "title"),
    
            ("cover", "attachment_cover"),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            ("musicbrainzId", "mbid"),
            ("attributedTo", "attributed_to"),
            ("released", "release_date"),
    
            ("_artist", "artist"),
    
    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,
    
    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
            }
    
            if instance.artist.get_channel():
                d["artists"] = [
                    {
                        "type": instance.artist.channel.actor.type,
                        "id": instance.artist.channel.actor.fid,
                    }
                ]
            else:
                d["artists"] = [
                    ArtistSerializer(
                        instance.artist, context={"include_ap_context": False}
                    ).data
                ]
    
            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
    
        def validate(self, data):
            validated_data = super().validate(data)
            if not self.parent:
    
                artist_data = validated_data["artists"][0]
                if artist_data.get("type", "Artist") == "Artist":
                    validated_data["_artist"] = utils.retrieve_ap_object(
                        artist_data["id"],
                        actor=self.context.get("fetch_actor"),
                        queryset=music_models.Artist,
                        serializer_class=ArtistSerializer,
                    )
                else:
                    # we have an actor as an artist, so it's a channel
                    actor = actors.get_actor(artist_data["id"])
                    validated_data["_artist"] = actor.channel.artist
    
    
            return validated_data
    
        create = MusicEntitySerializer.update_or_create
    
    
    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,
            allow_empty_mimetype=True,
    
    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")
    
                library = utils.retrieve_ap_object(
                    v,
                    actor=self.context.get("fetch_actor"),
                    queryset=music_models.Library,
                    serializer_class=LibrarySerializer,
                )
            except Exception:
                raise serializers.ValidationError("Invalid library")
            if actor and library.actor != actor:
    
                raise serializers.ValidationError("Invalid library")
    
            return library
    
        def update(self, instance, validated_data):
            return self.create(validated_data)
    
        @transaction.atomic
    
            instance = self.instance or None
            if not self.instance:
                try:
                    instance = music_models.Upload.objects.get(fid=validated_data["id"])
                except music_models.Upload.DoesNotExist:
                    pass
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
            if instance:
                data = {
                    "mimetype": validated_data["url"]["mediaType"],
                    "source": validated_data["url"]["href"],
                    "creation_date": validated_data["published"],
                    "modification_date": validated_data.get("updated"),
                    "duration": validated_data["duration"],
                    "size": validated_data["size"],
                    "bitrate": validated_data["bitrate"],
                    "import_status": "finished",
                }
                return music_models.Upload.objects.update_or_create(
                    fid=validated_data["id"], defaults=data
                )[0]
            else:
                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"],
                    "modification_date": validated_data.get("updated"),
                    "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",
                }
                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 FlagSerializer(jsonld.JsonLdSerializer):
        type = serializers.ChoiceField(choices=[contexts.AS.Flag])
        id = serializers.URLField(max_length=500)
        object = serializers.URLField(max_length=500)
        content = serializers.CharField(required=False, allow_null=True, allow_blank=True)
        actor = serializers.URLField(max_length=500)
        type = serializers.ListField(
            child=TagSerializer(), min_length=0, required=False, allow_null=True
        )
    
        class Meta:
            jsonld_mapping = {
                "object": jsonld.first_id(contexts.AS.object),
                "content": jsonld.first_val(contexts.AS.content),
                "actor": jsonld.first_id(contexts.AS.actor),
                "type": jsonld.raw(contexts.AS.tag),
            }
    
        def validate_object(self, v):
            try:
                return utils.get_object_by_fid(v, local=True)
            except ObjectDoesNotExist:
                raise serializers.ValidationError(
                    "Unknown id {} for reported object".format(v)
                )
    
        def validate_type(self, tags):
            if tags:
                for tag in tags:
                    if tag["name"] in dict(moderation_models.REPORT_TYPES):
                        return tag["name"]
            return "other"
    
        def validate_actor(self, v):
            try:
                return models.Actor.objects.get(fid=v, domain=self.context["actor"].domain)
            except models.Actor.DoesNotExist:
                raise serializers.ValidationError("Invalid actor")
    
        def validate(self, data):
            validated_data = super().validate(data)
    
            return validated_data
    
        def create(self, validated_data):
            kwargs = {
                "target": validated_data["object"],
                "target_owner": moderation_serializers.get_target_owner(
                    validated_data["object"]
                ),
                "target_state": moderation_serializers.get_target_state(
                    validated_data["object"]
                ),
                "type": validated_data.get("type", "other"),
                "summary": validated_data.get("content"),
                "submitter": validated_data["actor"],
            }
    
            report, created = moderation_models.Report.objects.update_or_create(
                fid=validated_data["id"], defaults=kwargs,
            )
            moderation_signals.report_created.send(sender=None, report=report)
            return report
    
        def to_representation(self, instance):
            d = {
                "type": "Flag",
                "id": instance.get_federation_id(),
                "actor": actors.get_service_actor().fid,
                "object": [instance.target.fid],
                "content": instance.summary,
                "tag": [repr_tag(instance.type)],
            }
    
            if self.context.get("include_ap_context", self.parent is None):
                d["@context"] = jsonld.get_default_context()
            return d
    
    
    
    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(jsonld.JsonLdSerializer):
        id = serializers.URLField(max_length=500)
        type = serializers.ChoiceField(choices=[contexts.AS.Audio])
        url = LinkListSerializer(keep_mediatype=["audio/*"], min_length=1)
        name = TruncatedCharField(truncate_length=music_models.MAX_LENGTHS["TRACK_TITLE"])
        published = serializers.DateTimeField(required=False)
        duration = serializers.IntegerField(min_value=0, required=False)
        position = serializers.IntegerField(min_value=0, allow_null=True, required=False)
        disc = serializers.IntegerField(min_value=1, allow_null=True, required=False)
        album = serializers.URLField(max_length=500, required=False)
        license = serializers.URLField(allow_null=True, required=False)
        copyright = TruncatedCharField(
            truncate_length=music_models.MAX_LENGTHS["COPYRIGHT"],
            allow_null=True,
            required=False,
        )
        image = ImageSerializer(
            allowed_mimetypes=["image/*"],
            allow_null=True,
            required=False,
            allow_empty_mimetype=True,
        )
    
        mediaType = serializers.ChoiceField(
            choices=common_models.CONTENT_TEXT_SUPPORTED_TYPES,
            default="text/html",
            required=False,
        )
        content = TruncatedCharField(
            truncate_length=common_models.CONTENT_TEXT_MAX_LENGTH,
            required=False,
            allow_null=True,
        )
    
        tags = serializers.ListField(
            child=TagSerializer(), min_length=0, required=False, allow_null=True
        )
    
        class Meta:
            jsonld_mapping = {
                "name": jsonld.first_val(contexts.AS.name),
                "url": jsonld.raw(contexts.AS.url),
                "published": jsonld.first_val(contexts.AS.published),
                "mediaType": jsonld.first_val(contexts.AS.mediaType),
                "content": jsonld.first_val(contexts.AS.content),
                "duration": jsonld.first_val(contexts.AS.duration),
                "album": jsonld.first_id(contexts.FW.album),
                "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),
                "tags": jsonld.raw(contexts.AS.tag),
            }
    
        def validate_album(self, v):
            return utils.retrieve_ap_object(
                v,
                actor=actors.get_service_actor(),
                serializer_class=AlbumSerializer,
                queryset=music_models.Album.objects.filter(
                    artist__channel=self.context["channel"]
                ),
            )
    
        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
    
    
        def to_representation(self, upload):
            data = {
                "id": upload.fid,
                "type": "Audio",
    
                "name": upload.track.title,
    
                "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",
    
                        "mediaType": "text/html",
                        "href": utils.full_url(upload.track.get_absolute_url()),
    
                    },
                    {
                        "type": "Link",
    
                        "mediaType": upload.mimetype,
                        "href": utils.full_url(upload.listen_url_no_download),
    
            if upload.track.album:
                data["album"] = upload.track.album.fid
            if upload.track.local_license:
                data["license"] = upload.track.local_license["identifiers"][0]
    
            include_if_not_none(data, upload.duration, "duration")
            include_if_not_none(data, upload.track.position, "position")
            include_if_not_none(data, upload.track.disc_number, "disc")
            include_if_not_none(data, upload.track.copyright, "copyright")
            include_if_not_none(data["url"][1], upload.bitrate, "bitrate")
            include_if_not_none(data["url"][1], upload.size, "size")
    
            include_content(data, upload.track.description)
    
            include_image(data, upload.track.attachment_cover)
    
            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
    
    
        def update(self, instance, validated_data):
            return self.update_or_create(validated_data)
    
        @transaction.atomic
        def update_or_create(self, validated_data):
            channel = self.context["channel"]
            now = timezone.now()
            track_defaults = {
                "fid": validated_data["id"],
                "artist": channel.artist,
                "position": validated_data.get("position", 1),
                "disc_number": validated_data.get("disc", 1),
                "title": validated_data["name"],
                "copyright": validated_data.get("copyright"),
                "attributed_to": channel.attributed_to,
                "album": validated_data.get("album"),
                "creation_date": validated_data.get("published", now),
            }
            if validated_data.get("license"):
                track_defaults["license"] = licenses.match(validated_data["license"])
    
            track, created = music_models.Track.objects.update_or_create(
                artist__channel=channel, fid=validated_data["id"], defaults=track_defaults
            )
    
            if "image" in validated_data:
                new_value = self.validated_data["image"]
                common_utils.attach_file(
                    track,
                    "attachment_cover",
                    {"url": new_value["url"], "mimetype": new_value.get("mediaType")}
                    if new_value
                    else None,
                )
    
            common_utils.attach_content(
                track, "description", validated_data.get("description")
            )
    
            tags = [t["name"] for t in validated_data.get("tags", []) or []]
            tags_models.set_tags(track, *tags)
    
            upload_defaults = {
                "fid": validated_data["id"],
                "track": track,
                "library": channel.library,
                "creation_date": validated_data.get("published", now),
                "duration": validated_data.get("duration"),
                "bitrate": validated_data["url"][0].get("bitrate"),
                "size": validated_data["url"][0].get("size"),
                "mimetype": validated_data["url"][0]["mediaType"],
                "source": validated_data["url"][0]["href"],
                "import_status": "finished",
            }
            upload, created = music_models.Upload.objects.update_or_create(
                fid=validated_data["id"], defaults=upload_defaults
            )
            return upload
    
        def create(self, validated_data):
            return self.update_or_create(validated_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,
            }