Filtrar por propiedad

95

¿Es posible filtrar un conjunto de consultas de Django por propiedad del modelo?

tengo un método en mi modelo:

@property
def myproperty(self):
    [..]

y ahora quiero filtrar por esta propiedad como:

MyModel.objects.filter(myproperty=[..])

¿Es esto de alguna manera posible?

Schneck
fuente
Está en SQLAlchemy: docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html y puede conectar django con SQLAlchemy a través de pypi.python.org/pypi/aldjemy pero dudo que los dos puedan estar conectados de la forma en que quieres que sean.
rattray

Respuestas:

78

No Los filtros de Django operan a nivel de base de datos, generando SQL. Para filtrar según las propiedades de Python, debe cargar el objeto en Python para evaluar la propiedad, y en ese punto, ya ha hecho todo el trabajo para cargarlo.

Glenn Maynard
fuente
5
mala suerte de que esta función no esté implementada, sería una extensión interesante para al menos filtrar los objetos coincidentes después de que se haya creado el conjunto de resultados.
schneck
1
¿cómo lidiar con eso en admin? ¿Existe alguna solución?
andilabs
39

Puede que esté malinterpretando su pregunta original, pero hay un filtro incorporado en Python.

filtered = filter(myproperty, MyModel.objects)

Pero es mejor usar una lista de comprensión :

filtered = [x for x in MyModel.objects if x.myproperty()]

o mejor aún, una expresión generadora :

filtered = (x for x in MyModel.objects if x.myproperty())
Clint
fuente
15
Eso funciona para filtrarlo una vez que tienes un objeto Python, pero pregunta sobre Django QuerySet.filter, que construye consultas SQL.
Glenn Maynard
1
correcto, pero como se explicó anteriormente, me gustaría agregar la propiedad a mi filtro de base de datos. filtrar después de que se ha realizado la consulta es exactamente lo que quiero evitar.
schneck
19

Partiendo de la solución alternativa sugerida por @ TheGrimmScientist, puede crear estas "propiedades sql" definiéndolas en el Administrador o en el QuerySet, y reutilizarlas / encadenarlas / componerlas:

Con un gerente:

class CompanyManager(models.Manager):
    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyManager()

Company.objects.with_chairs_needed().filter(chairs_needed__lt=4)

Con un QuerySet:

class CompanyQuerySet(models.QuerySet):
    def many_employees(self, n=50):
        return self.filter(num_employees__gte=n)

    def needs_fewer_chairs_than(self, n=5):
        return self.with_chairs_needed().filter(chairs_needed__lt=n)

    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyQuerySet.as_manager()

Company.objects.needs_fewer_chairs_than(4).many_employees()

Consulte https://docs.djangoproject.com/en/1.9/topics/db/managers/ para obtener más información. Tenga en cuenta que me estoy saliendo de la documentación y no he probado lo anterior.

rattray
fuente
14

Parece que usar F () con anotaciones será mi solución para esto.

No se filtrará @property, ya que se Fcomunica con la base de datos antes de que los objetos se introduzcan en Python. Pero aún poniéndolo aquí como respuesta, ya que mi razón para querer filtrar por propiedad era realmente querer filtrar objetos por el resultado de aritmética simple en dos campos diferentes.

entonces, algo parecido a:

companies = Company.objects\
    .annotate(chairs_needed=F('num_employees') - F('num_chairs'))\
    .filter(chairs_needed__lt=4)

en lugar de definir la propiedad como:

@property
def chairs_needed(self):
    return self.num_employees - self.num_chairs

luego haciendo una lista de comprensión de todos los objetos.

TheGrimmCientífico
fuente
5

Tuve el mismo problema y desarrollé esta solución simple:

objects_id = [x.id for x in MyModel.objects.all() if x.myProperty == [...]]
MyModel.objects.filter(id__in=objects_id)

Sé que no es la solución más eficaz, pero puede ayudar en casos simples como el mío.

Vitalate
fuente
3

POR FAVOR alguien me corrija, pero supongo que he encontrado una solución, al menos para mi propio caso.

Quiero trabajar en todos aquellos elementos cuyas propiedades sean exactamente iguales a ... lo que sea.

Pero tengo varios modelos y esta rutina debería funcionar para todos los modelos. Y lo hace:

def selectByProperties(modelType, specify):
    clause = "SELECT * from %s" % modelType._meta.db_table

    if len(specify) > 0:
        clause += " WHERE "
        for field, eqvalue in specify.items():
            clause += "%s = '%s' AND " % (field, eqvalue)
        clause = clause [:-5]  # remove last AND

    print clause
    return modelType.objects.raw(clause)

Con esta subrutina universal, puedo seleccionar todos aquellos elementos que sean exactamente iguales a mi diccionario de combinaciones 'especificar' (nombre de propiedad, valor de propiedad).

El primer parámetro toma un (modelos.Modelo),

el segundo un diccionario como: {"propiedad1": "77", "propiedad2": "12"}

Y crea una declaración SQL como

SELECT * from appname_modelname WHERE property1 = '77' AND property2 = '12'

y devuelve un QuerySet en esos elementos.

Esta es una función de prueba:

from myApp.models import myModel

def testSelectByProperties ():

    specify = {"property1" : "77" , "property2" : "12"}
    subset = selectByProperties(myModel, specify)

    nameField = "property0"
    ## checking if that is what I expected:
    for i in subset:
        print i.__dict__[nameField], 
        for j in specify.keys():
             print i.__dict__[j], 
        print 

¿Y? ¿Qué piensas?

Akrueger
fuente
Generalmente parece un trabajo decente. No diría que es ideal, pero es mejor que tener que bifurcar un repositorio para modificar el modelo de los paquetes que instaló desde PyPI cada vez que necesite algo como esto.
hlongmore
Y ahora que he tenido tiempo para jugar un poco con él: la desventaja real de este enfoque es que los conjuntos de consultas devueltos por .raw () no son conjuntos de consultas completos, por lo que me refiero a que faltan métodos de conjuntos de consultas:AttributeError: 'RawQuerySet' object has no attribute 'values'
hlongmore
1

Sé que es una pregunta antigua, pero por el bien de los que saltan aquí, creo que es útil leer la pregunta a continuación y la respuesta relativa:

Cómo personalizar el filtro de administración en Django 1.4

FSp
fuente
1
Para aquellos que leen esta respuesta, este enlace contiene información sobre la implementación de filtros de lista en el administrador de Django usando "SimpleListFilter". Útil, pero no es una respuesta a la pregunta excepto en un caso muy específico.
jenniwren
0

También puede ser posible utilizar anotaciones de QuerySet que duplican la propiedad get / set-lógica, como se sugiere por ejemplo por @rattray y @thegrimmscientist , en conjunción con el property. Esto podría producir algo que funciona tanto en el nivel de Python y en el nivel de base de datos.

Sin embargo, no estoy seguro de los inconvenientes: consulte esta pregunta SO como ejemplo.

djvg
fuente
El enlace de la pregunta de revisión de código le notifica que su autor lo eliminó voluntariamente. ¿Le importaría actualizar su respuesta aquí, ya sea con un enlace al código o con una explicación, o simplemente elimine su respuesta?
hlongmore
@hlongmore: Perdón por eso. Esa pregunta se trasladó a SO. Arreglé el enlace de arriba.
djvg