使用Django rest框架实现标签

2024-04-19 23:45:01 发布

您现在位置:Python中文网/ 问答频道 /正文

TDLR:在django-rest框架中实现标记的最佳方法是什么。其中标记有一个created_by字段,该字段是当前经过身份验证的用户。在

我试图实现一个非常简单/普通的事情,添加标签到帖子。但显然这不是小菜一碟。在

所以我有一个posts模型和一个tags模型(may to many relationship)。我希望用户能够更新和创建帖子。当创建或更新帖子时,他应该能够更新帖子的标签。当一篇文章用一个新的标签来标记时,如果这个标签已经存在,就应该创建它。我还希望用户能够在请求中将标记指定为字符串列表。在

示例请求

{
    "name": "testpost1",
    "caption": "test caption",
    "tags": ["tag1", "tag2"],
},

在模型.py在

^{pr2}$

在序列化程序.py在

class TagsSerializerMini(serializers.ModelSerializer):
    created_by = serializers.PrimaryKeyRelatedField(default=serializers.CurrentUserDefault(), queryset=User.objects.all())

    class Meta:
        model = Tags
        fields = ('name', 'created_by')
        extra_kwargs = {
            'created_by': {'write_only': True},
            'name': {'validators': []},
        }

    def create(self, validated_data):
        tag, created = Tags.objects.get_or_create(**validated_data)
        if not created:
            raise exceptions.ValidationError(validated_data['name']+" already exists.")
        return tag

    def to_representation(self, instance):
        ret = super(TagsSerializerMini, self).to_representation(instance)
        data = dict()
        data['name'] = ret['name']
        return data

我试过两种方法。使用嵌套序列化程序和slug相关字段。在

当使用slugreatedfield时,它会抛出一个验证错误,即标记对象dosent存在。我在计划如果我可以取消这个检查,我可以在create()之前创建所有标记并调用super create。但我无法绕过验证检查。我也不知道如何将当前用户传递给slugrelatedfield。在

经过一番搜索,我计划使用嵌套序列化程序。但是我必须将标记指定为dict[{"name":"tag1"}]。我还必须定义自定义创建和更新。我可以让创建工作,但不能更新。在

class PostsSerializer(QueryFieldsMixin, WritableNestedModelSerializer):
    created_by = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())

    class Meta:
        model = Posts
        fields = ('id', 'name', 'caption', 'tags', 'created_by')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['tags'] = TagsSerializerMini(many=True, required=False, context=self.context)

    def create(self, validated_data):
        tags_data = validated_data.pop('tags', [])
        post = Posts.objects.create(**validated_data)
        for tag in tags_data:
            t, _ = Tags.objects.get_or_create(name=tag["name"])
            post.tags.add(t)
        return post

Tags: 用户name标记selfdatabyobjectscreate
2条回答

在我看来,使用SlugRelatedField而不是嵌套序列化程序更为优雅,因为这样您将拥有一个标记数组(以及响应中的标记名数组),而不是字典数组[{“name”:“tag name”}]

正如您所提到的,如果标记不存在,验证检查将失败。 我通过将SlugRelatedField子类化并重写“to_internal_value”方法来克服这个问题。在最初的实现中,这个方法尝试从queryset中获取一个对象,如果对象不存在,则验证失败。因此,我没有调用“get”方法,而是调用“get”或“u create”:

class CustomSlugRelatedField(serializers.SlugRelatedField):
    def to_internal_value(self, data):
        try:
            obj, created = self.get_queryset().get_or_create(**{self.slug_field: data})
            return obj
        except (TypeError, ValueError):
            self.fail('invalid')

如果您可以接受使用两个字段,下面是我的解决方案:

将SlugRelatedField用于只读,将列表字段用于只写,这样就可以拥有字符串列表而不是字典。在

要获取当前用户,可以使用自我语境['request'].user在序列化程序函数中。在

下面是示例代码(未测试):

class PostsSerializer(serializers.ModelSerializer):
    tags = serializers.SlugRelatedField(many=True, slug_field='name', read_only=True)
    update_tags = serializers.ListField(
        child=serializers.CharField(max_length=30), write_only=True)

    class Meta:
        model = Posts
        exclude = ()

    def create(self, validated_data):
        tag_names = validated_data.pop('update_tags')
        instance = super().create(validated_data)
        user = self.context['request'].user
        tags = []
        for name in tag_names:
            tag, created = Tags.objects.get_or_create(name=name, defaults={'created_by': user})
            tags.append(tag)
        instance.tags.set(tags)
        return instance

    def update(self, instance, validated_data):
        tag_names = validated_data.pop('update_tags')
        instance = super().update(instance, validated_data)
        user = self.context['request'].user
        tags = []
        for name in tag_names:
            tag, created = Tags.objects.get_or_create(name=name, defaults={'created_by': user})
            tags.append(tag)
        instance.tags.set(tags)
        return instance

注:我使用instance.tags.set而不是instance.tags.add,以便可以删除标记关系。尽管你总是需要发送标签。在

相关问题 更多 >