Django Rest Framework POST Update if existing or create

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.


A more generic answer, I think this should be in viewset instead of the serializer, because serializer just needs to serialize, nothing more.

This simulates conditions to update passing the id from request.data to kwargs, so if the instance doesn't exist, the UpdateModelMixin.update() raises an Http404 exception that is caught by the except block and calls 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)

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.


I would use the serializers' create method.

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.

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.

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:

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.