【问题标题】:django-rest-framework - trying to set required=False flag on nested 1-to-M?django-rest-framework - 试图在嵌套的 1 到 M 上设置 required=False 标志?
【发布时间】:2013-08-27 03:44:52
【问题描述】:

我在 django-rest-framework 和嵌套对象方面遇到了一些问题。

我有一个 Cart 对象以及 CartItem,它链接回一个 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.')

我已经为两者创建了序列化程序:

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

但是,每当我尝试 POST 以创建新购物车时,我都会收到一个错误,假设它尝试设置不存在的 CartItem 时:

TypeError at /api/v1/carts/
add() argument after * must be a sequence, not NoneType

但是,购物车并不一定要有 CartItems。

有什么方法可以让 DRF 尊重我在 Cart.cartitems 上获得的 required=False 标志?

干杯, 维克多

编辑:

我再次尝试追踪它:

它使用 CartSerializer 对象在 rest_framework/serializers.py 中调用 BaseSerializer.save()。

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

然后在同一个类上调用 save_object():

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)

Cart 对象有一个 _related_data 字段,该字段设置为 dict:

{'cartitems': None}

因此,在倒数第二行,它调用了 django/db/models/fields/related.py 中的 setattr:

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)

正是这最后一个衬里 (manager.add(*value)) 导致:

TypeError: add() argument after * must be a sequence, not NoneType

【问题讨论】:

  • 您是否仅从可浏览的 API 收到此错误?或者当你实际发布数据时?

标签: django django-rest-framework


【解决方案1】:

检查Serializer Relation Docs,首先您需要将many=True 添加到您的cartitems 字段中。

不幸的是,这是只读的。文档只是说“对于读写关系,你应该使用扁平关系样式”——你可以找到 question about that here(尽管这只是处理 1-1 的情况)。

当前的策略包括将cartitems 设为只读,然后:做某事post_save,使用第二个序列化程序或向单独的端点发出单独的请求以设置相关实体​​。鉴于对嵌套写入的更好支持即将到来,我目前可能倾向于向单独的端点发送单独的请求(尽管这显然取决于您的限制)。

希望对你有帮助。

编辑:(更新到 cmets 中的问题和讨论后)。

如果您使用单独的端点来添加 CartItems,则将 cartitems 设为只读应该可以消除错误。

但是(如果您没有将其设为只读)查看您从save_object 发布的 DRF 代码,在related_item in related 块中您确实需要一个列表。没有 CartItems 的购物车的适当字典(片段)不是{'cartitems': None},而是{'cartitems': []}。 — 这当然意味着您的 required=False 标志没有做任何事情。 (所以也许简短的回答是“不”——Will now defer to the mailing list discussion

【讨论】:

  • 嗯,我只是想创建一个空的 Cart 对象,而不是任何 CartItems。我已经有了第二个端点,它实际上将 CartItems 添加到购物车中。但我的理解是,这在当前的 DRF 迭代中是不可能的,对吧?
  • 是的。这是。您的 caritems 需要 many=True 标志和 read_only=True (这可能是暗示的)但是(假设在其他地方没有限制)DRF 会让您这样做。 ---- 你能在其中设置一个断点并返回错误发生的地方吗? “*... 之后的 add() 参数”表明该 api 被错误地调用(我猜这解决了在 if self.many: 叉子的错误一侧。(强调“猜测”。)
  • 我添加了一些更多信息来追踪原始问题。这是否有助于您进一步指出正确的方向?
  • 很好的额外信息。现在必须思考。 :-) 你接受了答案; many=True 解决了这个问题吗?
  • 不幸的是,不是 =(。我添加了 many=True,但我仍然遇到同样的错误。我想我会认识到您迄今为止为提供帮助所付出的努力。但是,任何其他帮助/您的建议将不胜感激=)。我注意到,如果我有 M2M,我会遇到同样的问题,并且我尝试创建一个新对象,而没有与它相关联。
猜你喜欢
  • 1970-01-01
  • 2015-07-22
  • 1970-01-01
  • 2020-07-28
  • 2015-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-08
相关资源
最近更新 更多