/ / Como anotar contagem filtrada em datetime? - django, django-annotate

Como anotar contagem filtrada em datetime? - django, django-annotate

Eu tenho categorias e itens. Os itens têm um campo final (datetime). Agora eu preciso listar todas as categorias e exibir a contagem de itens relacionados e a contagem de itens de itens no futuro. Como exemplo:

  • Cat Foo, 2 itens, 1 no futuro.
  • Cat n, n itens n no futuro.

A lista será grande. Assim, o banco de dados tem que fazer o trabalho pesado e anotar ambos item_count e future_item_count.

Modelos:

from django.db import models

class Cat(models.Model):
title = models.CharField(max_length=200)

class Item(models.Model):
cat = models.ForeignKey(Cat)
title = models.CharField(max_length=200)
end = models.DateTimeField()

Crie uma categoria e dois itens relacionados. Um no passado, um no futuro:

from datetime import timedelta
from django.utils import timezone

cat = Cat(title="Cat 1")
cat.save()

item_1 = Item(cat=cat, title="Item 1", end=timezone.now() - timedelta(days=1))
item_1.save()

item_2 = Item(cat=cat, title="Item 2", end=timezone.now() + timedelta(days=1))
item_2.save()

Quando anoto item_count, ele funciona como esperado:

from django.db.models import Count

Cat.objects.all().annotate(
item_count=Count("item")).values("title", "item_count")
# [{"item_count": 2, "title": u"Cat 1"}]

Eu não posso anotar filtrado por Item.end (datetime). Isso é possível com as consultas do Django?

Cat.objects.all().annotate(
item_count=Count("item"),
future_item_count=Count("item").filter(
end__gt=timezone.now())
).values(
"title",
"item_count",
"future_item_count"
)
# AttributeError: "Count" object has no attribute "filter"

Espero receber: [{"item_count": 2, "future_item_count": 1, "title": u"Cat 1"}]

Eu também tentei o RawSQL mas falta habilidades SQL:

from django.db.models.expressions import RawSQL

Cat.objects.all().annotate(
item_count=Count("item"),
future_item_count=RawSQL(
"""SELECT COUNT(*)
FROM project_item
JOIN project_item
AS foo
ON foo.cat_id = project_cat.id
WHERE project_item.end < NOW()""",
""
)).values(
"title",
"item_count",
"future_item_count"
)
# [{"item_count": 2, "future_item_count": 2L, "title": u"Cat 1"}]

Mas quando eu mudo WHERE project_item.end < NOW()" dentro WHERE project_item.end > NOW()" Eu obtenho o mesmo resultado:

[{"item_count": 2, "future_item_count": 2L, "title": u"Cat 1"}]

Como formatar o SQL bruto? Ou isso pode ser feito com as consultas do Django?

Respostas:

1 para resposta № 1

Eu pessoalmente não usei RawSQL (ainda faz coisas com .extra), mas eu acho que você não precisa JOIN project_item na tua RawSQL declaração. Apenas tente com:

RawSQL("""SELECT COUNT(*)
FROM project_item
WHERE
project_item.cat_id = project_cat.id
AND project_item.end < NOW()
""")

E mais uma coisa eu acho que você não deveria usar .values DEPOIS DE .annotate, mas ANTES a anotação. Então, o seu QuerySet completo deve ficar assim:

Cat.objects.values("title")
.annotate(
item_count=Count("item"),
future_item_count=RawSQL("""
SELECT COUNT(*)
FROM project_item
WHERE
project_item.cat_id = project_cat.id
AND project_item.end < NOW()
""")
)