Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRF Serializer update list of nested objects

I write the voting app project using Django-rest-framework. It has two models: question and nested answer. I want to update answers in question serializer upon request such as:

{
    "question":"question",
    "answers": [{"answer":"first updated answer"}, {"answer":"second update answer"}]
}

How can I do it properly? I tried to do it, but I deleting answers in every request and create new ones, because of that changes their id every time.

serializer.py

class QuestionSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField()
    answers = AnswerSerializer(many=True)

    class Meta(object):
        model = Question
        fields = ('id', 'question', 'answers', 'total_votes', 'user')
        read_only_fields = ('id', 'total_votes', )

    def update(self, instance, validated_data):
        instance.question = validated_data.get('question', instance.question)
        instance.save()

        question = Question.objects.get(id=instance.id)
        Answer.objects.filter(question=question).delete()

        answers_data = validated_data.pop('answers')
        for answer_data in answers_data:
            Answer.objects.create(question=question, **answer_data)

        return instance

models.py

class Question(models.Model):
    question = models.CharField(verbose_name='Question', max_length=255)
    total_votes = models.IntegerField(verbose_name='Total Votes', default=0)
    user = models.ForeignKey(User, verbose_name='Owner', on_delete=models.CASCADE)

class Answer(models.Model):
    question = models.ForeignKey(Question, verbose_name='Question', related_name='answers',
                                 on_delete=models.CASCADE)
    answer = models.CharField(verbose_name='Answer', blank=True, max_length=255)
    votes_count = models.IntegerField(verbose_name='Count of Votes', default=0)
like image 203
Oleksii Petrushynskyi Avatar asked Oct 24 '25 22:10

Oleksii Petrushynskyi


2 Answers

Just figured this after hours of documentation surfing:

serializer.py

class AnswerSerializer(serializers.ModelSerializer):

    # this is IMPORTANT, without this the 'id' field won't appear in validated data
    id = serializers.IntegerField(required=False, write_only=False)

    class Meta:
        model = Answer

class QuestionSerializer(serializers.ModelSerializer):

    user = serializers.StringRelatedField()
    answers = AnswerSerializer(many=True)

    def update(self, instance, validated_data):
        answer_data = validated_data.pop("answers")

        remove_items = { item.id: item for item in instance.answers.all() }

        for item in answer_data:
            item_id = item.get("id", None)

            if item_id is None:
                # new item so create this
                instance.answers.create(**item)
            elif remove_items.get(item_id, None) is not None:
                # update this item
                instance_item = remove_items.pop(item_id)
                Answer.objects.filter(id=instance_item.id).update(**item)

        for item in remove_items.values():
            item.delete()

        for field in validated_data:
            setattr(instance, field, validated_data.get(field, getattr(instance, field)))
        instance.save()

        return instance
like image 159
ManZzup Avatar answered Oct 27 '25 16:10

ManZzup


Try this

def update(self, instance, validated_data):

    if 'answers' in validated_data:
        answer_ids_new = []
        answer_ids_pre = instance.answers.all().values_list('id', flat=True)
        for answer in validated_data.pop('answers'):
            ans, _created = Answer.objects.get_or_create(**answer)
            ans.question = instance
            ans.save()
            answer_ids_new.append(ans.id)

        delete_ids = set(answer_ids_pre) - set(answer_ids_new)
        Answer.objects.filter(id__in=delete_ids).delete()
    for item, value in validated_data.items():
        setattr(instance, item, value)
    instance.save()
    return instance


Note: I didn't tried the solution

like image 29
JPG Avatar answered Oct 27 '25 16:10

JPG



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!