Django Rest Framework POST Update if existing or created

I am new to DRF. I read the API docs, this may not be great, but I could not find a convenient way to do this.

I have an Answer object that has a one-to-one relationship with a question.

On the front side, I used the POST method to create a response sent to api / answers and the PUT method to update sent by, for example. api / replies / 24

But I want to process it on the server side. I will only post the POST method to api / answers, and DRF will check based on answer_id or question_id (as it is one to one) if the object exists. If this happens, it will update the existing one if it does not create a new answer.

Where should I implement this, I could not understand. Override creation in serializer or in ViewSet or something else?

My model, serializer and view are as follows:

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',) 
+16
source share
6 answers

Unfortunately, your provided and accepted answer does not answer your original question, since it does not update the model. This, however, is easily achieved using 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 response attribute to validated_data['answer'] .

As noted in Nirri's answer, this function must be inside the serializer. If you use a generic ListCreateView , it will call the create function after sending the mail request and creating the corresponding response.

+26
source

@Nirri's answer also helped me , but I found a more elegant solution using the 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 the same - if the Answer does not exist with this Question , it will be created, otherwise it will be returned as is when searching for the question field.

This shortcut, however, will not update the object. The QuerySet API has another method for the update operation, which is called update_or_create and is posted in another answer down the chain .

+12
source

I would use the method of creating serializers.

In it, you can check whether the answer to the question (with the identifier that you ask in the field associated with the primary key has a question), and if it is, get the object and update it, otherwise create a new one.

So the first option would look something like this:

 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 

The second option is to check if the answer exists with the response identifier.

The response identifier will not appear in the verified publication request data unless you used a kind of workaround and manually defined them as read_only = false fields:

 id = serializers.IntegerField(read_only=False) 

But you must still rethink this. There is a good reason why the PUT method and POST methods exist as separate objects, and you should separate requests on the external interface.

+4
source

Also:

 try: serializer.instance = YourModel.objects.get(...) except YourModel.DoesNotExist: pass if serializer.is_valid(): serializer.save() # will INSERT or UPDATE your validated data 
+1
source

I tried the solution for the serializer, but there seemed to be an exception before it turned on the create(self, validated_data) serializer function. This is because I use ModelViewSet (which in turn uses the class CreatedModelMixin ). Further studies show that the exception here is:

 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 the functions provided by the framework, I prefer to catch exceptions and use the route 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 application can always call POST /api/my_model/ with parameters (here uuid = primary key).

However, it would be better if we could handle this in the update function?

  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 
+1
source

A better and more generalized way to apply this is to update the ModelSerializer object with a potential instance, if one exists. This allows DRF to follow standard protocols and easily abstract from models.

To make things common, start by creating the UpdateOrCreate class, which will be inherited along with modelSerializer when instantiating. In this we add def update_or_create_helper .

Then inherit the UpdateOrCreate class for each serializer you want functionality with, and add a simple is_valid definition specific to this model.

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') 
+1
source

All Articles