【问题标题】:Implementing Tags using Django rest framework使用 Django rest 框架实现标签
【发布时间】:2018-10-16 14:27:09
【问题描述】:

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

我正在尝试实现一个非常简单/常见的事情,为帖子添加标签。但显然它不是小菜一碟。

所以我有一个帖子模型和一个标签模型(可能有很多关系)。我希望用户能够更新和创建帖子。在创建或更新帖子时,他应该能够更新帖子的标签。当使用新标签标记帖子时,如果该标签不存在,则应创建该标签。我还希望用户能够将标签指定为请求中的字符串列表。

示例请求

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

models.py

class Tags(models.Model):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    name = models.CharField(max_length=50, unique=True)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="created_tags")

class Posts(models.Model):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    name = models.CharField(max_length=50)
    caption = models.TextField(max_length=1000)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    tags = models.ManyToManyField('Tags', related_name='posts')

序列化器.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 相关字段。

当使用 SlugRealtedfield 时,它会抛出标签对象存在的验证错误。我计划是否可以取消此检查,我可以在 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

【问题讨论】:

    标签: python django django-rest-framework


    【解决方案1】:

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

    如您所述,如果标签不存在,则验证检查将失败。 我设法通过继承 SlugRelatedField 并覆盖“to_internal_value”方法来克服这个问题。在原始实现中,此方法尝试从查询集中获取对象,如果对象不存在,则验证失败。所以我没有调用“get”方法,而是调用“get_or_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')
    

    【讨论】:

      【解决方案2】:

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

      SlugRelatedField 用于只读,ListField 用于只写,因此您可以拥有字符串列表而不是字典。

      要获取当前用户,您可以在序列化函数中使用 self.context['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,这样标签关系就可以删除了。不过,您只需要始终发送所有标签。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-05-02
        • 2011-01-30
        • 2016-03-23
        • 1970-01-01
        • 2021-10-15
        • 1970-01-01
        • 2015-03-25
        • 2021-11-02
        相关资源
        最近更新 更多