如何在Django中使用Count Q中的过滤器

时间:2018-01-08 19:32:59

标签: python django python-3.x

我正在尝试按其外键在过去7天内创建的对象数量对查询集进行排序。

如果有三个标题如:

Title one --> total entry: 100, entries in last 7 days: 5

Title two --> total entry: 10, entries in last 7 days: 8

Title three --> total entry: 50, entries in last 7 days: 2

我希望他们订购如下:

Title two > Title one > Title three

这就是我想要获得上述生成器的内容:

all_titles = Title.objects.annotate(
    num_entries=Count(
        "entry", filter = Q(entry__entry_date2__gte=make_aware(
            datetime.datetime.today()-datetime.timedelta(days=7)
            )
        )
    )
).order_by("num_entries")

这就是我的模型:

class Title(models.Model):

    title_text = models.CharField(max_length=50, null=True)
    title_url = models.CharField(max_length=100, null=True)
    title_channels = models.ManyToManyField(TitleChannels)

    def __str__(self):
        return self.title_text


class Entry(models.Model):

    entry_title = models.ForeignKey(Title, on_delete=models.CASCADE)
    entry_text = models.CharField(max_length=2500, null=True, blank=True)
    entry_author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
    entry_date = models.CharField(max_length=20, null=True, blank=True)
    entry_points = models.IntegerField(default=0, null=True, blank=True)
    entry_readability = models.BooleanField(default=True)
    entry_date2 = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.entry_text

我没有按照我想要的顺序排序,而是按照标题entry_set的长度排序。

1 个答案:

答案 0 :(得分:1)

请注意,默认情况下order_by会从较低到较高排序,因此您的订单将是

Title three --> 2 entries in the last week
Title one   --> 5 entries in the last week
Title two   --> 8 entries in the last week

所以我认为你想按降序排序:.order_by("-num_entries")

我已经创建了一个小型演示,其中的数据非常类似于你的数据,以显示差异:

last_week = datetime.datetime.today() - datetime.timedelta(days=7)
count_last_week = Count(
    "entry",
    filter=Q(
        entry__entry_date2__gte=(
            django.utils.timezone.make_aware(last_week)
        )
    )
)
q = Title.objects.annotate(
    entries_last_week=count_last_week
).order_by(
    "entries_last_week"
)
for title_entry in q.all():
    print(
        "%s: %s entries last week (total entries: %s)" % (
            title_entry.title_text,
            title_entry.entries_last_week,
            Entry.objects.filter(entry_title=title_entry).count()
        )
    )
print('----')
q = Title.objects.annotate(
    entries_last_week=count_last_week
).order_by(
    "-entries_last_week"  # Important '-'
)
for title_entry in q.all():
    print(
        "%s: %s entries last week (total entries: %s)" % (
            title_entry.title_text,
            title_entry.entries_last_week,
            Entry.objects.filter(entry_title=title_entry).count()
        )
    )

我在print()中按标题添加了条目总数,以显示所有值(计数注释)都已正确计算。

Title three: 2 entries last week (total entries: 50)
Title one: 5 entries last week (total entries: 100)
Title two: 8 entries last week (total entries: 10)
----
Title two: 8 entries last week (total entries: 10)
Title one: 5 entries last week (total entries: 100)
Title three: 2 entries last week (total entries: 50)
根据OP的评论/ chat消息

编辑

将过滤器传递给Count()(或其他注释)的可能性为added in Django 2.0

在Django 1.11.X中你应该仍然可以使用子查询执行此操作:

import datetime
import django.utils.timezone
from django.db.models import OuterRef, Subquery, Count, IntegerField

last_week = django.utils.timezone.make_aware(
    datetime.datetime.today() - datetime.timedelta(days=7)
)
entries_last_week_sub_q = Entry.objects.filter(
    entry_date2__gte=last_week,
    entry_title=OuterRef('pk')  # The primary key of the Title objects
                                # where this subquery will be "embedded"
).values(
    'entry_title'
).annotate(
    cnt=Count('*')
).values('cnt')[:1]

q = Title.objects.annotate(
    entries_last_week=Subquery(entries_last_week_sub_q, output_field=IntegerField()),
).order_by(
    '-entries_last_week'
)
print("query %s" % q.query)
print('----')
print("Using subqueries:")
for title_entry in q.all():
    print("%s: %s entries last week" % (
        title_entry.title_text, title_entry.entries_last_week)
    )

在Django 2.0中测试过,不在1.11中...... : - (

灵感来自here,当然还有Django docs

而且,也许值得查看Title.entry_set的{​​{3}},但使用prefetch_related以便您可以通过提供来缩小要加载的Entry个对象的范围上周创建的条目查询集?像这样:

q = Title.objects.prefetch_related(
    Prefetch('entry_set', queryset=Entry.objects.filter(entry_date2__gte=last_week))
).annotate(
    entries_last_week=Count('entry_set),
).order_by(
    '-entries_last_week'
)

此方法(与上面几行子查询的示例相反)尚未经过测试。