Nach Eigenschaft filtern

95

Ist es möglich, ein Django-Abfrageset nach Modelleigenschaft zu filtern?

Ich habe eine Methode in meinem Modell:

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

und jetzt möchte ich nach dieser Eigenschaft filtern wie:

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

ist das irgendwie möglich

schneck
quelle
Es befindet sich in SQLAlchemy: docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html und Sie können Django mit SQLAlchemy über pypi.python.org/pypi/aldjemy verbinden, aber ich bezweifle , dass die beiden verbunden werden könnten so wie du es willst.
Rattray

Antworten:

78

Nee. Django-Filter arbeiten auf Datenbankebene und generieren SQL. Um basierend auf Python-Eigenschaften zu filtern, müssen Sie das Objekt in Python laden, um die Eigenschaft auszuwerten - und zu diesem Zeitpunkt haben Sie bereits die gesamte Arbeit zum Laden erledigt.

Glenn Maynard
quelle
4
Pech, dass diese Funktion nicht implementiert ist, wäre eine interessante Erweiterung, um zumindest übereinstimmende Objekte herauszufiltern, nachdem die Ergebnismenge erstellt wurde.
Schneck
1
Wie gehe ich in Admin damit um? Gibt es eine Problemumgehung?
Andilabs
39

Ich könnte Ihre ursprüngliche Frage falsch verstehen, aber in Python ist ein Filter eingebaut.

filtered = filter(myproperty, MyModel.objects)

Es ist jedoch besser, ein Listenverständnis zu verwenden :

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

oder noch besser, ein Generatorausdruck :

filtered = (x for x in MyModel.objects if x.myproperty())
Clint
quelle
14
Das funktioniert, um es zu filtern, sobald Sie ein Python-Objekt haben, aber er fragt nach Django QuerySet.filter, das SQL-Abfragen erstellt.
Glenn Maynard
1
Richtig, aber wie oben erläutert, möchte ich die Eigenschaft meinem Datenbankfilter hinzufügen. Das Filtern nach Abschluss der Abfrage ist genau das, was ich vermeiden möchte.
Schneck
18

Wenn Sie die von @ TheGrimmScientist vorgeschlagene Problemumgehung umgehen, können Sie diese "SQL-Eigenschaften" erstellen, indem Sie sie im Manager oder im QuerySet definieren und sie wiederverwenden / verketten / zusammenstellen:

Mit einem 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)

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

Weitere Informationen finden Sie unter https://docs.djangoproject.com/de/1.9/topics/db/managers/ . Beachten Sie, dass ich die Dokumentation verlasse und die oben genannten nicht getestet habe.

Rattray
quelle
14

Die Verwendung von F () mit Anmerkungen scheint meine Lösung dafür zu sein.

Es wird nicht nach gefiltert @property, da FGespräche mit der Datenbank geführt werden, bevor Objekte in Python gebracht werden. Aber immer noch hier als Antwort, da mein Grund für das Filtern nach Eigenschaften wirklich darin bestand, Objekte nach dem Ergebnis einfacher Arithmetik in zwei verschiedenen Feldern filtern zu wollen.

Also etwas in der Art von:

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

anstatt die Eigenschaft zu definieren als:

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

Führen Sie dann ein Listenverständnis für alle Objekte durch.

TheGrimmScientist
quelle
3

BITTE korrigiert mich jemand, aber ich denke, ich habe eine Lösung gefunden, zumindest für meinen eigenen Fall.

Ich möchte an all jenen Elementen arbeiten, deren Eigenschaften genau gleich ... was auch immer sind.

Aber ich habe mehrere Modelle, und diese Routine sollte für alle Modelle funktionieren. Und es tut:

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)

Mit dieser universellen Unterroutine kann ich alle Elemente auswählen, die genau meinem Wörterbuch der Kombinationen "Angeben" (Eigenschaftsname, Eigenschaftswert) entsprechen.

Der erste Parameter nimmt a (models.Model),

das zweite ein Wörterbuch wie: {"property1": "77", "property2": "12"}

Und es erstellt eine SQL-Anweisung wie

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

und gibt ein QuerySet für diese Elemente zurück.

Dies ist eine Testfunktion:

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 

Und? Was denken Sie?

akrueger
quelle
Im Allgemeinen scheint es eine anständige Arbeit zu sein. Ich würde nicht sagen, dass es ideal ist, aber es ist besser, ein Repository zu teilen, um das Modell für Pakete, die Sie von PyPI installiert haben, jedes Mal zu ändern, wenn Sie so etwas benötigen.
Hlongmore
Und jetzt, da ich Zeit hatte, ein bisschen damit zu spielen: Der eigentliche Nachteil dieses Ansatzes ist, dass von .raw () zurückgegebene Querysets keine vollwertigen Querysets sind, womit ich meine, dass Queryset-Methoden fehlen:AttributeError: 'RawQuerySet' object has no attribute 'values'
hlongmore
3

Ich hatte das gleiche Problem und entwickelte diese einfache Lösung:

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

Ich weiß, dass es nicht die performativste Lösung ist, kann aber in einfachen Fällen wie meinen helfen

Vitalieren
quelle
1

Ich weiß, dass es eine alte Frage ist, aber für diejenigen, die hier springen, halte ich es für nützlich, die folgende Frage und die relative Antwort zu lesen:

So passen Sie den Admin-Filter in Django 1.4 an

FSp
quelle
1
Für diejenigen, die diese Antwort überfliegen - dieser Link enthält Informationen zum Implementieren von Listenfiltern im Django-Administrator mithilfe von "SimpleListFilter". Nützlich, aber keine Antwort auf die Frage, außer in einem ganz bestimmten Fall.
Jenniwren
0

Es kann auch möglich sein queryset Anmerkungen zu verwenden , die die Eigenschaft get / set-Logik, wie vorgeschlagen zB durch doppelte @rattray und @thegrimmscientist , in Verbindung mit der property. Dies könnte zu etwas führen, das sowohl auf Python- als auch auf Datenbankebene funktioniert .

Ich bin mir jedoch nicht sicher über die Nachteile: Ein Beispiel finden Sie in dieser SO-Frage .

djvg
quelle
Der Link zu Ihrer Codeüberprüfungsfrage weist darauf hin, dass er vom Autor freiwillig entfernt wurde. Würde es Ihnen etwas ausmachen, Ihre Antwort hier zu aktualisieren, entweder mit einem Link zum Code oder mit einer Erklärung, oder einfach Ihre Antwort zu entfernen?
Hlongmore
@hlongmore: Entschuldigung. Diese Frage wurde nach SO verschoben. Ich habe den Link oben behoben.
DJVG