Django的REST的框架.更新嵌套对象

dim*_*mmg 27 python django rest 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')
Run Code Online (Sandbox Code Playgroud)

我需要从父级创建子对象,我的意思是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
Run Code Online (Sandbox Code Playgroud)

到目前为止,创建/读取/删除方法完美地工作,除了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
Run Code Online (Sandbox Code Playgroud)

任何帮助将非常感激.

dim*_*mmg 29

这是我完成任务的方式:

我已经添加了一个id字段InvoiceItemSerializer

class InvoiceItemSerializer(serializers.ModelSerializer):
    ...
    id = serializers.IntegerField(required=False)
    ...
Run Code Online (Sandbox Code Playgroud)

和更新方法 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')

    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
Run Code Online (Sandbox Code Playgroud)

同样在create方法中我弹出id如果它被传递.

  • 谢谢你的样品.但是用户请不要忘记捕获`Does_otExist`异常,其中`inv_item = InvoiceItem.objects.get(id = item_id,invoice = instance)`可能会引发. (3认同)
  • 值得注意的是,如果您要创建一个真正的“id”字段,您可能需要遵循 django 的约定:“id = models.BigAutoField(primary_key=True)”(请参阅​​ https://docs.djangoproject.com/en /3.2/topics/db/models/#automatic-primary-key-fields) 另外,您可以只使用帮助器 get_or_create:`InvoiceItem.objects.get_or_create()` https,而不是检查 InvoiceItem 是否存在并拆分代码://docs.djangoproject.com/en/3.2/ref/models/querysets/#get-or-create (2认同)

Ket*_*kla 9

我认为 Vitor Hugo Morales 的答案很棒,并且愿意通过循环键将对象中的每个字段分配给经过验证的数据中的字段来贡献我的一分钱,而不是像他那样对其进行硬编码。例如,

def update_product_items(self, instance, validated_data):
    # get the nested objects list
    product_items = validated_data.pop('products')
    # get all nested objects related with this instance and make a dict(id, object)
    product_items_dict = dict((i.id, i) for i in instance.products.all())

    for item_data in product_items:
        if 'id' in item_data:
            # if exists id remove from the dict and update
            product_item = product_items_dict.pop(item_data['id'])
            # remove id from validated data as we don't require it.
            item_data.pop('id')
            # loop through the rest of keys in validated data to assign it to its respective field
            for key in item_data.keys():
                setattr(product_item,key,item_data[key])

            product_item.save()
        else:
            # else create a new object
            ProductItem.objects.create(product=instance, **item_data)

    # delete remaining elements because they're not present in my update call
    if len(product_items_dict) > 0:
        for item in product_items_dict.values():
            item.delete()
Run Code Online (Sandbox Code Playgroud)


djq*_*djq 8

我最近遇到了同样的问题.我解决它的方式是强制id成为必填字段:

class MySerializer(serializers.ModelSerializer):

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

这样我就能够检索正确的实例并进行更新

  • 在`create`方法中,`id`是多余的,会发生什么? (3认同)

Pre*_*eer 8

所有这些解决方案似乎过于复杂或者太具体的我,我结束了使用代码从这里教程这是难以置信的简单,可重复使用

from rest_framework import serializers
from django.contrib.auth import get_user_model
from myapp.models import UserProfile


# You should already have this somewhere
class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = ['nested', 'fields', 'you', 'can', 'edit']


class UserSerializer(serializers.ModelSerializer):
    # CHANGE "userprofile" here to match your one-to-one field name
    userprofile = UserProfileSerializer()

    def update(self, instance, validated_data):
        # CHANGE "userprofile" here to match your one-to-one field name
        if 'userprofile' in validated_data:
            nested_serializer = self.fields['userprofile']
            nested_instance = instance.userprofile
            nested_data = validated_data.pop('userprofile')

            # Runs the update on whatever serializer the nested data belongs to
            nested_serializer.update(nested_instance, nested_data)

        # Runs the original parent update(), since the nested fields were
        # "popped" out of the data
        return super(UserSerializer, self).update(instance, validated_data)
Run Code Online (Sandbox Code Playgroud)

编辑:修正,我在尝试更新它之前添加了对嵌套字段是否存在的检查。

  • 否决者:请提供您否决票的反馈或理由,以便我们能够继续改进 StackOverflow 上的内容。谢谢 (5认同)