基于动态属性()的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.
有没有什么办法可以做到这一点?我需要在数据库中创建另一个字段来存储全名吗?
4 个回答
我之前遇到过类似的问题,也在寻找解决办法。我认为使用搜索引擎是个不错的选择(比如用 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+'%'])
这个被接受的答案并不是完全正确。
在很多情况下,你可以在模型管理器里重写 get()
方法,从关键字参数中提取动态属性,然后把你想要查询的实际属性放进 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")
filter()
是在数据库层面上工作的(它实际上会生成 SQL 语句),所以你不能用它来处理任何基于你 Python 代码的查询 (你问题中的动态属性)
。
这是从很多其他回答中整理出来的一个答案 : )