W Django, jak można filtrować QuerySet za pomocą dynamicznego wyszukiwania pól?

160

Biorąc pod uwagę klasę:

from django.db import models

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

Czy jest możliwe, a jeśli tak, to w jaki sposób, mieć zestaw QuerySet filtrujący na podstawie dynamicznych argumentów? Na przykład:

 # 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
źródło

Odpowiedzi:

310

Do rozwiązania tego problemu można użyć rozszerzenia argumentów Pythona:

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

Person.objects.filter(**kwargs)

To bardzo popularny i przydatny idiom Pythona.

Daniel Naab
źródło
6
Tylko szybkie ostrzeżenie: upewnij się, że ciągi znaków w kwargach są typu str, a nie unicode, w przeciwnym razie filter () będzie narzekał.
Steve Jalim
1
@santiagobasulto Odnosi się również do parametru pakowania / rozpakowywania i jego odmian.
Daniel Naab,
7
miło, miło i miło !
Oscar Mederos
5
@DanielNaab, ale będzie to działać tylko na kwargach pracujących na filtrowaniu warunku AND, dowolnej alternatywy dla warunku OR.
Prateek099
3
@prateek, zawsze możesz używać obiektów Q: stackoverflow.com/questions/13076822/…
deecodameeko
6

Uproszczony przykład:

W aplikacji ankietowej Django chciałem mieć listę wyboru HTML pokazującą zarejestrowanych użytkowników. Ale ponieważ mamy 5000 zarejestrowanych użytkowników, potrzebowałem sposobu na filtrowanie tej listy na podstawie kryteriów zapytania (takich jak tylko osoby, które ukończyły określone warsztaty). Aby element ankiety mógł być ponownie użyty, potrzebowałem, aby osoba tworząca pytanie ankietowe mogła dołączyć te kryteria do tego pytania (nie chcę na stałe zakodować zapytania w aplikacji).

Rozwiązanie, które wymyśliłem, nie jest w 100% przyjazne dla użytkownika (wymaga pomocy technika w celu utworzenia zapytania), ale rozwiązuje problem. Podczas tworzenia pytania redaktor może wprowadzić słownik w własne pole, np .:

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

Ten ciąg jest przechowywany w bazie danych. W kodzie widoku wraca jako self.question.custom_query. Wartością tego jest łańcuch, który wygląda jak słownik. Przekształcamy go z powrotem w prawdziwy słownik za pomocą eval (), a następnie umieszczamy go w zestawie zapytań za pomocą ** kwargs:

kwargs = eval(self.question.custom_query)
user_list = User.objects.filter(**kwargs).order_by("last_name")   
shacker
źródło
Zastanawiam się, co należałoby zrobić, aby utworzyć niestandardowy model ModelField / FormField / WidgetField, który zaimplementował zachowanie umożliwiające użytkownikowi po stronie GUI „zbudowanie” zapytania, nigdy nie widząc rzeczywistego tekstu, ale używając interfejsu do Zrób tak. Brzmi jak zgrabny projekt ...
T. Stone
1
T. Stone - wyobrażam sobie, że byłoby łatwo zbudować takie narzędzie w uproszczony sposób, gdyby modele wymagające zapytań były proste, ale bardzo trudne do wykonania w sposób, który ujawniłby wszystkie możliwe opcje, zwłaszcza gdyby modele były złożony.
shacker
5
-1 wywołanie eval()importu użytkowników to zły pomysł, nawet jeśli całkowicie ufasz swoim użytkownikom. Lepszym pomysłem byłoby tutaj pole JSON.
John Carter,
5

Django.db.models.Q jest dokładnie tym, czego potrzebujesz w Django.

