Django Rest Framework POST更新如果存在或创建

Ali*_*ali 16 python django django-rest-framework

我是DRF的新手.我阅读了API文档,也许它没有注意到,但我找不到一个方便的方法来做到这一点.

我有一个Answer对象与问题有一对一的关系.

在前端我曾经使用POST方法创建一个发送到api/answers的答案,并将PUT方法更新发送到例如api/answers/24

但我想在服务器端处理它.我只会向api/answers发送POST方法,如果对象存在,DRF将根据answer_id或question_id(因为它是一对一)进行检查.如果是,它将更新现有的,如果不是,它将创建一个新的答案.

我应该在哪里实现它,我无法弄明白.在序列化程序或ViewSet或其他内容中覆盖create?

我的模型,序列化器和视图如下:

class Answer(models.Model):
    question = models.OneToOneField(
        Question, on_delete=models.CASCADE, related_name="answer"
    )
    answer = models.CharField(
        max_length=1, choices=ANSWER_CHOICES, null=True, blank=True
    )


class AnswerSerializer(serializers.ModelSerializer):
    question = serializers.PrimaryKeyRelatedField(
        many=False, queryset=Question.objects.all()
    )

    class Meta:
        model = Answer
        fields = ("id", "answer", "question")


class AnswerViewSet(ModelViewSet):
    queryset = Answer.objects.all()
    serializer_class = AnswerSerializer
    filter_fields = ("question", "answer")
Run Code Online (Sandbox Code Playgroud)

小智 26

很遗憾,您提供和接受的答案无法回答您的原始问题,因为它不会更新模型.然而,这可以通过另一种便利方法轻松实现:update-or-create

def create(self, validated_data):
    answer, created = Answer.objects.update_or_create(
        question=validated_data.get('question', None),
        defaults={'answer': validated_data.get('answer', None)})
    return answer
Run Code Online (Sandbox Code Playgroud)

这应该Answer在数据库中创建一个对象,如果一个对象question=validated_data['question']不存在并且答案取自validated_data['answer'].如果它已经存在,django会将其answer属性设置为validated_data['answer'].

正如Nirri的回答所指出的,这个函数应该驻留在序列化器中.如果您使用通用ListCreateView,它将在发送发布请求后调用create函数并生成相应的响应.

  • 唯一的问题是它在更新时返回“HTTP_201_CREATED”:( (3认同)
  • 我认为这正走向错误的方向。Update_or_create 应该在视图级别而不是序列化器级别完成。 (3认同)

Dam*_*nic 13

@Nirri发布的答案对我有帮助,但我发现使用Django QuerySet API快捷方式更优雅的解决方案:

def create(self, validated_data):
    answer, created = Answer.objects.get_or_create(
        question=validated_data.get('question', None),
        defaults={'answer': validated_data.get('answer', None)})

    return answer
Run Code Online (Sandbox Code Playgroud)

它不完全一样的东西-如果AnswerQuestion不存在的,它会被创建,否则-返回是question现场查询.

但是,此快捷方式不会更新对象.QuerySet API有另一种update操作方法,它被调用update_or_create并在线程中的其他答案中发布.


xtr*_*nch 7

我会使用序列化程序的 create 方法。

在其中,您可以检查问题(使用您在“问题”主键相关字段中提供的 ID)是否已经有答案,如果有,则获取对象并更新它,否则创建一个新的。

因此,第一个选项将类似于:

class AnswerSerializer(serializers.ModelSerializer):
    question = serializers.PrimaryKeyRelatedField(many=False, queryset=Question.objects.all())

    class Meta:
        model = Answer
        fields = (
            'id',
            'answer',
            'question',
        )

    def create(self, validated_data):
        question_id = validated_data.get('question', None)
        if question_id is not None:
            question = Question.objects.filter(id=question_id).first()
            if question is not None:
                answer = question.answer
                if answer is not None:
                   # update your answer
                   return answer

        answer = Answer.objects.create(**validated_data)
        return answer
Run Code Online (Sandbox Code Playgroud)

第二种选择是检查具有答案 ID 的答案是否存在。

除非您使用某种解决方法并将它们手动定义为 read_only = false 字段,否则应答 ID 不会显示在已验证的 post 请求数据中:

id = serializers.IntegerField(read_only=False)
Run Code Online (Sandbox Code Playgroud)

但是您应该重新考虑这一点, PUT 方法和 POST 方法作为单独的实体存在是有充分理由的,您应该将前端的请求分开。

  • 非常感谢。第一种选择就足够了。我想将它们分开的原因是:如果有答案,我会在前面调用 UpdateAnswer else CreateAnswer - 它在 React 中。所以让我们想象一下你和我在不同的浏览器中打开了网站。有一个未回答的问题,您单击“是”按钮并发送数据,以便服务器创建对象。我没有刷新浏览器并单击“是”或“否”,因此它将发送另一个失败的创建请求。所以我必须在服务器端处理它。这就是原因。 (3认同)

Fel*_*oni 5

一个更通用的答案,我认为这应该在视图集中而不是序列化器中,因为序列化器只需要序列化,仅此而已。

这模拟的条件update从传递IDrequest.datakwargs,所以如果实例不存在,则UpdateModelMixin.update()提出了一个Http404由该抓except块,并呼吁例外create()

from rest_framework.mixins import UpdateModelMixin
from django.http import Http404


class AnswerViewSet(UpdateModelMixin, ModelViewSet):
    queryset = Answer.objects.all()
    serializer_class = AnswerSerializer
    filter_fields = ("question", "answer")

    update_data_pk_field = 'id'

    def create(self, request, *args, **kwargs):
        kwarg_field: str = self.lookup_url_kwarg or self.lookup_field
        self.kwargs[kwarg_field] = request.data[self.update_data_pk_field]

        try:
            return self.update(request, *args, **kwargs)
        except Http404:
            return super().create(request, *args, **kwargs)
Run Code Online (Sandbox Code Playgroud)