The bitwise OR operator in Python is often convenient when you want to combine multiple things into one thing. For example, with the Django ORM you might do this:
from django.db.models import Q
filter_ = Q(first_name__icontains="peter") | Q(first_name__icontains="ashley")
for contact in Contact.objects.filter(filter_):
print((contact.first_name, contact.last_name))
See how it hardcodes the filtering on strings peter
and ashley
.
But what if that was a bit more complicated:
from django.db.models import Q
filter_ = Q(first_name__icontains="peter")
if include("ashley"):
filter_ | = Q(first_name__icontains="ashley")
for contact in Contact.objects.filter(filter_):
print((contact.first_name, contact.last_name))
So far, same functionality.
But what if the business logic is more complicated? You can't do this:
filter_ = None
if include("peter"):
filter_ | = Q(first_name__icontains="peter")
if include("ashley"):
filter_ | = Q(first_name__icontains="ashley")
for contact in Contact.objects.filter(filter_):
print((contact.first_name, contact.last_name))
What if the list of things you want to filter on depends on a list? You'd need to do the |=
stuff "dynamically". One way to solve that is with functools.reduce
. Suppose the list of things you want to bitwise-OR together is a list:
from django.db.models import Q
from operator import or_
from functools import reduce
def include(_):
import random
return random.random() > 0.5
filters = []
if include("peter"):
filters.append(Q(first_name__icontains="peter"))
if include("ashley"):
filters.append(Q(first_name__icontains="ashley"))
assert len(filters), "must have at least one filter"
filter_ = reduce(or_, filters)
for contact in Contact.objects.filter(filter_):
print((contact.first_name, contact.last_name))
And finally, if it's a list already:
from django.db.models import Q
from operator import or_
from functools import reduce
names = ["peter", "ashley"]
qs = [Q(first_name__icontains=x) for x in names]
filter_ = reduce(or_, qs)
for contact in Contact.objects.filter(filter_):
print((contact.first_name, contact.last_name))
Side note
Django's django.db.models.Q
is actually quite flexible with used with MyModel.objects.filter(...)
because this actually works:
from django.db.models import Q
def include(_):
import random
return random.random() > 0.5
filter_ = Q()
if include("peter"):
filter_ |= Q(first_name__icontains="peter")
if include("ashley"):
filter_ |= Q(first_name__icontains="ashley")
for contact in Contact.objects.filter(filter_):
print((contact.first_name, contact.last_name))