Django аннотирует поля с нулевыми значениями

У меня есть список Demand объектов, имеющих allocated поле, которое либо будет null, либо будет иметь имя (обозначающее выделение этого требования).

Я использую аннотации для подсчета выделенных/невыделенных номеров на команду:

Demand.objects.filter(project=project).values('team').annotate(
            unallocated=Count('allocated', filter=Q(allocated__isnull=True)),
            allocated=Count('allocated', filter=Q(allocated__isnull=False))
        )

Странно то, что числа для аннотации allocated выходят правильными, но числа для unallocated всегда равны нулю.

Например:

list(Demand.objects.filter(project=project).values('allocated', 'team'))

Со следующим результатом:

[{'allocated': None, 'team': 'Design'},
{'allocated': None, 'team': 'Engineering'},
{'allocated': None, 'team': 'Engineering'},
{'allocated': None, 'team': 'Engineering'},
{'allocated': None, 'team': 'Delivery'},
{'allocated': None, 'team': 'Product'}]

но в аннотации к нему есть только это:

<QuerySet 
[{'team': 'Delivery', 'unallocated': 0, 'allocated': 0},
{'team': 'Design', 'unallocated': 0, 'allocated': 0},
{'team': 'Engineering', 'unallocated': 0, 'allocated': 0},
{'team': 'Product', 'unallocated': 0, 'allocated': 0}]>

Я делаю это неправильно или это может быть ошибка?

Это потому, что Count(…) [Django-doc] не считает NULL, именно так SQL определяет работу агрегата COUNT: он не учитывает NULL значения (это также относится и к AVG, например). Но вы можете вместо этого считать первичный ключ, например:

from django.db.models import Count, Q

Demand.objects.filter(project=project).values('team').annotate(
    unallocated=Count('pk', filter=Q(allocated=None)),
    allocated=Count('allocated', filter=Q(allocated__isnull=False))
).order_by('team')

Также можно упростить allocated до:

from django.db.models import Count, Q

Demand.objects.filter(project=project).values('team').annotate(
    unallocated=Count('pk', filter=Q(allocated=None)),
    allocated=Count('allocated')
).order_by('team')
Вернуться на верх