Brent81
źródło
7
Czy mógłbyś (lub ktoś) podać przykład używania obiektów Q w używaniu dynamicznych nazw pól?
jackdbernier
3
To to samo, co w odpowiedzi Daniela Naaba . Jedyna różnica polega na tym, że argumenty są przekazywane do konstruktora obiektów Q. Q(**filters)Jeśli chcesz dynamicznie budować obiekty Q, możesz umieścić je na liście i użyć .filter(*q_objects)lub użyć operatorów bitowych do połączenia obiektów Q.
Will S
5
Ta odpowiedź powinna naprawdę zawierać przykład użycia Q do rozwiązania problemu OP.
pdoherty926
-2

Naprawdę złożone formularze wyszukiwania zwykle wskazują, że prostszy model próbuje wykopać, z którego się wydostaje.

Jak dokładnie spodziewasz się uzyskać wartości dla nazwy kolumny i operacji? Gdzie można uzyskać wartości ?'name''startswith'

 filter_by = '%s__%s' % ('name', 'startswith')
  1. Formularz „wyszukiwania”? Zamierzasz - co? - wybrać nazwisko z listy nazwisk? Wybrać operację z listy operacji? Chociaż otwarte, większość ludzi uważa, że ​​jest to zagmatwane i trudne w użyciu.

    Ile kolumn ma takie filtry? 6? 12? 18?

    • Kilka? Złożona lista wyboru nie ma sensu. Kilka pól i kilka instrukcji „jeśli” ma sens.
    • Duża liczba? Twój model nie brzmi dobrze. Wygląda na to, że „pole” jest w rzeczywistości kluczem do wiersza w innej tabeli, a nie kolumną.
  2. Określone przyciski filtrów. Czekaj ... Tak działa administrator Django. Poszczególne filtry zamieniają się w przyciski. Obowiązuje ta sama analiza, co powyżej. Kilka filtrów ma sens. Duża liczba filtrów oznacza zwykle rodzaj pierwszego naruszenia normalnej postaci.

Wiele podobnych pól często oznacza, że ​​powinno być więcej wierszy i mniej pól.

S.Lott
źródło
9
Z całym szacunkiem, zuchwalstwo wydaje się zalecenia, nie wiedząc nic o projekcie. „Proste zaimplementowanie” tej aplikacji spowodowałoby, że astronomiczne (> 200 aplikacji ^ 21 foos) funkcje spełniałyby wymagania. Wczytujesz cel i zamiar w przykładzie; nie powinieneś. :)
Brian M. Hunt
2
Spotykam wielu ludzi, którzy uważają, że ich rozwiązanie byłoby trywialne, gdyby tylko (a) były bardziej ogólne i (b) działały tak, jak sobie wyobrażali. W ten sposób pojawia się nieskończona frustracja, ponieważ rzeczy nie są takie, jak sobie wyobrażali. Widziałem zbyt wiele niepowodzeń związanych z „naprawianiem struktury”.
S.Lott
2
Wszystko działa zgodnie z oczekiwaniami i oczekiwaniami, na podstawie odpowiedzi Daniela. Moje pytanie dotyczyło składni, a nie projektowania. Gdybym miał czas na napisanie projektu, zrobiłbym to. Jestem pewien, że Twój wkład byłby pomocny, jednak nie jest to praktyczna opcja.
Brian M. Hunt
8
S.Lott, twoja odpowiedź nie odpowiada nawet zdalnie na to pytanie. Jeśli nie znasz odpowiedzi, zostaw pytanie w spokoju. Nie odpowiadaj niezamówionymi poradami projektowymi, jeśli masz absolutnie zerową wiedzę na temat projektu!
slypete
2
@slypete: Jeśli zmiana w projekcie usuwa problem, oznacza to, że problem został rozwiązany. Kontynuacja ścieżki w oparciu o kiepski projekt jest droższa i bardziej złożona niż to konieczne. Rozwiązywanie problemów źródłowych jest lepsze niż rozwiązywanie innych problemów wynikających ze złych decyzji projektowych. Przykro mi, że nie lubisz analizy przyczyn źródłowych. Ale kiedy coś jest naprawdę trudne, zwykle oznacza to, że zaczynasz robić coś złego.
S.Lott,