Reputation: 8995
So I want to find any kind of matching given some fields, so for example, this is what I would like to do:
possible_merchants = ["amazon", "web", "services"]
# Possible name --> "Amazon Service"
Companies.objects.filter(name__icontains__in=possible_merchants)
sadly it is not possible to mix icontains and the __in lookup.
It seems to be a pretty complex query so if at least I could ignore case the name that would be enough, for example:
Companies.objects.filter(name__ignorecase__in=possible_merchants)
Any ideas?
P.D.: The queries I posted don't work, it's just a way to express what I need (just in case heh)
Upvotes: 30
Views: 34872
Reputation: 3987
using iregex would be:
regex = '|'.join(['match 1', 'match 2', 'match 3'])
Companies.objects.filter(name__iregex=f'({regex})')
You can even add lookaround so that matches are guaranteed to be individual words, rather than parts of other words with different meaning ie:
options = ['match 1', 'match 2', 'match 3']
regex_options = [f'(?<!\w){option}(?!\w)' for option in options] # Adds lookaround
regex = '|'.join(regex_options)
Companies.objects.filter(name__iregex=f'({regex})')
lookaround (ie lookbehind + lookahead) will not incur in character consumption therefore it'll match with substrings starting or ending strings, that would't be possible with [^\w].
Upvotes: 0
Reputation: 83
The answer from Gareth Rees helped me a lot with a similar issue while using the django-filter package.
To use this with django_filters
in generic way one could create a MultipleInputs filter like this:
class MultipleInputs(filters.BaseInFilter, filters.CharFilter):
pass
Then use that in the filterset and use a custom filtering method:
from django.db.models import Q
class MyFilter(FilterSet):
search = MultipleInputs(field_name='longname', label='Text search',
method='multiplesearch',
help_text="Free text search. May be a comma separated list of strings.")
def multiplesearch(self, queryset, field_name, value):
q = Q()
for searchstring in value:
arguments = {field_name+'__icontains' : searchstring}
q |= Q(**arguments)
return queryset.filter(q)
No MultiInputs fields can have comma separated inputs. Also works if you inherit MyFilter but overwrite search with a Filter with a different field_name.
Upvotes: 0
Reputation: 1
Another approach would be to simulate the actions that Django normally does for iexact
queries (it converts both parts of the comparison statement to the upper case via SQL Upper function.
This way, the query will look like this:
Companies.objects.annotate(
upper_name=models.Upper("name")
).filter(
upper_name__in=[rchant.upper() for merchant in possible_merchants]
)
Upvotes: 0
Reputation: 1719
This is the approach that I adopted:
class MyManager(models.Manager):
def exclusive_in(self, lookup, value_list):
return self.filter(reduce(or_, (Q(**{lookup:_}) for _ in value_list)))
Here is now to use it:
Companies.objects.exclusive_in('name__icontains', possible_merchants])
It was inspired by other answers in this thread, as well as Django filter queryset __in for *every* item in list.
Upvotes: 4
Reputation: 1421
I find it a cleaner approach using reduce
and or_
operator:
from django.db.models import Q
from functools import reduce
from operator import or_
def get_companies_from_merchants(merchant_list):
q_object = reduce(or_, (Q(name__icontains=merchant) for merchant in merchant_list))
return Companies.objects.filter(q_object)
This would create a list of Q
objects querying the name
to contain a single element in merchant list. This would happpen for all the elements in merchant_list
and all these Q
objects would be reduced to a single Q
object having mutliple ORs which can be directly applied to the filter query.
Upvotes: 5
Reputation: 65884
You can create querysets with the Q
constructor and combine them with the |
operator to get their union:
from django.db.models import Q
def companies_matching(merchants):
"""
Return a queryset for companies whose names contain case-insensitive
matches for any of the `merchants`.
"""
q = Q()
for merchant in merchants:
q |= Q(name__icontains = merchant)
return Companies.objects.filter(q)
(And similarly with iexact
instead of icontains
.)
Upvotes: 55