запрос django, основанный на динамическом свойстве () - PullRequest
11 голосов
/ 21 января 2011

Мне было интересно, есть ли способ использовать фильтр Django () для наборов запросов, используя динамически генерируемое свойство python, используя property().У меня есть first_name и last_name каждого пользователя, и я хочу фильтровать по их объединенному имени first_name last_name.(Причиной этого является то, что когда я выполняю автозаполнение, я ищу, чтобы найти, соответствует ли запрос имени, фамилии или части конкатенации. Я хочу, чтобы John S соответствовало John Smith, например.

Я создал свойство name:

def _get_name(self):
    return self.first_name + " " + self.last_name
    name = property(_get_name)

Таким образом, я могу позвонить user.name, чтобы получить объединенное имя.

Однако, если я попытаюсь сделать User.objects.filter(name__istartswith=query) Iполучить сообщение об ошибке Cannot resolve keyword 'name' into field.

Есть идеи, как это сделать? Нужно ли создавать еще одно поле в базе данных для хранения полного имени?

Ответы [ 4 ]

13 голосов
/ 27 сентября 2013

Принятый ответ не совсем верен.

Во многих случаях вы можете переопределить get() в менеджере моделей на pop динамических свойствах из аргументов ключевого слова, а затем добавить фактические атрибуты, которые вы хотите запросить, в словарь аргументов kwargs ключевого слова. Обязательно верните super, поэтому любые обычные get() вызовы возвращают ожидаемый результат.

Я только вставляю свое собственное решение, но для __startswith и других условных запросов вы можете добавить некоторую логику к split двойному подчеркиванию и обработать соответствующим образом.

Вот мой обходной путь, позволяющий запрашивать динамическое свойство:

class BorrowerManager(models.Manager):
    def get(self, *args, **kwargs):
        full_name = kwargs.pop('full_name', None)
        # Override #1) Query by dynamic property 'full_name'
        if full_name:
            names = full_name_to_dict(full_name)
            kwargs = dict(kwargs.items() + names.items())
        return super(BorrowerManager, self).get(*args, **kwargs)

В models.py:

class Borrower(models.Model):
    objects = BorrowerManager()

    first_name = models.CharField(null=False, max_length=30)
    middle_name = models.CharField(null=True, max_length=30)
    last_name = models.CharField(null=False, max_length=30)
    created = models.DateField(auto_now_add=True)

В utils.py (для контекста):

def full_name_to_dict(full_name):
    ret = dict()
    values = full_name.split(' ')
    if len(values) == 1:
        raise NotImplementedError("Not enough names to unpack from full_name")
    elif len(values) == 2:
        ret['first_name'] = values[0]
        ret['middle_name'] = None
        ret['last_name'] = values[1]
        return ret
    elif len(values) >= 3:
        ret['first_name'] = values[0]
        ret['middle_name'] = values[1:len(values)-1]
        ret['last_name'] = values[len(values)-1]
        return ret
    raise NotImplementedError("Error unpacking full_name to first, middle, last names")
9 голосов
/ 21 января 2011

filter() работает на уровне базы данных (на самом деле он пишет SQL), поэтому его нельзя будет использовать для любых запросов, основанных на вашем коде Python (dynamic property in your question).

Это ответ, составленный из множества других ответов в этом отделе:)

3 голосов
/ 06 декабря 2015

У меня была похожая проблема, и я искал решение.Полагая, что поисковая система будет лучшим вариантом (например, django-haystack с Elasticsearch), вот как я реализовал бы некоторый код для ваших нужд, используя только Django ORM (вы можете заменить icontains на istartswith):

from django.db.models import Value
from django.db.models.functions import Concat

queryset = User.objects.annotate(full_name=Concat('first_name', Value(' '), 'last_name')
return queryset.filter(full_name__icontains=value)

В моем случае я не знал, вставит ли пользователь 'first_name last_name' или наоборот, поэтому я использовал следующий код.

from django.db.models import Q, Value
from django.db.models.functions import Concat

queryset = User.objects.annotate(first_last=Concat('first_name', Value(' '), 'last_name'), last_first=Concat('last_name', Value(' '), 'first_name'))
return queryset.filter(Q(first_last__icontains=value) | Q(last_first__icontains=value))

При Django <1.8 вам, вероятно, придется прибегнуть к <code>extra с функцией SQL CONCAT, что-то вроде следующего:

queryset.extra(where=['UPPER(CONCAT("auth_user"."last_name", \' \', "auth_user"."first_name")) LIKE UPPER(%s) OR UPPER(CONCAT("auth_user"."first_name", \' \', "auth_user"."last_name")) LIKE UPPER(%s)'], params=['%'+value+'%', '%'+value+'%'])
0 голосов
/ 21 января 2011

Думаю, что в django невозможно отфильтровать свойства, которые не представлены в виде базы данных, но то, что вы можете сделать, чтобы сделать крутой поиск автозаполнения, выглядит примерно так:

if ' ' in query:
    query = query.split()
    search_results = list(chain(User.objects.filter(first_name__icontains=query[0],last_name__icontains=query[1]),
                                    User.objects.filter(first_name__icontains=query[1],last_name__icontains=query[0])))
else:
    search_results =  User.objects.filter(Q(first_name__icontains=query)| Q(last_name__icontains=query))

Этот код даетПользователь вашей системы может начать вводить имя или фамилию, и пользователь будет благодарен вам за это.

...