Reputation: 2594
I am trying to implement Search in my list view for multiple attributes. I don't want to use multiple if-else for each attribute.
Here is my current code for search in list view:
def get_queryset(self):
city = self.request.GET.get('city_name') or ''
user = self.request.GET.get('user_name') or ''
if (city != '' or user!=''):
userqueries = user.split()
cityqueries = city.split()
if len(userqueries) and len(cityqueries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
qset2 = functools.reduce(operator.__or__, [Q(city__name__icontains=query) for query in cityqueries])
object_list = self.model.objects.filter(qset1 , qset2)
elif len(userqueries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
object_list = self.model.objects.filter(qset1)
elif len(cityqueries):
qset1 = functools.reduce(operator.__or__, [Q(city__name__icontains=query) for query in cityqueries])
object_list = self.model.objects.filter(qset1)
else:
object_list = self.model.objects.all()
return object_list
If I will add one attribute:
city = self.request.GET.get('city_name') or ''
user = self.request.GET.get('user_name') or ''
state = self.request.GET.get('state_name') or ''
if (city != '' or user!='' or state!=''):
userqueries = user.split()
cityqueries = city.split()
statequeries = state.split()
if len(userqueries) and len(cityqueries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
qset2 = functools.reduce(operator.__or__, [Q(city__name__icontains=query) for query in cityqueries])
object_list = self.model.objects.filter(qset1 , qset2)
elif len(userqueries) and len(statequeries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
qset2 = functools.reduce(operator.__or__, [Q(city__state__name__icontains=query) for query in statequeries])
object_list = self.model.objects.filter(qset1 , qset2)
elif len(userqueries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
object_list = self.model.objects.filter(qset1)
elif len(cityqueries):
qset1 = functools.reduce(operator.__or__, [Q(city__name__icontains=query) for query in cityqueries])
object_list = self.model.objects.filter(qset1)
elif len(statequeries):
qset1 = functools.reduce(operator.__or__, [Q(city__state__name__icontains=query) for query in statequeries])
object_list = self.model.objects.filter(qset1)
I want to merge all these condition in one:
if len(userqueries) and len(cityqueries):
elif len(userqueries):
elif len(cityqueries):
Upvotes: 2
Views: 162
Reputation: 477824
We probably better make a helper function that constructs a Q
object that is the disjunction of several elements, like:
from django.db.models import Q
from functools import reduce
from operator import or_
def q_or_otherwise_true(iterable, *keys):
iterable = list(iterable)
if iterable:
return reduce(or_, [Q(**{key: val}) for val in iterable for key in keys])
else:
return Q()
This thus generates Q
objects like:
>>> q_or_otherwise_true(['foo'], 'col1__icontains', 'col2__icontains')
<Q: (OR: ('col1__icontains', 'foo'), ('col2__icontains', 'foo'))>
>>> q_or_otherwise_true(['foo', 'bar'], 'col1__icontains', 'col2__icontains')
<Q: (OR: ('col1__icontains', 'foo'), ('col2__icontains', 'foo'), ('col1__icontains', 'bar'), ('col2__icontains', 'bar'))>
>>> q_or_otherwise_true([], 'col1__icontains', 'col2__icontains')
<Q: (AND: )>
then we can generate this like:
def get_queryset(self):
city = self.request.GET.get('city_name') or ''
user = self.request.GET.get('user_name') or ''
userqueries = user.split()
cityqueries = city.split()
return self.model.objects.filter(
q_or_otherwise_true(userqueries, 'first_name__icontains', 'last_name__icontains'),
q_or_otherwise_true(cityqueries, 'city__name__icontains'),
)
This works because or q_or_otherwise_true
makes a disjunction of elements, given the iterable
contains any elements. If not it constructs a Q()
object, which - in a .filter(..)
call - does not filter out anything. So that means we can thus make a conjucntion of these two.
The function can easily be extended to more calls, by simply making an extra q_or_otherwise_true
call.
Upvotes: 1