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

时间:2016-05-15 16:12:19

标签: 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')

我需要从父级创建子对象,我的意思是在创建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

到目前为止,创建/读取/删除方法完美无缺,但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

任何帮助都会非常感激。

8 个答案:

答案 0 :(得分:24)

这是我完成任务的方式:

我在<xs:complexType name="AccountLoyaltyDetail"> <xs:sequence> <xs:element name="OperatingCompany" type="ns:OperatingCompanyType" minOccurs="0" /> <xs:element name="Journeys" type="ns:LoyaltyJourneyIdentifier>" /> <xs:element name="Segments" type="ns:LoyaltySegmentIdentifier" /> </xs:sequence> </xs:complexType> <xs:complexType name="LoyaltyJourneyIdentifier"> <xs:sequence> <xs:element name="JourneyIdentifierId" type="xs:string" minOccurs="0"> <xs:annotation> <xs:documentation>Free form text to be echoed back in the reply. Used to match requests and replies.</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType>

中添加了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') 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,我会弹出它。

答案 1 :(得分:6)

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

class MySerializer(serializers.ModelSerializer):

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

这样我就可以检索正确的实例并更新它

答案 2 :(得分:5)

所有这些解决方案对我来说似乎太复杂或太具体,我最终使用了来自 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 :(得分:1)

我认为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 :(得分:0)

尝试

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

答案 5 :(得分:0)

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

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

对于此实现:1-产品具有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 :(得分:0)

尝试一下。

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    

答案 7 :(得分:0)

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

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