Django-Rest-Framework. Обновление вложенного объекта

У меня проблема с обновлением вложенного объекта.

Итак, у меня есть модель, структура которой подобна этой:

class Invoice(models.Model):
    nr = models.CharField(max_length=100)
    title = models.CharField(max_length=100)

class InvoiceItem(models.Model):
    name = models.CharField(max_length=100)
    price = models.FloatField()
    invoice = models.ForeignKey(Invoice, related_name='items')

Мне нужно создать дочерние объекты из родителя, и я имею в виду это, заключается в том, чтобы создавать InvoiceItems непосредственно при создании объекта Invoice. Для этой цели я написал следующие сериализаторы:

class InvoiceItemSerializer(serializers.ModelSerializer):
    invoice = serializers.PrimaryKeyRelatedField(queryset=Invoice.objects.all(), required=False)
    class Meta:
        model = InvoiceItem


class InvoiceSerializer(serializers.ModelSerializer):
    items = InvoiceItemSerializer(many=True)

    class Meta:
        model = Invoice

    def create(self, validated_data):
        items = validated_data.pop('items', None)
        invoice = Invoice(**validated_data)
        invoice.save()
        for item in items:
            InvoiceItem.objects.create(invoice=invoice, **item)
        return invoice

До сих пор методы create/read/delete работали отлично, кроме update. Я думаю, что логика ниже должна быть правильной, но она что-то пропускает.

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

    # up till here everything is updating, however the problem appears here.
    # I don't know how to get the right InvoiceItem object, because in the validated
    # data I get the items queryset, but without an id.

    items = validated_data.get('items')
    for item in items:
        inv_item = InvoiceItem.objects.get(id=?????, invoice=instance)
        inv_item.name = item.get('name', inv_item.name)
        inv_item.price = item.get('price', inv_item.price)
        inv_item.save()

    return instance

Любая помощь будет действительно оценена.

Ответ 1

Так я выполнил задачу:

Я добавил поле id в InvoiceItemSerializer

class InvoiceItemSerializer(serializers.ModelSerializer):
    ...
    id = serializers.IntegerField(required=False)
    ...

И метод обновления для InvoiceSerializer

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

    items = validated_data.get('items')

    if items:
        for item in items:
            item_id = item.get('id', None)
            if item_id:
                inv_item = InvoiceItem.objects.get(id=item_id, invoice=instance)
                inv_item.name = item.get('name', inv_item.name)
                inv_item.price = item.get('price', inv_item.price)
                inv_item.save()
            else:
                InvoiceItem.objects.create(account=instance, **item)

    return instance

Также в методе create я вывожу id, если он передан.

Ответ 2

Недавно я столкнулся с той же проблемой. Способ, которым я обращался к нему, состоял в том, чтобы заставить id быть обязательным полем:

class MySerializer(serializers.ModelSerializer):

    class Meta:
        model = MyModel
        fields = ('id', 'name', 'url', )
        extra_kwargs = {'id': {'read_only': False, 'required': True}}

Таким образом, мне удалось получить правильный экземпляр и обновить его

Ответ 3

Попробуйте

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


    items = validated_data.get('items')
    for item in items:
        inv_item = InvoiceItem.objects.get(invoice=instance, pk=item.pk)
        inv_item.name = item.get('name', inv_item.name)
        inv_item.price = item.get('price', inv_item.price)
        inv_item.invoice = instance
        inv_item.save()

    instance.save()
    return instance