Q and operator.or_
I’ve finally settled on a nice syntax for OR-ing Django Q objects.
For a simple site search feature I needed to search for a term across several fields in a model. Suppose the model looks like this:
class BlogPost(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
summary = models.TextField()
And you have a view method that accepts a parameter q for searching across
the title, body and summary fields. I want to find objects that contain
the q phrase in any of those fields. I need to build a QuerySet
with a filter that is the equivalent of
queryset = BlogPost.objects.filter(
Q(title__icontains=q) | Q(body__icontains=q) | Q(summary__icontains=q)
)
That’s not too much of a hassle for this simple example, but in cases where the fields you are searching are chosen dynamically, or where you just have an awful lot of fields to search against, I think it is nicer to do it like so:
import operator
search_fields = ('title', 'body', 'summary')
q_objects = [Q(**{field + '__icontains':q}) for field in search_fields]
queryset = BlogPost.objects.filter(reduce(operator.or_, q_objects))
Nice one! The list comprehension gives me a list of Q objects generated from
the names in search_fields, so it is easy to change the fields to be searched.
And using reduce and operator.or_ gives me the
required OR filter in one line.
I see for Python 3 reduce has been moved to the functools module.
This stuff never used to be that obvious to me. It kind of isn’t even now.
P.S. I promise I am not writing a blog engine at this time, it was just for the example.