基于动态属性()的django查询

14 投票
4 回答
11936 浏览
提问于 2025-04-16 10:21

我在想有没有办法在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 个回答

4

我之前遇到过类似的问题,也在寻找解决办法。我认为使用搜索引擎是个不错的选择(比如用 django-haystackElasticsearch),不过我这里给你展示一下如何仅用 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+'%'])
13

这个被接受的答案并不是完全正确。

在很多情况下,你可以在模型管理器里重写 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")
10

filter() 是在数据库层面上工作的(它实际上会生成 SQL 语句),所以你不能用它来处理任何基于你 Python 代码的查询 (你问题中的动态属性)

这是从很多其他回答中整理出来的一个答案 : )

撰写回答