使用可变数量的参数过滤多个 Django 模型字段

Posted

技术标签:

【中文标题】使用可变数量的参数过滤多个 Django 模型字段【英文标题】:Filter multiple Django model fields with variable number of arguments 【发布时间】:2018-09-30 12:33:45 【问题描述】:

我正在实现搜索功能,其中包含通过匹配多个表和这些表中的多个字段来查找记录的选项。

假设我想通过他/她的名字或姓氏找到Customer,或者通过存储在与Customer 不同的模型中的Order 的ID。 我已经实现的简单场景是用户只在搜索字段中键入单个单词,然后我使用 Django Q 使用直接字段引用或 related_query_name 引用来查询 Order 模型,例如:

result = Order.objects.filter(
        Q(customer__first_name__icontains=user_input)
        |Q(customer__last_name__icontains=user_input)
        |Q(order_id__icontains=user_input)
        ).distinct()

小菜一碟,一点问题都没有。

但是如果用户想要缩小搜索范围并在搜索字段中输入多个单词怎么办。

示例:用户输入了Bruce,并通过搜索返回了大量记录。

现在他/她想要更具体,并将客户的姓氏添加到搜索中。因此搜索变为 Bruce Wayne,在将其拆分为单独的部分后,我得到了 BruceWayne。显然我不想搜索Orders 模型,因为order_id 是一个单字实例,一次找到客户就足够了,所以在这种情况下,我完全将其从查询中删除。

现在我正在尝试通过名字和姓氏来匹配客户,我还想处理提供数据的顺序是随机的情况,以正确处理 Bruce WayneWayne Bruce,这意味着我仍然有客户全名,但名字和姓氏的位置不固定。

这就是我正在寻找答案的问题:如何构建查询来搜索模型的多个字段,而不知道哪个搜索词属于哪个表。

我猜这个解决方案很简单,而且肯定有一种优雅的方法可以创建这样一个动态查询,但我想不出方法。

【问题讨论】:

【参考方案1】:

您可以动态地 OR 可变数量的 Q 对象来实现您想要的搜索。下面的方法使添加或删除要包含在搜索中的字段变得很简单。

from functools import reduce
from operator import or_


fields = (
    'customer__first_name__icontains',
    'customer__last_name__icontains',
    'order_id__icontains'
)
parts = []
terms = ["Bruce", "Wayne"]  # produce this from your search input field
for term in terms:
    for field in fields:
        parts.append(Q(**field: term))

query = reduce(or_, parts)

result = Order.objects.filter(query).distinct()

reduce 的使用通过 ORing 将 Q 对象组合在一起。归功于答案的那部分goes to this answer。

【讨论】:

这回答了与动态构建查询相关的部分问题,但也许我没有把问题说得足够清楚,所以我更新了这个问题。【参考方案2】:

我想出的解决方案相当复杂,但它的工作方式正是我想要处理这个问题的方式:

search_keys = user_input.split()
if len(search_keys) > 1:
    first_name_set = set()
    last_name_set = set()
    for key in search_keys:
        first_name_set.add(Q(customer__first_name__icontains=key))
        last_name_set.add(Q(customer__last_name__icontains=key))
    query = reduce(and_, [reduce(or_, first_name_set), reduce(or_, last_name_set)])

else:
    search_fields = [
        Q(customer__first_name__icontains=user_input),
        Q(customer__last_name__icontains=user_input),
        Q(order_id__icontains=user_input),
    ]
    query = reduce(or_, search_fields)

result = Order.objects.filter(query).distinct()

【讨论】:

以上是关于使用可变数量的参数过滤多个 Django 模型字段的主要内容,如果未能解决你的问题,请参考以下文章

在preparedStatement中使用可变数量的参数

在django中动态添加参数到queryset过滤器调用[重复]

使用 django 模板呈现可变数量的文本字段/输入对

Django:更新 X 数量模型的表单

django-rest-framework- 使用“或”过滤来自一个 url 参数的多个值

Django,如何使用过滤器检查字符串字段是不是是参数的子字符串?