Python Django Rest Framework POST 更新(如果存在或创建)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/37833307/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Django Rest Framework POST Update if existing or create
提问by Ali Ankarali
I am new to DRF. I read the API docs, maybe it is oblivious but I couldn't find a handy way to do it.
我是 DRF 的新手。我阅读了 API 文档,也许它被遗忘了,但我找不到一种方便的方法来做到这一点。
I have an Answer object which has one-to-one relationship with a question.
我有一个与问题具有一对一关系的 Answer 对象。
On the front side I used to use POST method to create an answer sent to api/answers, and PUT method to update sent to e.g. api/answers/24
在前端,我曾经使用 POST 方法创建发送到 api/answers 的答案,并使用 PUT 方法更新发送到例如 api/answers/24
But I want to handle it on the server side. I will only send a POST method to api/answers and DRF will check based on answer_id or question_id (since it is one to one) if the object exists. If it does, it will update the existing one, if it doesn't it will create a new answer.
但我想在服务器端处理它。我只会向 api/answers 发送一个 POST 方法,如果对象存在,DRF 将根据 answer_id 或 question_id (因为它是一对一的)进行检查。如果是,它将更新现有答案,如果不是,它将创建一个新答案。
Where I should implement it, I couldn't figure out. Overriding create in serializer or in ViewSet or something else?
我应该在哪里实施它,我无法弄清楚。覆盖序列化程序或 ViewSet 中的创建或其他内容?
My model, serializer and view are as these:
我的模型、序列化程序和视图如下:
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")
回答by K Moe
Unfortunately your provided and accepted answer does not answer your original question, since it does not update the model. This however is easily achieved by another convenience method: 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
This should create an Answer
object in the database if one with question=validated_data['question']
does not exist with the answer taken from validated_data['answer']
. If it already exists, django will set its answer attribute to validated_data['answer']
.
这应该Answer
在数据库中创建一个对象,如果question=validated_data['question']
从validated_data['answer']
. 如果它已经存在,django 会将它的 answer 属性设置为validated_data['answer']
。
As noted by the answer of Nirri, this function should reside inside the serializer. If you use the generic ListCreateViewit will call the create function once a post request is sent and generate the corresponding response.
正如 Nirri 的回答所指出的,这个函数应该驻留在序列化器中。如果您使用通用ListCreateView,它会在发送 post 请求后调用 create 函数并生成相应的响应。
回答by Damaged Organic
Answer posted by @Nirrihelped me as well, but I've found more elegant solution using Django QuerySet APIshortcut:
@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
It does exactly the same thing - if Answer
to that Question
does not exists, it will be created, else - returned as is by question
field lookup.
它不完全一样的东西-如果Answer
到Question
不存在的,它会被创建,否则-返回是question
现场查询。
This shortcut, however, won't update the object. QuerySet APIhas another method for an update
operation, which is called update_or_create
and posted in other answer down the thread.
但是,此快捷方式不会更新对象。QuerySet API有另一种update
操作方法,该方法在线程的其他答案中被调用update_or_create
和发布。
回答by xtrinch
I would use the serializers' create method.
我会使用序列化程序的 create 方法。
In it you could check if the question (with the ID of it you provide in the 'question' primary key related field) already has an answer, and if it does, fetch the object and update it, otherwise create a new one.
在其中,您可以检查问题(使用您在“问题”主键相关字段中提供的 ID)是否已经有答案,如果有,则获取对象并更新它,否则创建一个新的。
So the first option would go something like:
因此,第一个选项将类似于:
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
Second option would be to check if the answer with the answer id exists.
第二种选择是检查具有答案 ID 的答案是否存在。
Answer ID's wouldn't show up in the validated data of post requests, unless you used a sort of workaround and manually defined them as read_only = false fields:
除非您使用某种解决方法并将它们手动定义为 read_only = false 字段,否则应答 ID 不会显示在已验证的 post 请求数据中:
id = serializers.IntegerField(read_only=False)
But you should however rethink this through, There's a good reason the PUT method and the POST methods exist as separate entities, and you should separate the requests on the frontend.
但是您应该重新考虑这一点, PUT 方法和 POST 方法作为单独的实体存在是有充分理由的,您应该将前端的请求分开。
回答by S?awomir Lenart
Also:
还:
try:
serializer.instance = YourModel.objects.get(...)
except YourModel.DoesNotExist:
pass
if serializer.is_valid():
serializer.save() # will INSERT or UPDATE your validated data
回答by John Pang
I tried the serializer solution but it seems exception raised before hitting the serializer function create(self, validated_data)
. That's because I'm using ModelViewSet
(which in turn using class CreatedModelMixin
). Further study reveals that exception raised here:
我尝试了序列化程序解决方案,但在点击序列化程序功能之前似乎引发了异常create(self, validated_data)
。那是因为我正在使用ModelViewSet
(反过来使用class CreatedModelMixin
)。进一步的研究表明这里提出了例外:
rest_framework/mixins.py
class CreateModelMixin(object):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) <== Here
Since I want to keep all features provided by framework, so I prefer capturing the exceptions and route over to update:
因为我想保留框架提供的所有功能,所以我更喜欢捕获异常并路由到更新:
from rest_framework.exceptions import ValidationError
class MyViewSet(viewsets.ModelViewSet)
def create(self, request, *args, **kwargs):
pk_field = 'uuid'
try:
response = super().create(request, args, kwargs)
except ValidationError as e:
codes = e.get_codes()
# Check if error due to item exists
if pk_field in codes and codes[pk_field][0] == 'unique':
# Feed the lookup field otherwise update() will failed
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
self.kwargs[lookup_url_kwarg] = request.data[pk_field]
return super().update(request, *args, **kwargs)
else:
raise e
return response
My app can always call POST /api/my_model/
with parameters (here, uuid = primary key).
我的应用程序总是可以调用POST /api/my_model/
参数(这里,uuid = 主键)。
However, would it be better if we handle this in update
function?
但是,如果我们在update
函数中处理它会更好吗?
def update(self, request, *args, **kwargs):
try:
response = super().update(request, *args, **kwargs)
except Http404:
mutable = request.data._mutable
request.data._mutable = True
request.data["uuid"] = kwargs["pk"]
request.data._mutable = mutable
return super().create(request, *args, **kwargs)
return response
回答by conmak
A better and more generalized way to apply this would be to update the ModelSerializer object with a potential instance if it exists. This allows DRF to follow standard protocols and can be abstracted across models easily.
一种更好、更通用的应用方法是使用潜在实例更新 ModelSerializer 对象(如果存在)。这允许 DRF 遵循标准协议,并且可以轻松地跨模型抽象。
To keep things generic, start by making an UpdateOrCreate class to be inherited alongside the modelSerializer on instantiation. In this, add the def update_or_create_helper
.
为了保持通用性,首先创建一个 UpdateOrCreate 类,以便在实例化时与 modelSerializer 一起继承。在此,添加 def update_or_create_helper
。
Then inherit the UpdateOrCreate
class for each Serializer you want the functionality with and add a simple is_valid
def specific to that model.
然后UpdateOrCreate
为您想要功能的每个序列化程序继承类,并添加一个is_valid
特定于该模型的简单定义。
serializers.py
序列化程序.py
class UpdateOrCreate:
def update_or_create_helper(self, obj_model, pk):
# Check to see if data has been given to the serializer
if hasattr(self, 'initial_data'):
# Pull the object from the db
obj = obj_model.objects.filter(pk=self.initial_data[pk])
# Check if one and only one object exists with matching criteria
if len(obj)==1:
# If you want to allow for partial updates
self.partial = True
# Add the current instance to the object
self.instance = obj[0]
# Continue normally
return super().is_valid()
...
# Instantiate the model with your standard ModelSerializer
# Inherit the UpdateOrCreate class
class MyModelSerializer(serializers.ModelSerializer, UpdateOrCreate):
class Meta:
model = MyModel
fields = ['pk', 'other_fields']
# Extend is_valid to include the newly created update_or_create_helper
def is_valid(self):
return self.update_or_create_helper(obj_model=MyModel, pk='pk')
回答by Felipe Buccioni
A more generic answer, I think this should be in viewset instead of the serializer, because serializer need just serialize, nothing more.
一个更通用的答案,我认为这应该在视图集中而不是序列化器中,因为序列化器只需要序列化,仅此而已。
This simulates conditions to update
passing the id from request.data
to kwargs
, when if the instance doesn't exists, the UpdateModelMixin.update()
raises an Http404
exception what is catched by except block and do a create()
.
这模拟的条件update
从传递IDrequest.data
到kwargs
,如果实例不存在时,将UpdateModelMixin.update()
引发Http404
什么是除了块获取,并做了例外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)