Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework POST Update if existing or create

I am new to DRF. I read the API docs, maybe it is obvious but I couldn't find a handy way to do it.

I have an Answer object which has one-to-one relationship with a Question.

On the frontend 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

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, and if it doesn't, it will create a new answer.

I couldn't figure out where I should implement it. Should I override create() in serializer or in ViewSet or something else?

Here are my model, serializer, and view:

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") 
like image 728
Ali Ankarali Avatar asked Jun 15 '16 10:06

Ali Ankarali


People also ask

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.

What is renderers in Django REST framework?

The rendering process takes the intermediate representation of template and context, and turns it into the final byte stream that can be served to the client. REST framework includes a number of built in Renderer classes, that allow you to return responses with various media types.


2 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 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'].

As noted by the answer of Nirri, this function should reside inside the serializer. If you use the generic ListCreateView it will call the create function once a post request is sent and generate the corresponding response.

like image 74
K Moe Avatar answered Oct 11 '22 09:10

K Moe


Answer posted by @Nirri helped me as well, but I've found a more elegant solution using Django's QuerySet API shortcut:

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 the Answer to that Question does not exist, it will be created, otherwise it is returned as-is by the question field lookup.

This shortcut, however, won't update the object. QuerySet API has another method for an update operation, which is called update_or_create and posted in other answer down the thread.

like image 21
Damaged Organic Avatar answered Oct 11 '22 11:10

Damaged Organic