Django 休息框架。更新嵌套对象

Posted

技术标签:

【中文标题】Django 休息框架。更新嵌套对象【英文标题】:Django-Rest-Framework. Updating nested object 【发布时间】:2016-09-11 10:59:35 【问题描述】:

我在更新嵌套对象时遇到问题。

所以我有一个结构类似于这个的模型:

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')

我需要从父对象创建子对象,我的意思是在创建Invoice 对象时直接创建InvoiceItems。 为此,我编写了以下序列化程序:

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

到目前为止,除了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

任何帮助将不胜感激。

【问题讨论】:

您可能必须将 pk 作为有效负载的一部分传递。 我路过,但validated_data中不存在 你能展示你的payload吗? 这是我提出请求的方式pastie.org/private/3mnrcxp64ra4j65kvcmoyw 【参考方案1】:

drf-writable-nested 包提供可写的嵌套模型序列化程序,允许使用嵌套的相关数据创建/更新模型。

https://github.com/beda-software/drf-writable-nested

【讨论】:

【参考方案2】:

所有这些解决方案对我来说似乎都太复杂或太具体了,我最终使用了来自 the tutorial here 的代码,它非常简单且可重复使用

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)

编辑:错误修正,我在尝试更新嵌套字段之前添加了一个检查嵌套字段的存在。

【讨论】:

投反对票的人:请为您投反对票提供反馈或理由,以便我们继续改进 *** 上的内容。谢谢 很好的解决方案,但是如果用户配置文件被删除并且您正在检查它是否不在经过验证的数据中,那么什么都不做? 如果用户配置文件不存在,此代码应该仍然有效。这只是我的意见,但我不确定允许用户删除他们链接的用户个人资料是否是个好主意。【参考方案3】:

我认为 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()

【讨论】:

【参考方案4】:

试试这个。

from rest_framework.utils import model_meta

class InvoiceSerializer(serializers.ModelSerializer):
    invoice_item=InvoiceItemSerializer(many=True,required=False)

    field_map="invoice_item" :  "model":  models.InvoiceItem
                                   "pk_field" : "id"    



    class Meta:
        model = models.Invoice
        fields = '__all__'

    def create(self, validated_data):
        extra_data=
        for key in self.field_map.keys():
            extra_data[key]=validated_data.pop(key,[])

        # create invoice
        invoice = models.Invoice.objects.create(**validated_data)

        for key in extra_data.keys():
            for data in extra_data[key]:
                self.field_map[key]["model"].objects.create(invoice=invoice,**data)

        return invoice

    def _update(self,instance,validated_data):
        #drf default implementation
        info = model_meta.get_field_info(instance)

        for attr, value in validated_data.items():
            if attr in info.relations and info.relations[attr].to_many:
                field = getattr(instance, attr)
                field.set(value)
            else:
                setattr(instance, attr, value)
        instance.save()
        return instance

    def update(self,instance,validated_data):

        extra_data=
        for key in self.field_map.keys():
            extra_data[key]=validated_data.pop(key,[])

        instance=self._update(instance,validated_data)

        for key in extra_data.keys():
            for data in extra_data[key]:

                id=data.get(self.field_map[key]["pk_field"],None)
                if id:
                    try:
                        related_instance=self.field_map[key]["model"].objects.get(id=id)
                    except:
                        raise
                    self._update(related_instance,data)
                else:
                    self.field_map[key]["model"].objects.create(**data)

        return instance    

【讨论】:

【参考方案5】:

就我而言,我希望更新所有嵌套对象列表,即使它们已被删除。

我不想在每个嵌套对象中删除,调用嵌套模型的DELETE方法;只需更新整个对象和您的嵌套对象列表。

对于这个实现:1-Product 有 N-ProductItems

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'])

            product_item.quantity = item_data['quantity']
            product_item.size_pmg = item_data['size_pmg']
            product_item.size_number = item_data['size_number']
            product_item.color = item_data['color']
            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()

【讨论】:

【参考方案6】:

这是我完成任务的方式:

我在InvoiceItemSerializer 中添加了一个id 字段

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')

    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 通过,我将弹出它。

【讨论】:

感谢您的样品。但请用户不要忘记捕捉inv_item = InvoiceItem.objects.get(id=item_id, invoice=instance) 可能引发的DoesNotExist 异常。 我没有在更新函数的已验证数据对象中获取 id 字段。我做错了什么? 值得注意的是,如果您正在创建一个真正的“id”字段,您可能需要遵循 django 的约定:id = models.BigAutoField(primary_key=True)(参见docs.djangoproject.com/en/3.2/topics/db/models/…)另外,而不是检查 InvoiceItem 是否存在并拆分您的代码,您可以只使用助手 get_or_create:InvoiceItem.objects.get_or_create()docs.djangoproject.com/en/3.2/ref/models/querysets/…【参考方案7】:

试试

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

【讨论】:

【参考方案8】:

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

class MySerializer(serializers.ModelSerializer):

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

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

【讨论】:

如果create 方法在id 是多余的情况下会发生什么?

以上是关于Django 休息框架。更新嵌套对象的主要内容,如果未能解决你的问题,请参考以下文章

休息框架“元组”对象没有属性“_meta”

Django rest框架嵌套自引用对象

Django 和 Django 休息框架

Django 休息框架问题

Django 测试休息框架:APIRequestFactory vs APIClient

markdown django休息框架过滤器