It’s been a while, but I’m finally getting around to this.
I think Q.TRUE
and Q.FALSE
as class property aliases of ~Q(pk__in=[])
and Q(pk__in=[])
makes the most sense for now. It would be nice to just use Q.all()
and Q.none()
, but there would be confusion around Q.none()
not being the same as ~Q.any([])
.
It’s a bit weird that bool(Q.FALSE)
is True
, but that’s because only an empty Q()
evaluates to False
. ALWAYS/NEVER or EVERYTHING/NOTHING might avoid that, but it’s easier for me to think about the logical combinations using TRUE/FALSE.
I could add optimizations for simplifying combining logic, but the ORM handles that when creating the query anyway:
>>> print(get_user_model().objects.filter(Q(pk__in=[]) & Q(pk=7)).query)
...
django.core.exceptions.EmptyResultSet
>>> print(get_user_model().objects.filter(~Q(pk__in=[]) | Q(pk=7)).query)
SELECT "auth_user"."id", ... "auth_user"."date_joined" FROM "auth_user"
Q.any(iterable)
and Q.all(iterable)
are fairly simple. There’s a weird edge-case with iterables that only contain empty Q()
objects. Those iterables will also be considered “empty”. That way any
and all
NEVER return an empty Q()
object. For example, Q.any([Q(), Q()])
will still return Q(pk__in=[])
.
I can see someone eventually running into a bug where they’ve checked that their input iterable isn’t length 0 and can’t figure out why they’re getting Q(pk__in=[])
, but that’s better than accidentally exposing your entire table.
Edit: Actually, Q(Q())
can still cause problems. It evaluates to True
and bypasses the logic checking for empty Q()
objects. Simple to fix, but probably belongs in a separate ticket.
Edit2: And my Q(Q())
fix is revealing problems with deconstructing Q objects of query expressions. That might end up being it’s own ticket as well…