【问题标题】:Django: Nested Sum() annotations across three Model RelationsDjango:跨三个模型关系的嵌套 Sum() 注释
【发布时间】:2022-08-05 06:39:04
【问题描述】:

我正在使用 Django 整理店面环境来处理订单,并且正在努力编写我正在尝试编写的注释之一。

显着的数据模型是这样的

class Order(ClusterableModel):
    \"various model fields about the status, owner, etc of the order\"

class OrderLine(Model):
    order = ParentalKey(\"Order\", related_name=\"lines\")
    product = ForeignKey(\"Product\")
    quantity = PositiveIntegerField(default=1)
    base_price = DecimalField(max_digits=10, decimal_places=2) 

class OrderLineOptionValue(Model):
    order_line = ForeignKey(\"OrderLine\", related_name=\"option_values\")
    option = ForeignKey(\"ProductOption\")
    value = TextField(blank=True, null=True)
    price_adjustment = DecimalField(max_digits=10, decimal_places=2, default=0)

OrderLine 代表以特定基本价格和数量购买的一种或多种特定产品。此基本价格已从产品模型中复制,以保留创建订单时的产品价格。

因此,订单只是多个订单行的集合

复杂性来自 OrderLineOptionValue 模型,它表示基于用户做出的选择对基本价格的修改,如果产品有多个选项,则每个订单行可能有多个调整。颜色、尺寸、重量等都可能对价格产生不同的影响。

在查询 OrderLine 模型时,我已经能够使用以下查询成功地使用该行的适当行总数 (base+sum(price_adjustments))*quantity 注释每个结果:

annotation = {
    \"line_total\": ExpressionWrapper((F(\"base_price\")+Coalesce(Sum(\"option_values__price_adjustment\", output_field=DecimalField(max_digits=10, decimal_places=2)), Value(0)))*F(\"quantity\"), output_field=DecimalField(max_digits=10, decimal_places=2)),
}
OrderLine.objects.all().annotate(**annotation)

对于我尝试过的所有测试,该注释似乎都能正常工作。值得注意的是,OrderLines 可能没有 price_adjustments,因此是 Coalesce。

我的问题开始于尝试用它的总和将所有它各自的行项目加在一起来注释每个订单。我最初的尝试导致了异常无法计算 Sum(\'line_total\'):\'line_total\' 是一个聚合我只能假设这确实是一个非法的 SQL 请求,因为我对 SQL 的实际知识有点生疏。

lineItemSubquery=OrderLine.objects.filter(order=OuterRef(\'pk\')).order_by()
#the same annotation as above
lineItemSubquery=lineItemSubquery.annotate(**annotation).values(\"order\")

Order.objets.all().annotate(annotated_total=Coalesce(Subquery(lineItemSubquery.annotate(sum_total=Sum(\"line_total\")).values(\'sum_total\')), 0.0))

在偶然发现this question 之后,我尝试对其进行了一些重组,虽然我能够让它返回一个数字,但它这样做不正确,似乎只返回每个订单的第一个 line_total。

lineItemSubquery=OrderLine.objects.filter(Q(order=OuterRef(\"pk\"))).annotate(**annotation).values(\"line_total\")
Order.objects.all().annotate(annotated_total=Coalesce(Subquery(lineItemSubquery), 0.0))

通过对 lineItemSubquery [1:2] 进行切片,注释也可以工作,但随后会计算出第二个行项目的数量,而忽略任何其他行项目。我认为这是所引用问题的副产品以及他们如何请求一组值的最大值(按顺序排列的第一个结果)而不是整个数据集的总和。

我的直觉说我需要找到一种方法来 Sum() 子查询,或者由于多级方面,我需要某种额外的 OuterRef 来桥接所有三个模型之间的关系?我正在认真考虑将每个 OrderLine 的计算总数直接缓存在模型字段中,以便完全避免该问题。

    标签: django annotations wagtail


    【解决方案1】:

    我已经在一个在线商店平台上处理过这种确切的情况。我们模型的基础是(当然,还有比这些更多的领域):

    class Product(models.Model):
        base_price = models.DecimalField(...)
        options = models.ManyToManyField('ProductOption', ...)
    
    class ProductOption(models.Model):
        [price - see below]
        ...
    
    class Order(models.Model):
        cost = models.DecimalField(...)
    
    class OrderItem(models.Model):
        order = models.ForeignKey('Order', ...)
        cost = models.DecimalField(...)
    
    class OrderItemOption(models.Model):
        order_item = models.ForeignKey('OrderItem', null=True, blank=True, on_delete=models.CASCADE)
        product_option = models.ForeignKey('ProductOption', null=True, blank=True, on_delete=models.PROTECT)
        value = models.CharField(max_length=255, default='', blank=True)
    

    产品的price 属性可以是Product 模型上的字段(非常简单),也可以是基于定价表或任何数量的其他因素的计算属性。对我来说,后一种方法更可取。使用后一种方法时,您可以在def price(self): 声明中使用@property 装饰器来声明Product,或者定义一个函数:def calculate_price(self):

    在此背景下并解决您的问题,我在 OrderOrderItem 上覆盖 save() 以计算成本,然后将其存储在每个模型的 cost 字段中。当然,您还必须从OrderItem.save() 方法触发Order.cost 的计算,以便Order 成本始终是最新的。成本计算可能变得非常复杂。我永远不会尝试在 SQL 中计算成本。另外,我总是更喜欢覆盖save() 而不是使用post_save() 信号。

    在从另一个模型触发一个模型的计算时,您必须小心,以免遇到递归错误。

    上述代码(和连续迭代)已经在生产环境中运行了六年。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-26
      • 2014-10-12
      • 2020-05-21
      • 2015-07-31
      • 1970-01-01
      • 2012-05-27
      • 2016-12-05
      • 2021-04-30
      相关资源
      最近更新 更多