Reputation: 4277
Suppose there is a structure like this:
PARTICIPATION_STATUSES = (
(0, 'No, thanks'),
(1, 'I may attend'),
(2, 'I\'ll be there'),
)
class Model1(models.Model):
# ...
class Model2(models.Model):
status = models.PositiveIntegerField(
_('participation status'), choices=PARTICIPATION_STATUSES)
field = models.ForeignKey(Model1, related_name='model1_participation')
What I want to do is to annotate each object of Model1
with count of Model2
objects where status equals a specific value (status number is this particular example).
In my pseudo code it would look like:
queryset = Model1.objects.all()
queryset.annotate(declined=Count('model1_participation__status=0'))
queryset.annotate(not_sure=Count('model1_participation__status=1'))
queryset.annotate(accepted=Count('model1_participation__status=2'))
But I can't annotate the queryset in this way as Django doesn't resolve status=<n>
.
What is the right way to achieve what I want?
Upvotes: 5
Views: 4730
Reputation: 1
You can use an Exists Subquery:
from django.db.models.expressions import Exists, ExpressionWrapper, OuterRef, Subquery, Value
from django.db.models.fields import BooleanField
queryset = Model1.objects.all()
queryset.annotate(
declined=ExpressionWrapper(
Exists(Model2.objects.filter(
field=OuterRef('id'),
status=0)),
output_field=BooleanField()))),
not_sure=ExpressionWrapper(
Exists(Model2.objects.filter(
field=OuterRef('id'),
status=1)),
output_field=BooleanField()))),
accepted=ExpressionWrapper(
Exists(Model2.objects.filter(
field=OuterRef('id'),
status=2)),
output_field=BooleanField())))
)
To make it a bit more clear/reusable you can refactor into a function:
def is_status(status_code):
return ExpressionWrapper(
Exists(Model2.objects.filter(
field=OuterRef('id'),
status=status_code)),
output_field=BooleanField())))
Model1.objects.annotate(
declined=is_status(0),
not_sure=is_status(1),
accepted=is_status(2)
)
Upvotes: 0
Reputation:
If you are using Django 1.8 or above you can use Conditional Aggregations, these should work for annotate
querysets.
from django.db.models import IntegerField, Case, When, Count
queryset = Model1.objects.all()
queryset = queryset.annotate(
declined=Count(
Case(When(model1_participation__status=0, then=1),
output_field=IntegerField())
),
not_sure=Count(
Case(When(model1_participation__status=1, then=1),
output_field=IntegerField())
),
accepted=Count(
Case(When(model1_participation__status=2, then=1),
output_field=IntegerField())
)
)
Upvotes: 7