如何从字符串内的括号中使用 Django Q 对象创建动态查询

Posted

技术标签:

【中文标题】如何从字符串内的括号中使用 Django Q 对象创建动态查询【英文标题】:How to create dynamic queries with Django Q objects from parenthesis inside a string 【发布时间】:2022-01-08 18:12:43 【问题描述】:

我不知道问题的标题是否足够好。但基本上我希望能够从前端做这样的事情:

(name="abc" OR name="xyz") AND (status="active" OR (status="available" AND age=30))

我希望用户发送这个字符串。我将在后端解析它并形成一个查询。

我查看了this 的答案和this,但不知道如何解决这里的括号。

我正在考虑使用堆栈(我们解决中缀表达式的方式)来执行此操作,但不想走那么长的路,除非我确定没有其他/现成的解决方案可用。 如果有人可以用这种方法做到这一点,那就太好了。

【问题讨论】:

【参考方案1】:

所以我通过稍微更改“中缀表达式求解器”来完成它。

代码如下:

def complex_filter_by_string(mystr):
"""
Filters string by using Q objects and infix expression solver.
The input string should be of the type ( name = abc OR name = xyz ) AND ( other = fgh ) Or ( other_one = xyz )
Notice the spaces after each 'word'. We are splitting the string by space, so space is necessary.
"""
tokens = mystr.split()

# stack to store integer values.
values = []

# stack to store operators.
ops = []
i = 0
while i < len(tokens):

    # Current token is a whitespace,
    # skip it.
    if tokens[i] == ' ':
        i += 1
        continue

    # Current token is an opening
    # brace, push it to 'ops'
    elif tokens[i] == '(':
        ops.append(tokens[i])

    # Current token is =, convert it to a Q object and push
    # it to stack for q objects.
    elif tokens[i] == "=":

        # if token equals '=' . It means we will have tokens[i-1]=attribute and tokens[i+1]=value (this is a constraint on string)
        obj = 
        key = tokens[i-1]
        value = tokens[i+1]
        if isinstance(value,str):
            i+=1
            while i+1 < len(tokens) and (tokens[i+1] != 'OR' and tokens[i+1] != 'AND' and tokens[i+1] != ')'):
                value= value + " " + tokens[i+1]
                i+=1
            value = value.rstrip()
        obj[key]=value
        val = Q(**obj)
        values.append(val)
    
        
    # Closing brace encountered,
    # solve entire brace.
    elif tokens[i] == ')':
    
        while len(ops) != 0 and ops[-1] != '(':
        
            val2 = values.pop()
            val1 = values.pop()
            op = ops.pop()
            
            values.append(applyOp(val1, val2, op))
        
        # pop opening brace.
        ops.pop()
    
    # Current token is an operator.
    elif tokens[i] == "OR" or tokens[i] == "AND":
    
        # While top of 'ops' has same or
        # greater precedence to current
        # token, which is an operator.
        # Apply operator on top of 'ops'
        # to top two elements in values stack.
        while (len(ops) != 0 and
            precedence(ops[-1]) >=
            precedence(tokens[i])):
                    
            val2 = values.pop()
            val1 = values.pop()
            op = ops.pop()
            
            values.append(applyOp(val1, val2, op))
        
        # Push current token to 'ops'.
        ops.append(tokens[i])
    
    i += 1

# Entire expression has been parsed
# at this point, apply remaining ops
# to remaining values.
while len(ops) != 0:
    
    val2 = values.pop()
    val1 = values.pop()
    op = ops.pop()
            
    values.append(applyOp(val1, val2, op))

# Top of 'values' contains result,
# return it.
return values[-1]

这将返回一个 Q 对象,您可以将其传递给您的模型 lie MyModel.objects.filter(q_obj)

虽然对字符串有一些限制。主要限制是“每个关键字后应该有一个空格”。关键字可以是 ' OR , AND , attribute , value , = , open_parenthesis '。

所以我发布的字符串应该是:( name = abc OR name = xyz ) AND (status = active OR ( status = available AND age = 30 ))

【讨论】:

解决问题的方法太复杂了,你可以用相对简单的方式用 Q 对象来解决问题。 @wobbily_col 我不认为你理解这个问题。我正在根据 USER 字符串进行过滤。如果您认为可以以更简单的方式完成,会很高兴看到这一点。但据我所知,您需要一些机制来解析用户字符串并转换为 Q 对象。 我同意,我认为您没有明确说明您要实现的目标或您要过滤的 USER 字符串到底是什么。【参考方案2】:

我想会是这样的。

YourModel.objects.filter(
    Q(Q(name="abc") | Q(name="xyz")) 
    & 
    Q( Q(status="active") 
       OR 
       Q(Q(status="available") 
         & 
         Q(age=30)
       )
   )
)

【讨论】:

以上是关于如何从字符串内的括号中使用 Django Q 对象创建动态查询的主要内容,如果未能解决你的问题,请参考以下文章

如何从变量模板字符串中提取动态对象并再次合并?

如何在 sed 脚本中使用 sed 删除括号内的字符串?

ActiveAdmin剥离花括号内的内容

Django - 如何将 QuerySet 转换为 Q 对象?

Django中Q搜索的简单应用

Bash:如何消除字符串中的所有数字字符,但保留括号内的字符?