Filtra per proprietà


95

È possibile filtrare un set di query Django in base alla proprietà del modello?

ho un metodo nel mio modello:

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

e ora voglio filtrare in base a questa proprietà come:

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

è questo in qualche modo possibile?


È in SQLAlchemy: docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html e puoi connettere django con SQLAlchemy tramite pypi.python.org/pypi/aldjemy ma dubito che i due possano essere collegati nel modo in cui vuoi che siano.
Rattray

Risposte:


78

No. I filtri Django operano a livello di database, generando SQL. Per filtrare in base alle proprietà Python, devi caricare l'oggetto in Python per valutare la proprietà e, a quel punto, hai già fatto tutto il lavoro per caricarlo.


5
sfortuna che questa funzionalità non sia implementata, sarebbe un'estensione interessante per filtrare almeno gli oggetti corrispondenti dopo che il gruppo di risultati è stato compilato.
schneck

1
come affrontarlo in admin? C'è qualche soluzione alternativa?
andilabs

39

Potrei fraintendere la tua domanda originale, ma c'è un filtro integrato in Python.

filtered = filter(myproperty, MyModel.objects)

Ma è meglio usare una comprensione dell'elenco :

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

o ancora meglio, un'espressione generatrice :

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

15
Funziona per filtrarlo una volta che hai un oggetto Python, ma sta chiedendo di Django QuerySet.filter, che costruisce query SQL.
Glenn Maynard,

1
giusto, ma come spiegato sopra, vorrei aggiungere la proprietà al mio filtro del database. filtrare dopo che la query è stata eseguita è esattamente ciò che voglio evitare.
schneck

19

Rifiutando la soluzione alternativa suggerita da @ TheGrimmScientist, è possibile creare queste "proprietà sql" definendole in Manager o QuerySet e riutilizzarle / concatenarle / comporle:

Con un manager:

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()

Vedi https://docs.djangoproject.com/en/1.9/topics/db/managers/ per ulteriori informazioni. Nota che sto uscendo dalla documentazione e non ho testato quanto sopra.


14

Sembra che l' uso di F () con le annotazioni sarà la mia soluzione a questo.

Non filtrerà @property, poiché Fparla al database prima che gli oggetti vengano importati in Python. Ma lo metto ancora qui come risposta poiché la ragione per cui volevo filtrare per proprietà era davvero voler filtrare gli oggetti in base al risultato di una semplice aritmetica su due campi diversi.

quindi, qualcosa sulla falsariga di:

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

piuttosto che definire la proprietà come:

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

poi facendo una lista di comprensione di tutti gli oggetti.


5

Ho avuto lo stesso problema e ho sviluppato questa semplice soluzione:

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

So che non è la soluzione più performante, ma può aiutare in casi semplici come il mio


3

PER FAVORE qualcuno mi corregga, ma credo di aver trovato una soluzione, almeno per il mio caso.

Voglio lavorare su tutti quegli elementi le cui proprietà sono esattamente uguali a ... qualunque cosa.

Ma ho diversi modelli e questa routine dovrebbe funzionare per tutti i modelli. E lo fa:

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 questa subroutine universale, posso selezionare tutti quegli elementi che sono esattamente uguali al mio dizionario di combinazioni "specificate" (nome proprietà, valore proprietà).

Il primo parametro accetta un (models.Model),

il secondo un dizionario come: {"property1": "77", "property2": "12"}

E crea un'istruzione SQL come

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

e restituisce un QuerySet su quegli elementi.

Questa è una funzione di test:

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 

E? Cosa ne pensi?


In generale sembra un lavoro decente in giro. Non direi che è l'ideale, ma è meglio dover fare il fork di un repository per modificare il modello dei pacchetti che hai installato da PyPI ogni volta che hai bisogno di qualcosa di simile.
hlongmore

E ora che ho avuto il tempo di giocarci un po ': il vero svantaggio di questo approccio è che i set di query restituiti da .raw () non sono set di query a tutti gli effetti, con questo intendo che ci sono metodi di set di query che mancano:AttributeError: 'RawQuerySet' object has no attribute 'values'
hlongmore

1

so che è una vecchia domanda, ma per il bene di chi salta qui penso sia utile leggere la domanda qui sotto e la relativa risposta:

Come personalizzare il filtro di amministrazione in Django 1.4


1
Per coloro che scremano questa risposta, questo collegamento è alle informazioni sull'implementazione dei filtri elenco nell'amministratore di Django utilizzando "SimpleListFilter". Utile, ma non una risposta alla domanda se non in un caso molto specifico.
jenniwren

0

Potrebbe anche essere possibile utilizzare annotazioni di set di query che duplicano la proprietà get / set-logic, come suggerito ad esempio da @rattray e @thegrimmscientist , in collaborazione con il property. Questo potrebbe produrre qualcosa che funziona sia a livello Python e sul livello di database.

Non sono sicuro degli svantaggi, tuttavia: vedi questa domanda SO per un esempio.


Il collegamento alla domanda di revisione del codice fornisce un avviso che è stato rimosso volontariamente dal suo autore. Ti dispiacerebbe aggiornare la tua risposta qui, con un link al codice, o con una spiegazione, o semplicemente rimuovere la tua risposta?
hlongmore

@hlongmore: Scusa per quello. Questa domanda è stata spostata in SO. Ho corretto il collegamento sopra.
djvg
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.