Django query annotation with boolean field

As from Django 1.11 it is possible to use Exists. Example below comes from Exists documentation:

>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
...     post=OuterRef('pk'),
...     created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))

Read the docs about extra

qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })

Tested it works

But order_by or where(filter) by this field doesn't for me (Django 1.8) 0o:

If you need to order the resulting queryset using some of the new fields or tables you have included via extra() use the order_by parameter to extra() and pass in a sequence of strings. These strings should either be model fields (as in the normal order_by() method on querysets), of the form table_name.column_name or an alias for a column that you specified in the select parameter to extra().

qs = qs.extra(order_by = ['-has_images'])

qs = qs.extra(where = ['has_images=1'])

FieldError: Cannot resolve keyword 'has_images' into field.

I have found https://code.djangoproject.com/ticket/19434 still opened.

So if you have such troubles like me, you can use raw



I eventually found a way to do this using django 1.8's new conditional expressions:

from django.db.models import Case, When, Value, IntegerField
q = (
    Product.objects
           .filter(...)
           .annotate(image_count=Count('images'))
           .annotate(
               have_images=Case(
                   When(image_count__gt=0,
                        then=Value(1)),
                   default=Value(0),
                   output_field=IntegerField()))
           .order_by('-have_images')
)

And that's how I finally found incentive to upgrade to 1.8 from 1.7.


Use conditional expressions and cast outputfield to BooleanField

Product.objects.annotate(image_count=Count('images')).annotate(has_image=Case(When(image_count=0, then=Value(False)), default=Value(True), output_field=BooleanField())).order_by('-has_image')