Django - Аннотирование с помощью Case/When/Value и связанных объектов
У меня есть две следующие модели:
class Post(models.Model):
content = models.TextField()
class Vote(models.Model):
UP_VOTE = 0
DOWN_VOTE = 1
VOTE_TYPES = (
(UP_VOTE, "Up vote"),
(DOWN_VOTE, "Down vote"),
)
post = models.ForeignKey(Post, related_name="votes")
vote_type = models.PositiveSmallIntegerField(choices=VOTE_TYPES)
Я хотел бы иметь свойство score на Post, которое возвращает сумму значений голосов к этому посту, считая голоса с типом UP_VOTE как 1, а голоса с типом DOWN_VOTE как -1.
Вот что я пробовал:
# inside Post
@property
def score(self):
return (
self.votes.all()
.annotate(
value=Case(
When(vote_type=Vote.DOWN_VOTE, then=Value(-1)),
When(vote_type=Vote.UP_VOTE, then=Value(1)),
default=Value("0"),
output_field=models.SmallIntegerField(),
)
)
.aggregate(Sum("value"))["value__sum"]
)
Однако это дает результат None
. Более конкретно, без разыменования ["value__sum"]
это возвращает {'value__sum': None}
.
Является ли использование Case-When-Value правильным подходом к моему случаю использования? Если да, то что не так с кодом, который я разместил? Заранее спасибо.
The sum of an empty set will be NULL
/None
by default. As of django-4.0, you can work with the default=…
parameter [Django-doc]:
from django.db.models import F, Sum
@property
def score(self):
return self.votes.aggregate(total=Sum(-2*F('vote_type') + 1, default=0))['total']
До версии django-4.0 вы можете работать с Coalesce
[Django-doc]:
from django.db.models import F, Sum, Value
from django.db.models.functions import Coalesce
@property
def score(self):
return self.votes.aggregate(
total=Coalesce(Sum(-2*F('vote_type') + 1), Value(0))
)['total']
хотя в этом простом случае вы можете просто заменить None
на 0
на уровне Django/Python:
from django.db.models import F, Sum
@property
def score(self):
return self.votes.aggregate(total=Sum(-2*F('vote_type') + 1))['total'] or 0
Возможно, лучше использовать "оценку" голоса в качестве значения, так:
class Vote(models.Model):
UP_VOTE = 1
DOWN_VOTE = -1
# …
vote_type = models.SmallIntegerField(choices=VOTE_TYPES)
Это упростит логику агрегирования и позволит впоследствии разрешить, например, голосование +5, -10 и т.д.