如何使用字符串中括号中的 Django Q 对象创建动态查询
How to create dynamic queries with Django Q objects from parenthesis inside a string
不知道题目的格式是否够好。但基本上我希望能够从前端做这样的事情:
(name="abc" OR name="xyz") AND (status="active" OR (status="available" AND age=30))
我想让用户发送这个字符串。我将在后端解析它并形成一个查询。
我看过 this answer and this 但不知道如何解决这里的括号。
我正在考虑使用堆栈(我们解决中缀表达式的方式)来执行此操作,但不想走那么远的路,除非我确定没有 another/ready 可用的解决方案。
如果有人能用那种方法做到这一点,那就太好了。
我想会是这样的。
YourModel.objects.filter(
Q(Q(name="abc") | Q(name="xyz"))
&
Q( Q(status="active")
OR
Q(Q(status="available")
&
Q(age=30)
)
)
)
所以我通过稍微更改 'infix expression solver' 来完成它。
代码如下:
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]
这将 return 一个 Q 对象,您可以将其传递给您的模型谎言 MyModel.objects.filter(q_obj)
虽然对字符串有一些限制。主要限制是'there should be a whitespace after each keyword'。关键字可以是“OR、AND、attribute、value、=、open_parenthesis”。
所以我在问题中发布的字符串应该是:( name = abc OR name = xyz ) AND (status = active OR ( status = available AND age = 30 ))
不知道题目的格式是否够好。但基本上我希望能够从前端做这样的事情:
(name="abc" OR name="xyz") AND (status="active" OR (status="available" AND age=30))
我想让用户发送这个字符串。我将在后端解析它并形成一个查询。
我看过 this answer and this 但不知道如何解决这里的括号。
我正在考虑使用堆栈(我们解决中缀表达式的方式)来执行此操作,但不想走那么远的路,除非我确定没有 another/ready 可用的解决方案。 如果有人能用那种方法做到这一点,那就太好了。
我想会是这样的。
YourModel.objects.filter(
Q(Q(name="abc") | Q(name="xyz"))
&
Q( Q(status="active")
OR
Q(Q(status="available")
&
Q(age=30)
)
)
)
所以我通过稍微更改 'infix expression solver' 来完成它。
代码如下:
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]
这将 return 一个 Q 对象,您可以将其传递给您的模型谎言 MyModel.objects.filter(q_obj)
虽然对字符串有一些限制。主要限制是'there should be a whitespace after each keyword'。关键字可以是“OR、AND、attribute、value、=、open_parenthesis”。
所以我在问题中发布的字符串应该是:( name = abc OR name = xyz ) AND (status = active OR ( status = available AND age = 30 ))