Django:将价格注释到行,然后按字段对行进行分组,并将价格总和注释到组
Posted
技术标签:
【中文标题】Django:将价格注释到行,然后按字段对行进行分组,并将价格总和注释到组【英文标题】:Django: Annotating price to row, then grouping rows by field, and annotating price sums to groups 【发布时间】:2021-04-14 19:41:06 【问题描述】:我的情况:做快递。我有一个客户,它有一个价目表。价目表具有基于邮政编码的定价区域。客户有DeliveriesOrders,可以有多个Deliveries(取货、送件),价格根据邮政编码计算。
我计算交货行价格的方式是:
sheet_zones = customer.price_sheet.price_sheet_zones.filter(postal_codes__code=OuterRef("postal_code"))
delivery_orders = DeliveryOrder.objects.select_related("customer") \
.prefetch_related(
Prefetch(
'deliveries',
queryset=Delivery.objects.annotate(price=Subquery(sheet_zones.values("price"))).order_by("-is_pickup"),
to_attr="priced_deliveries"
)
)
这使我能够为每个交货行注释价格。
但是我不能注释预取字段,因为它会导致错误,所以.annotate(Sum("priced_deliveries"))
不起作用。
我很难得到一个交付订单中所有交付的总和。但更让我摸不着头脑的是,如何通过一个名为“reference”的字段对所有交货进行分组,并对每个参考的所有交货价格求和。
相关模型:
class DeliveryOrder(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, related_name="delivery_orders")
class Delivery(models.Model):
delivery_order = models.ForeignKey(DeliveryOrder, on_delete=models.SET_NULL, null=True, related_name="deliveries")
is_pickup = models.BooleanField(default=True)
reference = models.CharField(max_length=64, blank=True, null=True)
postal_code = models.IntegerField()
客户相关模型:
class PriceSheet(models.Model):
name = models.CharField(max_length=255)
class Customer(models.Model):
name = models.CharField(max_length=255)
price_sheet = models.ForeignKey(PriceSheet,
on_delete=models.SET_NULL, blank=True, null=True, related_name="customers")
class PriceSheetZoneItem(models.Model):
price_sheet = models.ForeignKey(PriceSheet, on_delete=models.SET_NULL, null=True, related_name="price_sheet_zones")
zone = models.ForeignKey(Zone, on_delete=models.SET_NULL, null=True, related_name="price_sheet_zones")
postal_codes = models.ManyToManyField(PostalCode, related_name="price_sheet_postal_codes")
price = models.DecimalField(decimal_places=2, max_digits=9)
邮政编码相关:
class Town(models.Model):
name = models.CharField(max_length=64)
class Zone(models.Model):
name = models.CharField(max_length=32)
class PostalCode(models.Model):
code = models.IntegerField()
town = models.ForeignKey(Town, on_delete=models.SET_NULL, null=True, related_name="postal_codes")
我愿意接受所有的建议和帮助。我很可能试图以错误的方式做到这一点。
使用 Django 3.2
提前谢谢你!
【问题讨论】:
【参考方案1】:首先,在Delivery
模型中,postal_code
是否应该是PostalCode
模型的 ForeignKey?在下面我将假设它是。
我认为您想使用子查询进行注释。在 SQL 中,这看起来像:
SELECT
delivery_order.*
(SELECT SUM(<price computation from a single delivery>)
FROM delivery
JOIN <join to price sheet table so you can calculate price>
WHERE delivery.delivery_order_id=delivery_order.id
) AS total_price;
FROM
delivery_order;
您的 ORM 代码类似于:
delivery_orders = DeliveryOrder.objects.annotate(
total_price=Subquery(
Delivery.objects.values(
'delivery_order' # This is necessary to get the proper group by
).annotate(
'customer_id'=OuterRef('id')
).annotate(
price=Sum(postal_code__pricesheetzoneitem__price)
).filter(
delivery_id=OuterRef('id'),
postal_code__pricesheetzoneitem__pricesheet__customer_id=OuterRef('id')
).values(
'price' # This is necessary to select only one value
)
)
)
使用django-sql-utils package 可能会更简单
from sql_util.aggregates import SubquerySum
delivery_orders = DeliveryOrder.objects.annotate(
total_price=SubquerySum('delivery__postal_code__pricesheetzoneitem__price',
filter=Q(pricesheet__customer_id=OuterRef('id'))
)
)
我无法测试这个确切的代码,所以我不确定它是否 100% 正确。
编辑:postal_code
不是外键
首先,如果您要存储邮政编码的实际数字,例如90210,在 postal_code 字段中,它应该是一个字符串,而不是一个整数。一些邮政编码以 0 开头。
您有几个选择。一种是创建两个字段,postal_code_string
存储原始邮政编码信息以防出错,postal_code
是 PostalCode
的 ForeignKey。
另一种选择是使用JoinField。这允许您保持一切不变,但 ORM 可以在 postal_code 字段上进行连接。对于这个问题,JoinField 有点离题
【讨论】:
感谢您的回复。在 IntegerField 上设置 postal_code 的原因是允许使用无效邮政编码的订单通过。然后它将定价为零,并且对邮政编码有更多了解的管理员可以在计费中修复它。 添加了一些关于 postal_code 不是外键的附加信息。 再次感谢您的支持。邮政编码为整数的原因是它可以处理范围内的邮政编码。我确实有帮助函数将邮政编码归零以实现人类可读的表示以上是关于Django:将价格注释到行,然后按字段对行进行分组,并将价格总和注释到组的主要内容,如果未能解决你的问题,请参考以下文章
FineReport报表开发工具中,如何对行进行强制分页,但是列全部显示?