Q()
is often used as a starting value for building complex lookups with Q objects. It is an “empty” operation that is dropped from any expression, leading to some confusion. To make usage more explicit, I’d like to add the following special values:
- An “always satisfied” condition:
Q(True)
- An “always unsatisfied” condition:
Q(False)
- An “empty” operation that defaults to no results:
Q(None)
Q(False)
Q(False)
is useful as a more readable version of Q(pk__in=[])
. A common mistake is the following:
def find_books(pseudonyms):
books_filter = Q()
for name in pseudonyms:
books_filter |= Q(author=name)
return Book.objects.filter(books_filter)
This result is similar to Book.objects.filter(author__in=pseudonyms)
, but ALL books are returned when pseudonyms
is empty.
To address this, you should use Q(pk__in=[])
as a starting value. The query optimizer recognizes that this condition is always unsatisfied and handles it nicely, even for complex queries. Many people use Q(pk=None)
or Q(pk__isnull=True)
, which aren’t handled as nicely (perhaps because pk
can be overridden).
Q(True)
and Q(None)
Uses for Q(True)
and Q(None)
are less obvious. Consider the following code:
def search_books(conditions, favourite_author=None):
q = STARTING_VALUE # either Q(), Q(True), or Q(None)
for condition in conditions:
q &= condition
if favourite_author is None:
return Book.objects.filter(q)
return Book.objects.filter(Q(author=favourite_author) | q)
When conditions
is populated, the behaviour is always the same. The function searches for books that either match all the conditions OR are written by the (optional) favourite author. The starting value matters when conditions
is empty:
STARTING_VALUE | favourite_author | result |
---|---|---|
Q() |
None |
All books |
Q() |
"Tolkien" |
Books by Tolkien |
Q(True) |
None |
All books |
Q(True) |
"Tolkien" |
All books |
Q(None) |
None |
Nothing |
Q(None) |
"Tolkien" |
Books by Tolkien |
Operations Tables
The special values and their operations can be defined at the Q object level, meaning expressions can be optimized during evaluation. This has a slight advantage over Q(id__in=[])
, which is optimized-away when building the query.
Currently ~Q()
and Q(Q())
are internally represented differently than Q()
, but I don’t think it would ever make a difference.
AND “&
”
&Q() |
&Q(True) |
&Q(False) |
&Q(None) |
&Q(**k) |
|
---|---|---|---|---|---|
Q() |
=Q() |
=Q(True) |
=Q(False) |
=Q(None) |
=Q(**k) |
Q(True) |
=Q(True) |
=Q(True) |
=Q(False) |
=Q(True) |
=Q(**k) |
Q(False) |
=Q(False) |
=Q(False) |
=Q(False) |
=Q(False) |
=Q(False) |
Q(None) |
=Q(None) |
=Q(True) |
=Q(False) |
=Q(None) |
=Q(**k) |
Q(**k) |
=Q(**k) |
=Q(**k) |
=Q(False) |
=Q(**k) |
NA |
OR “|
”
|Q() |
|Q(True) |
|Q(False) |
|Q(None) |
|Q(**k) |
|
---|---|---|---|---|---|
Q() |
=Q() |
=Q(True) |
=Q(False) |
=Q(None) |
=Q(**k) |
Q(True) |
=Q(True) |
=Q(True) |
=Q(True) |
=Q(True) |
=Q(True) |
Q(False) |
=Q(False) |
=Q(True) |
=Q(False) |
=Q(False) |
=Q(**k) |
Q(None) |
=Q(None) |
=Q(True) |
=Q(False) |
=Q(None) |
=Q(**k) |
Q(**k) |
=Q(**k) |
=Q(True) |
=Q(**k) |
=Q(**k) |
NA |
Negation “~
”
Q() |
Q(True) |
Q(False) |
Q(None) |
|
---|---|---|---|---|
~ |
=Q() |
=Q(False) |
=Q(True) |
=Q(None) |
Concerns with Q(None)
I am a bit worried that Q(None)
will lead to more mistakes than it prevents.
Q(False)
is a really nice way to ensure at least one OR condition exists:
def or_group(conditions):
q = Q(False)
for condition in conditions:
q &= condition
return q
Book.objects.filter(or_group([])) # empty
Book.objects.filter(or_group([]) & Q(author="Tolkien") # empty
It’s important to note that Q(None)
does not always do the same for AND conditions:
# incorrect
def and_group1(conditions):
q = Q(None)
for condition in conditions:
q &= condition
return q
Book.objects.filter(and_group1([])) # empty
Book.objects.filter(and_group1([]) & Q(author="Tolkien") # not empty
# correct
def and_group2(conditions):
q = Q()
for condition in conditions:
q &= condition
return q | Q(False)
Book.objects.filter(and_group2([])) # empty
Book.objects.filter(and_group2([]) & Q(author="Tolkien") # empty
The main benefit of Q(None)
is that you can use wherever you’d use Q()
unless you explicitly want the Book.objects.filter(Q())
behaviour.