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.