Django-запрос на основе динамического свойства()

Мне было интересно, можно ли использовать Django filter() для наборов запросов, используя динамически генерируемое свойство 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), я получаю ошибку Cannot resolve keyword 'name' into field.

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

Ответ 1

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

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

Ответ 2

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

Во многих случаях вы можете переопределить 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")

Ответ 3

У меня была аналогичная проблема и я искал решение. Принимая во внимание, что поисковая система была бы наилучшим вариантом (например, 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 вам, вероятно, придется обратиться к 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+'%'])

Ответ 4

Подумайте, что в 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))

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