2012-11-07 18 views
12

mam taki model książce:Django: ManyToMany filtr na dopasowanie wszystkich elementów listy

class Book(models.Model): 
    authors = models.ManyToManyField(Author, ...) 
    ... 

W skrócie:

Chciałbym odzyskać książki, których autorami są ściśle równa podany zestaw autorów. Nie jestem pewien, czy istnieje jedno zapytanie, które to robi, ale wszelkie sugestie będą pomocne.

W długi:

Oto, co starałem (które udało się uruchomić coraz AttributeError)

# A sample set of authors 
target_authors = set((author_1, author_2)) 

# To reduce the search space, 
# first retrieve those books with just 2 authors. 
candidate_books = Book.objects.annotate(c=Count('authors')).filter(c=len(target_authors)) 

final_books = QuerySet() 
for author in target_authors: 
    temp_books = candidate_books.filter(authors__in=[author]) 
    final_books = final_books and temp_books 

... i oto co mam:

AttributeError: 'NoneType' object has no attribute '_meta' 

Generalnie, jak powinienem zapytać model z ograniczeniem, że jego pole ManyToMany zawiera zbiór podanych obiektów, tak jak w moim przypadku?

ps: Znalazłem kilka istotnych pytań dotyczących SO, ale nie mogłem uzyskać jednoznacznej odpowiedzi. Pomocny będzie również każdy dobry wskaźnik. Dzięki.

+0

Prawie tam. Zobacz odpowiedź na to pytanie: http://stackoverflow.com/questions/8618068/django-filter-queryset-in-for-every-item-in-list –

Odpowiedz

12

Podobny do użytkownika @ goliney podejściu, znalazłem rozwiązanie. Myślę jednak, że wydajność można by poprawić.

# A sample set of authors 
target_authors = set((author_1, author_2)) 

# To reduce the search space, first retrieve those books with just 2 authors. 
candidate_books = Book.objects.annotate(c=Count('authors')).filter(c=len(target_authors)) 

# In each iteration, we filter out those books which don't contain one of the 
# required authors - the instance on the iteration. 
for author in target_authors: 
    candidate_books = candidate_books.filter(authors=author) 

final_books = candidate_books 
+0

Twoje rozwiązania robią to samo. Filtry kwargs są "AND" wydane –

+0

Masz rację, wyglądają tak samo. Uważam jednak, że istnieje różnica wykonania. Z tego co rozumiem, w twoim podejściu, 'autor' w polu' authors', który pasuje do 'author_1', również powinien pasować do' author_2'. Z drugiej strony filtrowanie iteracyjne nie wymusza takiego ograniczenia. Jeśli się mylę, popraw mnie. Jestem tutaj, aby się uczyć. Dzięki jeszcze raz! – iuysal

+0

Dobry pomysł na zmniejszenie przestrzeni poszukiwań. Awansuj! –

3

Można użyć complex lookups with Q objects

from django.db.models import Q 
... 
target_authors = set((author_1, author_2)) 
q = Q() 
for author in target_authors: 
    q &= Q(authors=author) 
Books.objects.annotate(c=Count('authors')).filter(c=len(target_authors)).filter(q) 
+3

Dzięki @goliney, chociaż podejście jest fajne i inspirujące, I domyślam się, że nie robi tego, czego szukam. Jeśli jest tylko jeden autor, działa dobrze, ale gdy jest wielu autorów, proces AND prawdopodobnie prowadzi do niemożliwego ograniczenia, takiego jak (gdzie a = x AND a = y). – iuysal

+2

Jak się okazało, jest coś niejasnego w zachowaniu Q() dla mnie. Według [tego względnego pytania] (http://stackoverflow.com/a/5542966/1065780) znajduję na SO, 'Q() i Q()' nie jest równe '.filter(). Filter()' . Dziękujemy za pytanie –

+2

Dzięki za link i udostępnianie czasu. – iuysal

0

natknąłem się na ten sam problem i doszedł do takiego samego wniosku jak iuysal, aż musiałem zrobić to średniej wielkości przeszukiwanie (z 1000 rekordy 150 filtruje moja prośba by time out).

W moim konkretnym przypadku wyszukiwanie nie spowoduje żadnych rekordów, ponieważ szansa, że ​​pojedynczy rekord zostanie dopasowany do wszystkich filtrów ALL 150 jest bardzo rzadka, można obejść problemy z wydajnością, sprawdzając, czy istnieją pozycje w kwerendzie przed zastosowaniem więcej filtrów, aby zaoszczędzić czas.

# In each iteration, we filter out those books which don't contain one of the 
# required authors - the instance on the iteration. 
for author in target_authors: 
    if candidate_books.count() > 0: 
     candidate_books = candidate_books.filter(authors=author) 

Z jakiegoś powodu Django stosuje filtry, aby opróżnić kwerendy. Ale jeśli optymalizacja ma być zastosowana poprawnie, konieczne jest użycie przygotowanego zestawu QuerySet i poprawnie zastosowanych indeksów.