No sé si el título de la pregunta está lo suficientemente bien formado. Pero esencialmente me gustaría poder hacer algo como esto desde el frente:
(name="abc" OR name="xyz") AND (status="active" OR (status="available" AND age=30))Quiero que el usuario envíe esta cadena. Lo analizaré en backend y formaré una consulta.
Miré this answer y this pero no pude encontrar la manera de resolver el paréntesis aquí.
Estoy pensando en usar una pila (la forma en que resolvemos las expresiones infijas) para hacer esto, pero no quiero seguir ese camino largo a menos que esté seguro de que no hay otra solución lista disponible. Si alguien puede hacer esto con ese método, también sería genial.
Creo que será algo como esto.
YourModel.objects.filter( Q(Q(name="abc") | Q(name="xyz")) & Q( Q(status="active") OR Q(Q(status="available") & Q(age=30) ) ) )Así que lo hice cambiando un poco el 'solucionador de expresiones infijas'.
Aquí está el código:
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] Esto devolverá un objeto Q que puede pasar a su modelo miente MyModel.objects.filter(q_obj)
Sin embargo, hay algunas restricciones en la cadena. La restricción principal es 'debe haber un espacio en blanco después de cada palabra clave'. Una palabra clave puede ser 'OR, AND, atributo, valor, =, paréntesis abierto'.
Entonces, la cadena que publiqué en cuestión debería ser: ( name = abc OR name = xyz ) AND (status = active OR ( status = available AND age = 30 ))