django-rest-framework - 尝试在嵌套的1到M上设置required = False标志? [英] django-rest-framework - trying to set required=False flag on nested 1-to-M?
问题描述
我有一个Cart对象,以及CartItem,它们链接回一个购物车:
class Cart(models.Model):
customer = models.ForeignKey(Customer)
date_created = models.DateTimeField(auto_now_add = True)
date_modified = models.DateTimeField(auto_now = True)
类CartItem(models.Model):
cart = models.ForeignKey (Cart,related_name ='cartitems')
product = models.ForeignKey(Product,help_text ='cart in a cart')
quantity = models.PositiveIntegerField(default = 1,help_text ='产品')
date_added = models.DateTimeField(auto_now_add = True,help_text =该产品添加到购物车的日期。)
我已经创建了两个序列化器:
class CartItemSerializer(serializers.ModelSerializer ):
product = serializers.Hyperli nkedRelatedField(view_name ='product-detail')
class Meta:
model = CartItem
class CartSerializer(serializers.ModelSerializer):
customer = serializer.HyperlinkedRelatedField(view_name ='customer-detail')
cartitems = CartItemSerializer(required = False)
total_price = serializers.CharField(source ='total_price',read_only = True)
shipping_cost = serializer.CharField(source ='shipping_cost',read_only = True)
class Meta:
model = Cart
fields =('id','customer','date_created' ,'date_modified','cartitems','total_price','shipping_cost')
尝试POST以创建一个新的购物车,我收到一个错误,假设当它尝试设置不存在的CartItem:
TypeError at / api / v1 / carts /
add()参数*必须是序列,而不是NoneType
有没有办法让DRF尊重 required = False
标志我得到Cart.cartitems?
干杯,
维克多
编辑:
我再次尝试追踪它:
这是调用BaseSerializer.save )在rest_framework / serializers.py中与CartSerializer对象。
def save(self,** kwargs):
保存反序列化对象并返回。
如果isinstance(self.object,list):
[self.save_object(item,** kwargs)for item in self.object]
如果self.object._deleted:
[self.object._deleted中的项目的self.delete_object(item)]
else:
self.save_object(self.object,** kwargs)
return self.object
然后调用save_object()类:
def save_object(self,obj,** kwargs):
保存反序列化的对象并返回它。
如果getattr(obj,'_nested_forward_relations',无):
#在我们可以保存
#父实例之前,需要保存嵌套关系
for field_name,obj._nested_forward_relations.items()中的子对象:
如果sub_object:
self.save_object(sub_object)
setattr(obj,field_name,sub_object)
obj.save(** kwargs)
if getattr(obj,'_m2m_data',None):
for accessor_name,object_list in obj._m2m_data.items():
setattr(obj,accessor_name,object_list)
del(obj._m2m_data)
如果getattr(obj,'_related_data',None):
for accessor_name,相关于obj._related_data .items():
如果isinstance(related,RelationsList):
#嵌套的反向fk关系
相关的相关的_item
fk_field = obj._meta.get_field_by _name(accessor_name)[0] .field.name
setattr(related_item,fk_field,obj)
self.save_object(related_item)
#删除任何已删除的对象
如果related._deleted:
[self.delete_object(item)for related in_andleted]
elif isinstance(related,models.Model):
#一个关系
fk_field = obj._meta.get_field_by_name(accessor_name)[0] .field.name
setattr(related,fk_field,obj)
self.save_object(related)
else :
#反向FK或反向单一
setattr(obj,accessor_name,related)
del(obj._related_data)
Cart对象具有设置为dict的 _related_data
字段:
{'cartitems':无}
因此,在第二行,它在django / db / models / fields / related.py中调用setattr:
$ _ $ _ $(self,instance,value):
如果实例为None:
raise AttributeError(Manager必须通过实例访问)
manager = self .__ get __(instance)
#如果外键可以支持null,则完全清除相关的集合。
#否则,只需将命名的对象移动到集合中。
如果self.related.field.null:
manager.clear()
manager.add(* value)
这是最后一个班级(manager.add(* value)),导致:
TypeError:*必须是序列后的add()参数,而不是NoneType
检查串行器关系文档,首先您需要将 many = True
添加到您的 cartitems
字段中。
不幸的是这是只读的。文档只是说对于读写关系,你应该使用平面关系风格 - 你可以找到一个关于这里的问题(虽然这只是处理1-1的情况)。
目前的策略涉及使 cartitems
只读,然后:做某事 post_save
,使用第二个串行器或单独的请求单独的端点设置相关实体。鉴于更好地支持嵌套文字即将到来,我可能会倾向于单独请求单独的端点(尽管这显然将取决于您的约束)。
我希望有帮助。
编辑:(更新到评论中的问题和讨论后)。
如果您使用单独的端点来添加CartItem,则使 cartitems
只读应消除错误。
但是(如果你不是只读),请查看您从 save_object
发布的DRF代码发生在相关的块中的 related_item中,您确实需要一个列表。对于没有CartItems的Cart的适当dict(片段)不是
{'cartitems':无}
而是 {'cartitems':[]}
。 - 这当然意味着你的 required = False
标志没有做任何事情。 (所以也许简短的答案是否 - Will现在推迟到邮件列表讨论
I'm having some issue with django-rest-framework, and nested objects.
I have a Cart object, as well as CartItem, which links back to a Cart:
class Cart(models.Model):
customer = models.ForeignKey(Customer)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='cartitems')
product = models.ForeignKey(Product, help_text='Product in a cart')
quantity = models.PositiveIntegerField(default=1, help_text='Quantity of this product.')
date_added = models.DateTimeField(auto_now_add=True, help_text='Date that this product was added to the cart.')
I've created serializers for both:
class CartItemSerializer(serializers.ModelSerializer):
product = serializers.HyperlinkedRelatedField(view_name='product-detail')
class Meta:
model = CartItem
class CartSerializer(serializers.ModelSerializer):
customer = serializers.HyperlinkedRelatedField(view_name='customer-detail')
cartitems = CartItemSerializer(required=False)
total_price = serializers.CharField(source='total_price', read_only=True)
shipping_cost = serializers.CharField(source='shipping_cost', read_only=True)
class Meta:
model = Cart
fields = ('id', 'customer', 'date_created', 'date_modified', 'cartitems', 'total_price', 'shipping_cost')
However, whenever I try to POST to create a new cart, I get an error, assumedly when it tries to set the non-existent CartItem:
TypeError at /api/v1/carts/
add() argument after * must be a sequence, not NoneType
However, a Cart isn't required to actually have CartItems.
Is there any way to get DRF to respect the required=False
flag I get on Cart.cartitems?
Cheers, Victor
EDIT:
I took a stab at tracing it through again:
It's calling BaseSerializer.save() in rest_framework/serializers.py with a CartSerializer object.
def save(self, **kwargs):
"""
Save the deserialized object and return it.
"""
if isinstance(self.object, list):
[self.save_object(item, **kwargs) for item in self.object]
if self.object._deleted:
[self.delete_object(item) for item in self.object._deleted]
else:
self.save_object(self.object, **kwargs)
return self.object
It then calls save_object() on the same class:
def save_object(self, obj, **kwargs):
"""
Save the deserialized object and return it.
"""
if getattr(obj, '_nested_forward_relations', None):
# Nested relationships need to be saved before we can save the
# parent instance.
for field_name, sub_object in obj._nested_forward_relations.items():
if sub_object:
self.save_object(sub_object)
setattr(obj, field_name, sub_object)
obj.save(**kwargs)
if getattr(obj, '_m2m_data', None):
for accessor_name, object_list in obj._m2m_data.items():
setattr(obj, accessor_name, object_list)
del(obj._m2m_data)
if getattr(obj, '_related_data', None):
for accessor_name, related in obj._related_data.items():
if isinstance(related, RelationsList):
# Nested reverse fk relationship
for related_item in related:
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
setattr(related_item, fk_field, obj)
self.save_object(related_item)
# Delete any removed objects
if related._deleted:
[self.delete_object(item) for item in related._deleted]
elif isinstance(related, models.Model):
# Nested reverse one-one relationship
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
setattr(related, fk_field, obj)
self.save_object(related)
else:
# Reverse FK or reverse one-one
setattr(obj, accessor_name, related)
del(obj._related_data)
The Cart object has a _related_data
field that is set to a dict:
{'cartitems': None}
Hence, on the second-last line, it calls setattr in django/db/models/fields/related.py:
def __set__(self, instance, value):
if instance is None:
raise AttributeError("Manager must be accessed via instance")
manager = self.__get__(instance)
# If the foreign key can support nulls, then completely clear the related set.
# Otherwise, just move the named objects into the set.
if self.related.field.null:
manager.clear()
manager.add(*value)
It's this last liner (manager.add(*value)) that causes the:
TypeError: add() argument after * must be a sequence, not NoneType
Checking the Serializer Relation Docs, first you need to add many=True
to your cartitems
field.
Unfortunately this is read-only. The docs just say "For read-write relationships, you should use a flat relational style" — you can find a question about that here (although that's only dealing with the 1-1 case).
Current strategies involve making cartitems
read-only and then either: doing something post_save
, using a second serializer or making a separate request to a separate endpoint to set the related entities. Given that better support for Nested Writes is coming I'd probably be inclined towards a separate request to a separate endpoint for the moment (though that will obviously depend on your constraints).
I hope that helps.
EDIT: (After update to question & discussion in comments).
If you're using a separate endpoint for adding CartItems then making cartitems
read-only should eliminate the error.
However (if you're not making it read-only) looking at the DRF code you posted from save_object
it occurs that in the related_item in related
block you really do need a list. The appropriate dict (fragment) for a Cart with no CartItems is not {'cartitems': None}
but rather {'cartitems': []}
. — This of course means your required=False
flag isn't doing anything. (So perhaps the short answer is "No" — Will now defer to the mailing list discussion
这篇关于django-rest-framework - 尝试在嵌套的1到M上设置required = False标志?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!