我是DRF的新手。 我阅读了API文档,也许它没有注意到,但我找不到方便的方法。
我有一个Answer对象与问题有一对一的关系。
在正面我曾经使用POST方法创建发送到api / answers的答案,并且PUT方法更新发送到例如API /答案/ 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',)
答案 0 :(得分:24)
不幸的是,您提供和接受的答案无法回答您的原始问题,因为它不会更新模型。然而,这可以通过另一种方便的方法轻松实现: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
如果来自Answer
的答案不存在question=validated_data['question']
,则应在数据库中创建validated_data['answer']
个对象。如果它已经存在,django会将其答案属性设置为validated_data['answer']
。
正如Nirri的回答所指出的,这个函数应该驻留在序列化器中。如果您使用通用ListCreateView,它将在发送发布请求后调用create函数并生成相应的响应。
答案 1 :(得分:11)
@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
它完全相同 - 如果Answer
与Question
不存在,则会创建,否则 - 按question
字段查找返回。
但是,此快捷方式不会更新对象。 QuerySet API 还有另一种update
操作方法,该方法称为update_or_create
并发布在other answer down the thread。
答案 2 :(得分:3)
我会使用序列化程序的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
第二个选项是检查答案ID的答案是否存在。
答案ID不会显示在发布请求的验证数据中,除非您使用了某种解决方法并手动将其定义为read_only = false字段:
id = serializers.IntegerField(read_only=False)
但是你应该重新思考这个问题,PUT方法和POST方法作为单独的实体存在是一个很好的理由,你应该在前端分开请求。
答案 3 :(得分:3)
一个更通用的答案,我认为这应该在视图集中而不是序列化程序中,因为序列化程序只需要序列化就可以了。
这将模拟条件,update
将id从request.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)
答案 4 :(得分:1)
我尝试了序列化程序解决方案,但是在单击序列化程序功能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
由于我想保留框架提供的所有功能,因此我更喜欢捕获异常并进行更新:
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
我的应用始终可以使用参数(此处为uuid =主键)调用POST /api/my_model/
。
但是,如果我们在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
答案 5 :(得分:1)
一种更好且更通用的方法是使用可能的实例更新ModelSerializer对象(如果存在)。这使DRF可以遵循标准协议,并且可以轻松地跨模型进行抽象。
要保持通用性,请首先在实例化时使UpdateOrCreate类与modelSerializer一起继承。在其中添加def update_or_create_helper
。
然后为每个要使用其功能的序列化器继承UpdateOrCreate
类,并为该模型添加一个简单的is_valid
定义。
serializers.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')
答案 6 :(得分:0)
也:
try:
serializer.instance = YourModel.objects.get(...)
except YourModel.DoesNotExist:
pass
if serializer.is_valid():
serializer.save() # will INSERT or UPDATE your validated data
答案 7 :(得分:0)
此混入将允许在ListSerializer中使用创建或更新
class CreateOrUpdateMixin(object):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# check if self.pk key is in Meta.fields, if not append it
if self.Meta.model._meta.pk.name not in self.Meta.fields:
self.Meta.fields.append(self.Meta.model._meta.pk.name)
# init pk field on serializer (field will be named accordingly to your pk name)
# specify serializers.IntegerField if you use models.AutoField
self._declared_fields[self.Meta.model._meta.pk.name] = serializers.UUIDField(required=False)
def create(self, validated_data):
obj, created = self.Meta.model.objects.update_or_create(
pk=validated_data.pop(self.Meta.model._meta.pk.name, None),
defaults={**validated_data}
)
return obj
使用方法:
class DatacenterListSerializer(CreateOrUpdateMixin, serializers.ModelSerializer):
class Meta:
model = Datacenter
fields = ['somefield', 'somefield2']