def test_channel_serializer_external_representation(factories, to_api_date): content = factories["common.Content"]() channel = factories["audio.Channel"](artist__description=content, external=True) expected = { "artist": music_serializers.serialize_artist_simple(channel.artist), "uuid": str(channel.uuid), "creation_date": to_api_date(channel.creation_date), "actor": None, "attributed_to": federation_serializers.APIActorSerializer(channel.attributed_to).data, "metadata": {}, "rss_url": channel.get_rss_url(), "url": channel.actor.url, } expected["artist"]["description"] = common_serializers.ContentSerializer( content).data assert serializers.ChannelSerializer(channel).data == expected
def test_can_fetch_data_from_api(api_client, factories): url = reverse("api:v1:users:users-me") response = api_client.get(url) # login required assert response.status_code == 401 user = factories["users.User"](permission_library=True, with_actor=True) summary = {"content_type": "text/plain", "text": "Hello"} summary_obj = common_utils.attach_content(user.actor, "summary_obj", summary) avatar = factories["common.Attachment"]() user.actor.attachment_icon = avatar user.actor.save() api_client.login(username=user.username, password="******") response = api_client.get(url) assert response.status_code == 200 assert response.data["username"] == user.username assert response.data["is_staff"] == user.is_staff assert response.data["is_superuser"] == user.is_superuser assert response.data["email"] == user.email assert response.data["name"] == user.name assert response.data["permissions"] == user.get_permissions() assert ( response.data["avatar"] == common_serializers.AttachmentSerializer(avatar).data ) assert ( response.data["summary"] == common_serializers.ContentSerializer(summary_obj).data )
def serialize_artist_simple(artist): data = { "id": artist.id, "fid": artist.fid, "mbid": str(artist.mbid), "name": artist.name, "creation_date": DATETIME_FIELD.to_representation(artist.creation_date), "modification_date": DATETIME_FIELD.to_representation(artist.modification_date), "is_local": artist.is_local, "content_category": artist.content_category, } if "description" in artist._state.fields_cache: data["description"] = ( common_serializers.ContentSerializer(artist.description).data if artist.description else None ) if "attachment_cover" in artist._state.fields_cache: data["cover"] = ( cover_field.to_representation(artist.attachment_cover) if artist.attachment_cover else None ) if getattr(artist, "_tracks_count", None) is not None: data["tracks_count"] = artist._tracks_count if getattr(artist, "_prefetched_tagged_items", None) is not None: data["tags"] = [ti.tag.name for ti in artist._prefetched_tagged_items] return data
def test_detail_serializers_with_description_description( factory_name, serializer_class, factories): content = factories["common.Content"]() obj = factories[factory_name](description=content) expected = common_serializers.ContentSerializer(content).data serializer = serializer_class(obj, context={"description": True}) assert serializer.data["description"] == expected
def test_api_full_actor_serializer(factories, to_api_date): summary = factories["common.Content"]() icon = factories["common.Attachment"]() user = factories["users.User"]() actor = user.create_actor(summary_obj=summary, attachment_icon=icon) expected = { "fid": actor.fid, "url": actor.url, "creation_date": to_api_date(actor.creation_date), "last_fetch_date": to_api_date(actor.last_fetch_date), "user": users_serializers.UserBasicSerializer(user).data, "is_channel": False, "domain": actor.domain_id, "type": actor.type, "manually_approves_followers": actor.manually_approves_followers, "full_username": actor.full_username, "name": actor.name, "preferred_username": actor.preferred_username, "is_local": actor.is_local, "summary": common_serializers.ContentSerializer(summary).data, "icon": common_serializers.AttachmentSerializer(icon).data, } serializer = api_serializers.FullActorSerializer(actor) assert serializer.data == expected
def to_representation(self, obj): repr = super().to_representation(obj) if self.context.get("description", False): description = obj.description repr["description"] = ( common_serializers.ContentSerializer(description).data if description else None ) return repr
def test_content_serializer(factories): content = factories["common.Content"]() expected = { "text": content.text, "content_type": content.content_type, "html": utils.render_html(content.text, content.content_type), } serializer = serializers.ContentSerializer(content) assert serializer.data == expected
class CustomFormSerializer(serializers.Serializer): help_text = common_serializers.ContentSerializer(required=False, allow_null=True) fields = serializers.ListField(child=CustomFieldSerializer(), min_length=0, max_length=10, required=False) def validate_help_text(self, v): if not v: return v["html"] = common_utils.render_html(v["text"], content_type=v["content_type"], permissive=True) return v
class DescriptionMutation(mutations.UpdateMutationSerializer): description = common_serializers.ContentSerializer() def get_previous_state_handlers(self): handlers = super().get_previous_state_handlers() handlers["description"] = (lambda obj: common_serializers. ContentSerializer(obj.description).data if obj.description_id else None) return handlers def update(self, instance, validated_data): description = validated_data.pop("description", NOOP) r = super().update(instance, validated_data) if description != NOOP: common_utils.attach_content(instance, "description", description) return r
class UserWriteSerializer(serializers.ModelSerializer): summary = common_serializers.ContentSerializer(required=False, allow_null=True) avatar = common_serializers.RelatedField( "uuid", queryset=common_models.Attachment.objects.all().local().attached( False), serializer=None, queryset_filter=lambda qs, context: qs.filter(actor=context["request"]. user.actor), write_only=True, ) class Meta: model = models.User fields = [ "name", "privacy_level", "avatar", "instance_support_message_display_date", "funkwhale_support_message_display_date", "summary", ] def update(self, obj, validated_data): if not obj.actor: obj.create_actor() summary = validated_data.pop("summary", NOOP) avatar = validated_data.pop("avatar", NOOP) obj = super().update(obj, validated_data) if summary != NOOP: common_utils.attach_content(obj.actor, "summary_obj", summary) if avatar != NOOP: obj.actor.attachment_icon = avatar obj.actor.save(update_fields=["attachment_icon"]) return obj def to_representation(self, instance): r = super().to_representation(instance) r["avatar"] = common_serializers.AttachmentSerializer( instance.get_avatar()).data return r
class AlbumCreateSerializer(serializers.Serializer): title = serializers.CharField(required=True, max_length=255) cover = COVER_WRITE_FIELD release_date = serializers.DateField(required=False, allow_null=True) tags = tags_serializers.TagsListField(required=False) description = common_serializers.ContentSerializer(allow_null=True, required=False) artist = common_serializers.RelatedField( "id", queryset=models.Artist.objects.exclude(channel__isnull=True), required=True, serializer=None, filters=lambda context: {"attributed_to": context["user"].actor}, ) def validate(self, validated_data): duplicates = validated_data["artist"].albums.filter( title__iexact=validated_data["title"] ) if duplicates.exists(): raise serializers.ValidationError("An album with this title already exist") return super().validate(validated_data) def to_representation(self, obj): obj.artist.attachment_cover return AlbumSerializer(obj, context=self.context).data def create(self, validated_data): instance = models.Album.objects.create( attributed_to=self.context["user"].actor, artist=validated_data["artist"], release_date=validated_data.get("release_date"), title=validated_data["title"], attachment_cover=validated_data.get("cover"), ) common_utils.attach_content( instance, "description", validated_data.get("description") ) tag_models.set_tags(instance, *(validated_data.get("tags", []) or [])) instance.artist.get_channel() return instance
def test_album_mutation_description(factory_name, factories, mocker): mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") content = factories["common.Content"]() obj = factories[factory_name](description=content) mutation = factories["common.Mutation"]( type="update", target=obj, payload={ "description": { "content_type": "text/plain", "text": "hello there" } }, ) mutation.apply() obj.refresh_from_db() assert obj.description.content_type == "text/plain" assert obj.description.text == "hello there" assert (mutation.previous_state["description"] == common_serializers.ContentSerializer(content).data)
class FullActorSerializer(serializers.Serializer): fid = serializers.URLField() url = serializers.URLField() domain = serializers.CharField(source="domain_id") creation_date = serializers.DateTimeField() last_fetch_date = serializers.DateTimeField() name = serializers.CharField() preferred_username = serializers.CharField() full_username = serializers.CharField() type = serializers.CharField() is_local = serializers.BooleanField() is_channel = serializers.SerializerMethodField() manually_approves_followers = serializers.BooleanField() user = users_serializers.UserBasicSerializer() summary = common_serializers.ContentSerializer(source="summary_obj") icon = common_serializers.AttachmentSerializer(source="attachment_icon") def get_is_channel(self, o): try: return bool(o.channel) except ObjectDoesNotExist: return False
class ChannelCreateSerializer(serializers.Serializer): name = serializers.CharField(max_length=music_models.MAX_LENGTHS["ARTIST_NAME"]) username = serializers.CharField( max_length=music_models.MAX_LENGTHS["ARTIST_NAME"], validators=[users_serializers.ASCIIUsernameValidator()], ) description = common_serializers.ContentSerializer(allow_null=True) tags = tags_serializers.TagsListField() content_category = serializers.ChoiceField( choices=music_models.ARTIST_CONTENT_CATEGORY_CHOICES ) metadata = serializers.DictField(required=False) cover = music_serializers.COVER_WRITE_FIELD def validate(self, validated_data): existing_channels = self.context["actor"].owned_channels.count() if existing_channels >= preferences.get("audio__max_channels"): raise serializers.ValidationError( "You have reached the maximum amount of allowed channels" ) validated_data = super().validate(validated_data) metadata = validated_data.pop("metadata", {}) if validated_data["content_category"] == "podcast": metadata_serializer = ChannelMetadataSerializer(data=metadata) metadata_serializer.is_valid(raise_exception=True) metadata = metadata_serializer.validated_data validated_data["metadata"] = metadata return validated_data def validate_username(self, value): if value.lower() in [n.lower() for n in settings.ACCOUNT_USERNAME_BLACKLIST]: raise serializers.ValidationError("This username is already taken") matching = federation_models.Actor.objects.local().filter( preferred_username__iexact=value ) if matching.exists(): raise serializers.ValidationError("This username is already taken") return value @transaction.atomic def create(self, validated_data): from . import views cover = validated_data.pop("cover", None) description = validated_data.get("description") artist = music_models.Artist.objects.create( attributed_to=validated_data["attributed_to"], name=validated_data["name"], content_category=validated_data["content_category"], attachment_cover=cover, ) common_utils.attach_content(artist, "description", description) if validated_data.get("tags", []): tags_models.set_tags(artist, *validated_data["tags"]) channel = models.Channel( artist=artist, attributed_to=validated_data["attributed_to"], metadata=validated_data["metadata"], ) channel.actor = models.generate_actor( validated_data["username"], name=validated_data["name"], ) channel.library = music_models.Library.objects.create( name=channel.actor.preferred_username, privacy_level="everyone", actor=validated_data["attributed_to"], ) channel.save() channel = views.ChannelViewSet.queryset.get(pk=channel.pk) return channel def to_representation(self, obj): return ChannelSerializer(obj, context=self.context).data
class ChannelUpdateSerializer(serializers.Serializer): name = serializers.CharField(max_length=music_models.MAX_LENGTHS["ARTIST_NAME"]) description = common_serializers.ContentSerializer(allow_null=True) tags = tags_serializers.TagsListField() content_category = serializers.ChoiceField( choices=music_models.ARTIST_CONTENT_CATEGORY_CHOICES ) metadata = serializers.DictField(required=False) cover = music_serializers.COVER_WRITE_FIELD def validate(self, validated_data): validated_data = super().validate(validated_data) require_metadata_validation = False new_content_category = validated_data.get("content_category") metadata = validated_data.pop("metadata", NOOP) if ( new_content_category == "podcast" and self.instance.artist.content_category != "postcast" ): # updating channel, setting as podcast require_metadata_validation = True elif self.instance.artist.content_category == "postcast" and metadata != NOOP: # channel is podcast, and metadata was updated require_metadata_validation = True else: metadata = self.instance.metadata if require_metadata_validation: metadata_serializer = ChannelMetadataSerializer(data=metadata) metadata_serializer.is_valid(raise_exception=True) metadata = metadata_serializer.validated_data validated_data["metadata"] = metadata return validated_data @transaction.atomic def update(self, obj, validated_data): if validated_data.get("tags") is not None: tags_models.set_tags(obj.artist, *validated_data["tags"]) actor_update_fields = [] artist_update_fields = [] obj.metadata = validated_data["metadata"] obj.save(update_fields=["metadata"]) if "description" in validated_data: common_utils.attach_content( obj.artist, "description", validated_data["description"] ) if "name" in validated_data: actor_update_fields.append(("name", validated_data["name"])) artist_update_fields.append(("name", validated_data["name"])) if "content_category" in validated_data: artist_update_fields.append( ("content_category", validated_data["content_category"]) ) if "cover" in validated_data: artist_update_fields.append(("attachment_cover", validated_data["cover"])) if actor_update_fields: for field, value in actor_update_fields: setattr(obj.actor, field, value) obj.actor.save(update_fields=[f for f, _ in actor_update_fields]) if artist_update_fields: for field, value in artist_update_fields: setattr(obj.artist, field, value) obj.artist.save(update_fields=[f for f, _ in artist_update_fields]) return obj def to_representation(self, obj): return ChannelSerializer(obj, context=self.context).data
def get_summary(self, o): if not o.actor or not o.actor.summary_obj: return return common_serializers.ContentSerializer(o.actor.summary_obj).data