我遇到了更新嵌套对象的问题。
所以我有一个模型,其结构类似于这个:
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
任何帮助都会非常感激。
答案 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 包提供了可写的嵌套模型序列化程序,它允许使用嵌套的相关数据创建/更新模型。