You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于字符串内括号逻辑使用Django Q对象生成动态查询

实现带优先级的查询字符串转Django查询的两种方案

你要实现的需求核心是把类SQL的逻辑表达式转换成Django ORM支持的Q对象组合,两种实现方案如下:

方案一:用pyparsing实现(无需手动处理栈,最简便)

不需要自己手写栈处理优先级,直接通过语法规则定义自动解析:

  1. 先安装依赖
pip install pyparsing django
  1. 核心实现代码
from pyparsing import infixNotation, opAssoc, Word, alphas, nums, dblQuotedString, removeQuotes
from django.db.models import Q

# 定义解析规则
# 匹配字段名:字母开头,可包含数字下划线
field = Word(alphas, alphas + nums + "_")
# 匹配值:支持数字、双引号字符串
value = Word(nums) | dblQuotedString.setParseAction(removeQuotes)
# 匹配比较操作符,示例默认做等于判断,可扩展gt/lt/contains等
op = Word("=", max=1)
# 单个条件:字段 + 操作符 + 值,直接转成Q对象
condition = (field + op + value).setParseAction(lambda t: Q(**{f"{t[0]}__exact": t[2]}))

# 定义逻辑操作符优先级:AND高于OR,括号自动处理优先级
expr = infixNotation(
    condition,
    [
        ("AND", 2, opAssoc.LEFT, lambda t: t[0][0] & t[0][2]),
        ("OR", 2, opAssoc.LEFT, lambda t: t[0][0] | t[0][2]),
    ]
)

# 使用示例
query_str = '(name="abc" OR name="xyz") AND (status="active" OR (status="available" AND age=30))'
parsed_q = expr.parseString(query_str)[0]
# 直接用于查询即可
# result = YourModel.objects.filter(parsed_q)

方案二:手动栈实现(无第三方解析依赖)

如果不想引入额外依赖,可以自己实现中缀表达式转后缀表达式,再构造Q对象:

from django.db.models import Q

def tokenize(query_str):
    """把查询字符串拆成token"""
    tokens = []
    i = 0
    n = len(query_str)
    while i < n:
        c = query_str[i]
        if c in '()':
            tokens.append(c)
            i += 1
        elif c.isspace():
            i += 1
        elif query_str[i:i+3] in ('AND', 'OR'):
            tokens.append(query_str[i:i+3])
            i += 3
        elif c == '"':
            # 匹配双引号包裹的字符串值
            j = i + 1
            while j < n and query_str[j] != '"':
                j += 1
            tokens.append(query_str[i:j+1])
            i = j + 1
        else:
            # 匹配字段名、数字、操作符
            j = i
            while j < n and not query_str[j].isspace() and query_str[j] not in '()="':
                j += 1
            tokens.append(query_str[i:j])
            i = j
    # 把拆分的字段、等于号、值合并成单个条件token
    merged_tokens = []
    idx = 0
    while idx < len(tokens):
        if idx + 2 < len(tokens) and tokens[idx+1] == '=':
            k = tokens[idx]
            v = tokens[idx+2].strip('"')
            merged_tokens.append(Q(**{k: v}))
            idx += 3
        else:
            merged_tokens.append(tokens[idx])
            idx += 1
    return merged_tokens

def infix_to_postfix(tokens):
    """中缀转后缀(逆波兰表达式),处理优先级和括号"""
    precedence = {'OR': 1, 'AND': 2}
    output = []
    op_stack = []
    for token in tokens:
        if token == '(':
            op_stack.append(token)
        elif token == ')':
            while op_stack and op_stack[-1] != '(':
                output.append(op_stack.pop())
            op_stack.pop() # 弹出左括号丢弃
        elif token in ('AND', 'OR'):
            while op_stack and op_stack[-1] != '(' and precedence[op_stack[-1]] >= precedence[token]:
                output.append(op_stack.pop())
            op_stack.append(token)
        else:
            # 是已经转换好的Q对象,直接入输出队列
            output.append(token)
    while op_stack:
        output.append(op_stack.pop())
    return output

def eval_postfix(postfix):
    """计算后缀表达式得到最终Q对象"""
    stack = []
    for token in postfix:
        if isinstance(token, Q):
            stack.append(token)
        elif token in ('AND', 'OR'):
            b = stack.pop()
            a = stack.pop()
            if token == 'AND':
                stack.append(a & b)
            else:
                stack.append(a | b)
    return stack[0] if stack else Q()

# 使用示例
query_str = '(name="abc" OR name="xyz") AND (status="active" OR (status="available" AND age=30))'
tokens = tokenize(query_str)
postfix = infix_to_postfix(tokens)
parsed_q = eval_postfix(postfix)
# 直接用于查询即可
# result = YourModel.objects.filter(parsed_q)

安全提示

一定要加字段白名单校验,解析的时候判断用户传入的字段是不是你允许查询的模型字段,避免用户传入敏感字段导致数据泄露。


内容的提问来源于stack exchange,提问作者Mazhar Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 08:06:05