What I am trying to do:
I have models Topic
and Entry
. Entry
has a ForeignKey to topic. I need to list topics on condition that the user has entries in it (created in last 24 hours). I also need to annotate count, it needs to be the total number of entries created after the last entry written by the user. (To put it more thoroughly, you can think of an inbox where you have list of conversations with number of unread messages.)
This's what I have come up with:
relevant_topics = (
Entry.objects.filter(author=user, date_created__gte=time_threshold(hours=24))
.values_list("topic__pk", flat=True)
.order_by()
.distinct()
)
qs = (
Topic.objects.filter(pk__in=relevant_topics).annotate(
latest=Max("entries__date_created", filter=Q(entries__author=user)),
count=Count("entries", filter=Q(date_created__gte=F("latest__date_created"))),
)
).values("title", "count")
Which will throw:
FieldError: Cannot resolve keyword 'date_created' into field. Join on 'latest' not permitted.
I don't really know if Django itself doesn't support what I've written, or my solution is faulty. I thought adding count using .extra(), but I couldn't figure out how to use latest
annotation there. I would really appreciate any query that produces the expected output.
Reference data set:
(assume the current user = Jack)
<User username: Jack>
<User username: John>
<Topic title: foo>
<Topic title: bar>
<Topic title: baz>
(Assume higher pk = created later.)
<Entry pk:1 topic:foo user:Jack>
<Entry pk:2 topic:foo user:Jack> (date_created in last 24 hours)
<Entry pk:3 topic:foo user:John> (date_created in last 24 hours)
<Entry pk:4 topic:bar user:Jack> (date_created in last 24 hours)
<Entry pk:5 topic:baz user:John> (date_created in last 24 hours)
Given the dataset, the output should only be:
<Topic:foo count:1>
EDIT:
To give you an idea, here is a raw SQL solution which produces correct output:
pk = user.pk
threshold = time_threshold(hours=24)
with connection.cursor() as cursor:
cursor.execute(
"""
select
s.title,
s.slug,
s.count
from
(
select
tt.title,
tt.slug,
e.count,
e.max_id
from
(
select
z.topic_id,
count(
case when z.id > k.max_id then z.id end
) as count,
k.max_id
from
dictionary_entry z
inner join (
select
topic_id,
max(de.id) as max_id
from
dictionary_entry de
where
de.date_created >= %s
and de.author_id = %s
group by
author_id,
topic_id
) k on k.topic_id = z.topic_id
group by
z.topic_id,
k.max_id
) e
inner join dictionary_topic tt on tt.id = e.topic_id
) s
where
s.count > 0
order by
s.max_id desc
""",
[threshold, pk],
)
# convert to dict
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]