Wie filtert man in Django ein QuerySet mit dynamischen Feldsuchen?

160

Bei einer Klasse:

from django.db import models

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

Ist es möglich und wenn ja wie, ein QuerySet zu haben, das basierend auf dynamischen Argumenten filtert? Beispielsweise:

 # 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
quelle

Antworten:

310

Die Argumenterweiterung von Python kann verwendet werden, um dieses Problem zu lösen:

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

Person.objects.filter(**kwargs)

Dies ist eine sehr verbreitete und nützliche Python-Sprache.

Daniel Naab
quelle
6
Nur ein kurzes Heads-up: Stellen Sie sicher, dass die Zeichenfolgen in den Warns vom Typ str sind und nicht Unicode, da sonst filter () knurrt.
Steve Jalim
1
@santiagobasulto Es wird auch auf einen Parameter Packen / Entpacken und Variationen davon verwiesen.
Daniel Naab
7
nett, nett und nett !
Oscar Mederos
5
@ DanielNaab, aber dies funktioniert nur bei kwargs, die an UND-Bedingungsfilterung arbeiten, jede Alternative für ODER-Bedingung.
Prateek099
3
@prateek Sie können immer Q-Objekte verwenden: stackoverflow.com/questions/13076822/…
deecodameeko
6

Ein vereinfachtes Beispiel:

In einer Django-Umfrage-App wollte ich eine HTML-Auswahlliste mit registrierten Benutzern. Da wir jedoch 5000 registrierte Benutzer haben, brauchte ich eine Möglichkeit, diese Liste anhand von Abfragekriterien zu filtern (z. B. nur Personen, die einen bestimmten Workshop abgeschlossen haben). Damit das Umfrageelement wiederverwendbar ist, musste die Person, die die Umfragefrage erstellt, diese Kriterien an diese Frage anhängen können (ich möchte die Abfrage nicht fest in der App codieren).

Die Lösung, die ich gefunden habe, ist nicht 100% benutzerfreundlich (erfordert die Hilfe einer technischen Person, um die Abfrage zu erstellen), löst jedoch das Problem. Beim Erstellen der Frage kann der Editor ein Wörterbuch in ein benutzerdefiniertes Feld eingeben, z.

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

Diese Zeichenfolge wird in der Datenbank gespeichert. Im Ansichtscode wird es als zurückgegeben self.question.custom_query. Der Wert davon ist eine Zeichenfolge, die wie ein Wörterbuch aussieht . Wir verwandeln es mit eval () wieder in ein echtes Wörterbuch und füllen es dann mit ** kwargs in das Abfrageset:

kwargs = eval(self.question.custom_query)
user_list = User.objects.filter(**kwargs).order_by("last_name")   
Shacker
quelle
Ich frage mich, was erforderlich ist, um ein benutzerdefiniertes ModelField / FormField / WidgetField zu erstellen, das das Verhalten implementiert, damit der Benutzer auf der GUI-Seite im Grunde genommen eine Abfrage "erstellen" kann, ohne den tatsächlichen Text zu sehen, sondern eine Schnittstelle zu verwenden tun Sie dies. Klingt nach einem ordentlichen Projekt ...
T. Stone
1
T. Stone - Ich würde mir vorstellen, dass es einfach wäre, ein solches Tool auf vereinfachte Weise zu erstellen, wenn die Modelle, die abgefragt werden müssen, einfach, aber sehr schwierig auf eine gründliche Weise zu erstellen wären, die alle möglichen Optionen aufdeckt, insbesondere wenn die Modelle es wären Komplex.
Shacker
5
-1 Das Aufrufen eval()des Benutzerimports ist eine schlechte Idee, selbst wenn Sie Ihren Benutzern vollkommen vertrauen. Ein JSON-Feld wäre hier eine bessere Idee.
John Carter
5

Django.db.models.Q ist genau das, was Sie auf Django-Weise wollen.

