En Django, ¿cómo se filtra un QuerySet con búsquedas de campo dinámico?

160

Dada una clase:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=20)

¿Es posible, y si es así, tener un QuerySet que filtre en función de argumentos dinámicos? Por ejemplo:

 # Instead of:
 Person.objects.filter(name__startswith='B')
 # ... and:
 Person.objects.filter(name__endswith='B')

 # ... is there some way, given:
 filter_by = '{0}__{1}'.format('name', 'startswith')
 filter_value = 'B'

 # ... that you can run the equivalent of this?
 Person.objects.filter(filter_by=filter_value)
 # ... which will throw an exception, since `filter_by` is not
 # an attribute of `Person`.
Brian M. Hunt
fuente

Respuestas:

310

La expansión del argumento de Python puede usarse para resolver este problema:

kwargs = {
    '{0}__{1}'.format('name', 'startswith'): 'A',
    '{0}__{1}'.format('name', 'endswith'): 'Z'
}

Person.objects.filter(**kwargs)

Este es un idioma de Python muy común y útil.

Daniel Naab
fuente
66
Solo una advertencia rápida: asegúrese de que las cadenas en los kwargs sean de tipo str no unicode, de lo contrario filter () se quejará.
Steve Jalim
1
@santiagobasulto También se refiere a un parámetro de empaque / desempaque, y sus variaciones.
Daniel Naab
77
agradable, agradable y agradable !
Oscar Mederos
55
@DanielNaab, pero esto solo funcionará en kwargs que trabajen en el filtrado de condiciones AND, cualquier alternativa para la condición OR.
Prateek099
3
@prateek siempre puedes usar objetos Q: stackoverflow.com/questions/13076822/…
deecodameeko
6

Un ejemplo simplificado:

En una aplicación de encuesta de Django, quería una lista de selección HTML que mostrara a los usuarios registrados. Pero debido a que tenemos 5000 usuarios registrados, necesitaba una forma de filtrar esa lista en función de los criterios de consulta (como las personas que completaron un determinado taller). Para que el elemento de la encuesta sea reutilizable, necesitaba que la persona que crea la pregunta de la encuesta pueda adjuntar esos criterios a esa pregunta (no quiero codificar la consulta en la aplicación).

La solución que se me ocurrió no es fácil de usar al 100% (requiere la ayuda de un técnico para crear la consulta), pero resuelve el problema. Al crear la pregunta, el editor puede ingresar un diccionario en un campo personalizado, por ejemplo:

{'is_staff':True,'last_name__startswith':'A',}

Esa cadena se almacena en la base de datos. En el código de vista, vuelve como self.question.custom_query. El valor de eso es una cadena que se parece a un diccionario. Lo convertimos de nuevo en un diccionario real con eval () y luego lo rellenamos en el conjunto de consultas con ** kwargs:

kwargs = eval(self.question.custom_query)
user_list = User.objects.filter(**kwargs).order_by("last_name")   
shacker
fuente
Me pregunto qué se necesitaría para crear un ModelField / FormField / WidgetField personalizado que implementara el comportamiento para permitir al usuario, en el lado de la GUI, básicamente "construir" una consulta, sin ver nunca el texto real, pero usando una interfaz para hazlo Suena como un proyecto ordenado ...
T. Stone
1
T. Stone: Me imagino que sería fácil construir una herramienta de este tipo de manera simplista si los modelos que necesitaran consulta fueran simples, pero muy difíciles de hacer de una manera exhaustiva que exponga todas las opciones posibles, especialmente si los modelos fueran complejo.
shacker
55
-1 llamar eval()a la importación de usuarios es una mala idea, incluso si confía completamente en sus usuarios. Un campo JSON sería una mejor idea aquí.
John Carter
5

Django.db.models.Q es exactamente lo que quieres de una manera Django.

Brent81
fuente
77
¿Podría usted (o alguien) proporcionar un ejemplo de cómo usar objetos Q al usar nombres de campo dinámico?
jackdbernier
3
Es lo mismo que en la respuesta de Daniel Naab. La única diferencia es que pasa los argumentos al constructor de objetos Q. Q(**filters), si desea construir dinámicamente objetos Q, puede ponerlos en una lista y usarlos .filter(*q_objects), o usar los operadores bit a bit para combinar los objetos Q.
Will S
55
Esta respuesta realmente debería incluir un ejemplo del uso de Q para resolver el problema de OP.
pdoherty926
-2

Un formulario de búsqueda realmente complejo generalmente indica que un modelo más simple está intentando cavar.

¿Cómo, exactamente, espera obtener los valores para el nombre y la operación de la columna? ¿De dónde sacas los valores de 'name'un 'startswith'?

 filter_by = '%s__%s' % ('name', 'startswith')
  1. ¿Un formulario de "búsqueda"? Vas a ... ¿qué? - elegir el nombre de una lista de nombres? Elija la operación de una lista de operaciones? Si bien son abiertas, la mayoría de las personas encuentran esto confuso y difícil de usar.

    ¿Cuántas columnas tienen tales filtros? 6? 12? 18?

    • ¿Unos pocos? Una lista de selección compleja no tiene sentido. Algunos campos y algunas declaraciones if tienen sentido.
    • ¿Un número grande? Tu modelo no suena bien. Parece que el "campo" es en realidad una clave para una fila en otra tabla, no una columna.
  2. Botones de filtro específicos. Espera ... Así es como funciona el administrador de Django. Los filtros específicos se convierten en botones. Y se aplica el mismo análisis que el anterior. Algunos filtros tienen sentido. Una gran cantidad de filtros generalmente significa una especie de primera violación de forma normal.

Muchos campos similares a menudo significan que debería haber más filas y menos campos.

S.Lott
fuente
9
Con respeto, es presuntuoso hacer recomendaciones sin saber nada sobre el diseño. Para "simplemente implementar" esta aplicación engendraría funciones astronómicas (> 200 aplicaciones ^ 21 foos) para cumplir con los requisitos. Estás leyendo propósito e intención en el ejemplo; no deberías :)
Brian M. Hunt
2
Conozco a muchas personas que sienten que su problema sería trivial de resolver si solo las cosas fueran (a) más genéricas y (b) funcionaran de la manera que imaginaban. De esa manera se encuentra la frustración interminable porque las cosas no son como se las imaginaban. He visto demasiadas fallas derivadas de "arreglar el marco".
S.Lott
2
Las cosas funcionan según lo esperado y deseado por la respuesta de Daniel. Mi pregunta era sobre la sintaxis, no sobre el diseño. Si hubiera tenido tiempo de escribir el diseño, lo habría hecho. Estoy seguro de que su aporte sería útil, sin embargo, no es una opción práctica.
Brian M. Hunt
8
S.Lott, tu respuesta ni siquiera responde remotamente esta pregunta. Si no sabe una respuesta, deje la pregunta sola. ¡No responda con consejos de diseño no solicitados cuando tenga absolutamente ningún conocimiento del diseño!
slypete
2
@slypete: si un cambio en el diseño elimina el problema, entonces el problema está resuelto. Continuar por el camino basado en un diseño pobre es más costoso y complejo de lo necesario. Resolver problemas de causa raíz es mejor que resolver otros problemas derivados de malas decisiones de diseño. Lamento que no le guste el análisis de causa raíz. Pero cuando algo es realmente difícil, generalmente significa que estás tratando de hacer algo incorrecto para empezar.
S.Lott