2

I have a list of strings:

phrases_filter = json.loads(request.GET['phrases_filter'])
['xx', 'yy']

I need to lookup all phrases in db, that contain xx OR yy

I tried it like this:

Phrase.objects.filter(name__in phrases_filter)

But this gave me only phrases that have in name xx AND yy

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
user2950593
  • 9,233
  • 15
  • 67
  • 131

1 Answers1

2

We can unroll the list, and define a set of __contains predicate with or-logic:

from django.db.models import Q
from functools import reduce
from operator import or_

Phrase.objects.filter(
    reduce(or_, (Q(name__contains=e) for e in phrases_filter))
)

In the case of the given phrases_filter, we will generate something equivalent to:

Phrase.objects.filter(
    Q(name__contains='xx') | Q(name__contains='yy')
)

So reduce(..) will operate on a generator of Q(..) objects, here [Q(name__contains='xx'), Q(name__contains='yy')]. The reduce(..) works like what is known in functional programming as a "fold" (which is a special case of a catamorphism).

We thus will add or_s in between, resulting in Q(name__contains='xx') | Q(name__contains='yy'). This is an object that basically encodes a filter condition: it means that the name column should __contains the substring 'xx', or the substring 'yy'.

Note: in case you want to match case insensitive (so rows with XX, or Xx, or xX, or xx are all candidates), then replace __contains with __icontains.

EDIT: based on your comment, in case phrases_filter is empty, you want all rows to match, we can do this by using an if statement:

from django.db.models import Q
from functools import reduce
from operator import or_

queryset = Phrase.objects.all()
if phrases_filter:
    queryset = filter(
        reduce(or_, (Q(name__contains=e) for e in phrases_filter))
    )

So only in case phrases_filter contains at least on element, we perform a filtering.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555