Brent81
quelle
7
Könnten Sie (oder jemand) ein Beispiel für die Verwendung von Q-Objekten bei der Verwendung dynamischer Feldnamen geben?
Jackdbernier
3
Es ist dasselbe wie in Daniel Naabs Antwort. Der einzige Unterschied besteht darin, dass Sie die Argumente an den Q-Objektkonstruktor übergeben. Q(**filters)Wenn Sie Q-Objekte dynamisch aufbauen möchten, können Sie sie in eine Liste .filter(*q_objects)einfügen und verwenden oder die bitweisen Operatoren verwenden, um die Q-Objekte zu kombinieren.
Will S
5
Diese Antwort sollte wirklich ein Beispiel für die Verwendung von Q zur Lösung des OP-Problems enthalten.
pdoherty926
-2

Ein wirklich komplexes Suchformular zeigt normalerweise an, dass ein einfacheres Modell versucht, seinen Ausweg zu finden.

Wie genau erwarten Sie die Werte für den Spaltennamen und die Operation? Wo sehen Sie die Werte erhalten 'name'ein 'startswith'?

 filter_by = '%s__%s' % ('name', 'startswith')
  1. Ein "Such" -Formular? Du wirst - was? - Wählen Sie den Namen aus einer Liste von Namen? Wählen Sie die Operation aus einer Liste von Operationen aus? Während offen, finden die meisten Menschen dies verwirrend und schwer zu bedienen.

    Wie viele Spalten haben solche Filter? 6? 12? 18?

    • Ein paar? Eine komplexe Auswahlliste macht keinen Sinn. Ein paar Felder und ein paar if-Anweisungen sind sinnvoll.
    • Eine große Anzahl? Ihr Modell klingt nicht richtig. Es klingt so, als ob das "Feld" tatsächlich ein Schlüssel für eine Zeile in einer anderen Tabelle ist, keine Spalte.
  2. Spezifische Filtertasten. Warten Sie ... So funktioniert der Django-Administrator. Bestimmte Filter werden zu Schaltflächen. Und es gilt die gleiche Analyse wie oben. Einige Filter sind sinnvoll. Eine große Anzahl von Filtern bedeutet normalerweise eine Art erste normale Formverletzung.

Viele ähnliche Felder bedeuten oft, dass mehr Zeilen und weniger Felder vorhanden sein sollten.

S.Lott
quelle
9
In Bezug auf Respekt ist es vermessen, Empfehlungen abzugeben, ohne etwas über das Design zu wissen. Eine "einfache Implementierung" dieser Anwendung würde astronomische Funktionen (> 200 Apps ^ 21 foos) erzeugen, um die Anforderungen zu erfüllen. Sie lesen Zweck und Absicht in das Beispiel ein; du solltest nicht. :)
Brian M. Hunt
2
Ich treffe viele Leute, die das Gefühl haben, dass ihr Problem trivial zu lösen wäre, wenn nur die Dinge (a) allgemeiner wären und (b) so funktionieren würden, wie sie es sich vorgestellt haben. Auf diese Weise liegt endlose Frustration, weil die Dinge nicht so sind, wie sie es sich vorgestellt haben. Ich habe zu viele Fehler gesehen, die auf das "Reparieren des Frameworks" zurückzuführen sind.
S.Lott
2
Die Dinge funktionieren wie erwartet und gewünscht gemäß Daniels Antwort. Meine Frage betraf die Syntax, nicht das Design. Wenn ich Zeit gehabt hätte, das Design aufzuschreiben, hätte ich das getan. Ich bin sicher, Ihre Eingabe wäre hilfreich, aber es ist einfach keine praktische Option.
Brian M. Hunt
8
S.Lott, Ihre Antwort beantwortet diese Frage nicht einmal aus der Ferne. Wenn Sie keine Antwort wissen, lassen Sie die Frage bitte in Ruhe. Antworten Sie nicht mit unaufgeforderten Design-Ratschlägen, wenn Sie absolut keine Kenntnisse über das Design haben!
Slypete
2
@slypete: Wenn eine Änderung am Design das Problem behebt, ist das Problem gelöst. Die Fortsetzung des Weges aufgrund eines schlechten Designs ist teurer und komplexer als nötig. Das Lösen von Grundproblemen ist besser als das Lösen anderer Probleme, die sich aus schlechten Entwurfsentscheidungen ergeben. Es tut mir leid, dass Sie die Ursachenanalyse nicht mögen. Aber wenn etwas wirklich schwierig ist, bedeutet dies normalerweise, dass Sie zunächst das Falsche versuchen.
S.Lott