Search code examples
djangodjango-modelsdjango-ormdjango-q

Django queries: return a Q object that will translate to "AND (...)"


I am writing a simple search for our website, and I need to filter the entries on a number of criteria. However, if the strictest filtering is not satisfied, I want to return the results for a less strict search. To that end, I want to create a number of query objects, each with one more filter, then iterate them backwards trying to get a result, and return the first one I get this way.

What I am doing is:

q0 = MyIndex.objects.in_city(city_id)
q1 = q0.filter(name_contains(words))

where

def name_contains(words):
    if not words:
        return Q()
    query = Q(words_contains=words[0])
    for word in word[1:]:
        query = query | Q(words_contains=word)

When I pass the words that are not present in the database, I still get a result, because the query part returned by the name_contains is attached via OR and not AND.

Is there a way I can return that query so that it is always attached to the rest of the query by AND?


Solution

  • Unless your have a strange object manager, the in_city probably wraps to a .filter(..).

    If you chain multiple .filter(..)s, then you have implicitly written a logical and in between, since your first filter(..) virtually already removes the elements that do not satisfy the requirement, and your second filter only filters more.

    You can verify this suspicion, by printing the underlying query Django will perform with:

    print(str(q0.filter(name_contains(words)).query))

    Note that you can make your function more declarative like:

    from functools import reduce
    from operator import or_
    
    def name_contains(words):
        if words:
            return reduce(or_, (Q(word_contains=word) for word in words))
        else:
            return Q()