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 , 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']

До версии вы можете работать с 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 и т.д.

Вернуться на верх