Применить поиск по нескольким атрибутам в Django - PullRequest
0 голосов
/ 14 октября 2018

Я пытаюсь реализовать Поиск в моем списке для нескольких атрибутов.Я не хочу использовать несколько if-else для каждого атрибута.

Вот мой текущий код для поиска в представлении списка:

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

Если я добавлю один атрибут:

    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)

Я хочу объединить все эти условия в одно:

        if len(userqueries) and len(cityqueries):

        elif len(userqueries):

        elif len(cityqueries):

1 Ответ

0 голосов
/ 14 октября 2018

Вероятно, нам лучше создать вспомогательную функцию, которая создает объект Q, который является дизъюнкцией нескольких элементов, например:

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()

Таким образом, генерируется Q объектов, таких как:

>>> 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: )>

тогда мы можем сгенерировать это следующим образом:

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'),
    )

Это работает, потому что или q_or_otherwise_true создает дизъюнкцию элементов, поскольку iterable содержит какие-либо элементы.Если нет, он создает объект Q(), который - при вызове .filter(..) - ничего не отфильтровывает.Таким образом, это означает, что мы можем таким образом сделать соединение этих двух.

Функция может быть легко расширена для большего количества вызовов, просто делая дополнительный вызов q_or_otherwise_true.

...