Theo
Theo

Reputation: 495

Django query an unknown number of multiple date ranges

I have a form which allows a user to select users by multiple age ranges: 18-24, 25-34, 35-44 etc. Currently if a user selects the first two fields in the multiple selection list, the query will return users 18 - 34. If a user selects 18 - 24 year olds, and 35-44 year olds, it will include all ages from 18 - 44, when it should not include users aged 25-34.

How would you approach including multiple range filters on a query without knowing the number of ranges you need to filter by prior to the users form selections?

If you knew the number of age ranges the user was going to filter by I know you could chain range queries using Q.

This is my current query:

query = User.objects.filter(
            gender__in=genders,
            created__in=dates,
            data_source__in=sources,
            dob__range=[dob_from, dob_to]
        )

Thanks in advance.

Upvotes: 1

Views: 1410

Answers (1)

Alasdair
Alasdair

Reputation: 309069

As you say in your question, you can use Q() objects and combine them together. It's not a problem that you don't know how many Q() objects there will be. Generate a list of the Q() objects, then reduce the list to a single Q().

from operator import or_

# import for Python 3, not required in Python 2
from functools import reduce

ranges = [(18,24), (35, 44)]  # hardcoded as an example, you can dynamically generate this
qs = [Q(dob_range=[dob_from_, dob_to]) for (dob_from_, dob_to) in ranges]
# combine the qs
dob_q = reduce(or_, qs, Q())

query = User.objects.filter(
    dob_q,
    gender__in=genders,
    created__in=dates,
    data_source__in=sources,
)

If you are unfamiliar with reduce and or_, you can get the same result by looping through the list:

qs = [Q(dob_range=[dob_from_, dob_to]) for (dob_from_, dob_to) in ranges]
dob_q = Q()
for q in qs:
    dob_q = dob_q | q

Upvotes: 7

Related Questions