【问题标题】:Filter a query set in Django based on aggregate of one of its fields for a foreign key?根据外键字段之一的聚合过滤Django中的查询集?
【发布时间】:2019-07-17 01:21:17
【问题描述】:

考虑以下两个模型,WorkerInvoice

class Worker(models.Model):
    name = models.CharField(max_length=255)


class Invoice(models.Model):
    worker = models.ForeignKey(
        'Worker', on_delete=models.CASCADE)
    amount = models.DecimalField(max_digits=10, decimal_places=2)

对于给定的Worker,我只想查询总数(amounts 的总和)大于零的 Invoices。

基本上,我想定义一个 get_payable_invoices() 函数,它返回一个 Queryset 以便测试通过:

from decimal import Decimal

from django.test import TestCase
from django.db.models import Sum

from myapp.models import Worker, Invoice


def get_payable_invoices():
    return Invoice.objects.filter(
        worker__in=Worker.objects.annotate(Sum('invoice__amount')))\
        .filter(invoice__amount__sum__gt=0)


class PayableInvoicesTests(TestCase):
    def test_get_payable_invoices(self):
        worker1 = Worker.objects.create(name="John Doe")

        invoice1 = Invoice.objects.create(
            worker=worker1, amount=Decimal('100.00'))
        invoice2 = Invoice.objects.create(
            worker=worker1, amount=Decimal('-150.00'))

        worker2 = Worker.objects.create(name="Mary Contrary")
        invoice3 = Invoice.objects.create(
            worker=worker2, amount=Decimal('200.00'))

        self.assertEqual(get_payable_invoices().count(), 1)
        self.assertEqual(get_payable_invoices().first(), invoice3)

但当前的实现不起作用,并返回一个

django.core.exceptions.FieldError: Cannot resolve keyword 'invoice' into field. Choices are: amount, id, worker, worker_id

看起来虽然遍历查询集时返回的对象确实有invoice__amount__sum属性,但不能这样在filter()中使用。

在我看来,我应该按照https://docs.djangoproject.com/en/2.2/ref/models/expressions/#using-aggregates-within-a-subquery-expression 中的查询来制定查询,但我正在努力使该示例适应我的情况,因为total_comments 返回一个数字,而我想要一个列表Workers。我也不完全确定子查询是否是正确的方法,或者如果没有它们,这是否可以以更简单的方式完成。关于如何在 Django 中实现这样的查询有什么想法吗?

【问题讨论】:

  • 你确定查询集有invoice__amount__sum吗?我认为它应该是聚合函数中的Sum(invoice_set__amount),因为您没有为Invorce.worker 明确指定related_name

标签: python django subquery django-orm


【解决方案1】:

事实证明,从https://docs.djangoproject.com/en/2.2/topics/db/aggregation/#filtering-on-annotations 开始,在过滤注释时,您需要使用与默认名称不同的名称来“消除歧义”。以下函数使测试通过:

def get_payable_invoices():
    return Invoice.objects.filter(
        worker__in=Worker.objects
        .annotate(invoice_total=Sum('invoice__amount'))
        .filter(invoice_total__gt=0))

我还验证了执行了一个查询。例如,我可以在单元测试的底部添加以下内容:

    with self.assertNumQueries(1):
        for invoice in get_payable_invoices():
            pass

【讨论】:

    猜你喜欢
    • 2019-02-17
    • 2016-07-20
    • 2011-12-25
    • 2013-09-17
    • 2020-12-19
    • 2014-10-26
    • 1970-01-01
    • 2021-10-16
    • 2021-11-17
    相关资源
    最近更新 更多