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))
Этот код дает пользователю вашей системы гибкость, чтобы начать вводить имя или фамилию, и пользователь будет благодарен вам за это.