2012-11-07 6 views
12

j'ai un tel modèle du livre:Django: mise en correspondance de filtre ManyToMany sur tous les articles dans une liste

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

En bref:

Je voudrais récupérer les livres dont les auteurs sont strictement égale à un ensemble donné d'auteurs. Je ne suis pas sûr si une seule requête le fait, mais toutes les suggestions seront utiles.

En longue:

Voici ce que j'ai essayé, (qui a échoué à courir obtenir un 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 

... et voici ce que je suis:

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

En général, comment dois-je interroger un modèle avec la contrainte que son champ ManyToMany contient un ensemble d'objets donnés comme dans mon cas? Ps: J'ai trouvé des questions SO pertinentes mais je n'ai pas pu obtenir de réponse claire. Tout bon pointeur sera utile aussi. Merci.

+0

Presque là. Voir la réponse dans cette question: http://stackoverflow.com/questions/8618068/django-filter-queryset-in-for-every-item-in-list –

Répondre

12

similaires à l'approche de @ goliney, j'ai trouvé une solution. Cependant, je pense que l'efficacité pourrait être améliorée.

# 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

Vos solutions font la même chose. Les filtres kwargs sont "ET" ed –

+0

Vous avez raison, ils se ressemblent. Cependant, je pense qu'il y a une différence d'exécution. Autant que j'ai compris, dans votre approche, le 'author' dans le champ' authors' qui correspond à 'author_1' devrait également correspondre à' author_2'. D'un autre côté, le filtrage itératif n'applique pas une telle contrainte. Si j'ai tort, corrigez-moi s'il vous plaît. Je suis ici pour apprendre. Merci encore! – iuysal

+0

Bon point sur l'idée de réduire l'espace de recherche. Upvote! –

3

Vous pouvez utiliser 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

Merci @goliney, bien que l'approche soit soignée et inspirante, je Je suppose que ça ne fait pas ce que je cherche. S'il n'y a qu'un seul auteur, cela fonctionne bien, mais quand il y a plusieurs auteurs, le processus AND conduit probablement à une contrainte impossible comme (où a = x ET a = y). – iuysal

+2

Comme il s'est avéré, il y a quelque chose de flou dans le comportement Q() pour moi. Selon [cette question relative] (http://stackoverflow.com/a/5542966/1065780) je trouve sur SO, 'Q() & Q()' n'est pas égal à '.filter(). Filter()' . Merci pour votre question –

+2

Merci pour le lien et partager votre temps. – iuysal

0

je suis tombé sur le même problème et est arrivé à la même conclusion que iuysal, jusqu'à ce que je dû faire une recherche de taille moyenne (avec 1000 enregistrements avec 150 filtres ma demande serait expirer). Dans mon cas particulier, la recherche n'aboutirait à aucun enregistrement car la possibilité qu'un seul enregistrement s'aligne avec TOUS les 150 filtres est très rare, vous pouvez contourner les problèmes de performances en vérifiant qu'il existe des enregistrements dans le QuerySet avant d'appliquer plus de filtres pour gagner du temps.

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

Pour certaines raisons, Django applique des filtres aux QuerySets vides. Cependant, si l'optimisation doit être appliquée correctement, il est nécessaire d'utiliser un QuerySet préparé et des index correctement appliqués.

Questions connexes