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

Filter multiple Django model fields with variable number of arguments

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

假设我想通过 his/her 名字或姓氏,或通过存储在与 Customer 不同模型中的已放置 Order 的 ID 来查找 Customer。 我已经实现的简单场景是用户只在搜索字段中键入单个单词,然后我使用 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 并作为搜索结果返回了大量记录。

现在 he/she 想要更具体并将客户的姓氏添加到 search.So 搜索变成 Bruce Wayne,在将其拆分成单独的部分后我有 BruceWayne。显然,我不想搜索 Orders 模型,因为 order_id 是一个单词实例,它足以立即找到客户,所以对于这种情况,我将其完全排除在查询之外。

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

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

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

您可以动态地将可变数量的 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 的使用通过 OR 将 Q 对象组合在一起。感谢那部分答案 goes to this answer.

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

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()