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 № 1Eu 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()
""")
)