Doug Dresser
Doug Dresser

Reputation: 154

Conditionally Filter Within Django Queryset

I'm wondering if there is a way to conditionally filter a queryset in Django based on if the value filtered against is None or not. I know that you can do something like this:

some_name = 'Bob'
qs = MyModel.objects.filter(gender='boy')
if some_name:
    qs = qs.filter(name=some_name)

where we conditionally filter on the variable some_name only if it exists. I'm curious if this logic can be replicated with a single query statement, instead of "chaining" on a filter to the end of a queryset. So this would look something like:

qs = MyModel.objects.filter(gender='boy').filter(name=some_name if some_name else None)

Obviously that example isn't valid, because it would filter on name=None instead of not filtering, but has anyone found a way to do what I'm intending? For longer, nested queries, it would be very helpful to have it all in one statement.

Upvotes: 1

Views: 335

Answers (1)

Henry Woody
Henry Woody

Reputation: 15652

I don't believe there is a built-in way to do this in Django specifically, but you can make use of the dictionary unpack operator ** and a dictionary comprehension to achieve the desired result.

For example:

some_name = 'Bob'
field_value_pairs = [('gender', 'boy'), ('name', some_name)]
filter_options = {k:v for k,v in field_value_pairs if v}
qs = MyModel.objects.filter(**filter_options)

So if some_name is None, then the 'name' field will be excluded from filter_options and therefore not be included in the filter query.


Update:

You can also use the Q object to build a query that can be passed to a single call of the filter method. This allows for more flexibility and allows for more complex filtering similar to the SQL WHERE clause.

For example:

from django.db.models import Q

some_name = 'Bob'
query = Q(gender='boy')
if some_name:
    query = query & Q(name=some_name)
qs = MyModel.objects.filter(query)

Or if you want a more compact view for simple cases like this:

query = Q(gender='boy') & (Q(name=some_name) if some_name else Q())

or (closer to your desired look):

qs = MyModel.objects.filter(Q(gender='boy') & (Q(name=some_name) if some_name else Q()))

Note that using separate filter calls isn't really bad since only one database query will be made (assuming you don't try to reference the objects in the query set before the last filter call). You can check this by looking at the database queries Django is making. But as a matter of style, I can see the preference for only calling filter once.

Upvotes: 3

Related Questions