2012-10-19 12 views
67

supponiamo di avere questo modello:Perché django's prefetch_related() funziona solo con all() e non filter()?

class PhotoAlbum(models.Model): 
    title = models.CharField(max_length=128) 
    author = models.CharField(max_length=128) 

class Photo(models.Model): 
    album = models.ForeignKey('PhotoAlbum') 
    format = models.IntegerField() 

Ora, se io voglio guardare un sottoinsieme di foto in un sottogruppo di album in modo efficiente. Lo faccio qualcosa di simile:

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set") 
for a in someAlbums: 
    somePhotos = a.photo_set.all() 

Questo fa solo due domande, che è quello che mi aspetto (uno per ottenere gli album, e poi uno come `SELECT * in foto DOVE photoalbum_id IN()

.

Tutto è grande

Ma se faccio questo:.

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set") 
for a in someAlbums: 
    somePhotos = a.photo_set.filter(format=1) 

Poi si fa un sacco di domande con WHERE format = 1 Sto facendo qualcosa di sbagliato o è Django non è intelligente eno! Per capire che ha già recuperato tutte le foto e può filtrarle in python? Giuro di aver letto da qualche parte nella documentazione che dovrebbe farlo ...

+0

possibile duplicato di [Filtro su prefetch \ _related in Django] (http://stackoverflow.com/questions/10915319/filter-on-prefetch-related-in-django) – akaihola

risposta

132

In Django 1.6 e precedenti, non è possibile evitare le query aggiuntive. La chiamata prefetch_related memorizza in modo efficace i risultati di a.photoset.all() per ogni album nel set di query. Tuttavia, a.photoset.filter(format=1) è un queryset diverso, quindi genererai una query aggiuntiva per ogni album.

Questo è spiegato nei documenti prefetch_related. Il filter(format=1) è equivalente a filter(spicy=True).

Nota che si potrebbe ridurre il numero o le query filtrando le foto in python, invece:

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set") 
for a in someAlbums: 
    somePhotos = [p for p in a.photo_set.all() if p.format == 1] 

In Django 1.7, c'è un oggetto Prefetch() che permette di controllare il comportamento di prefetch_related.

from django.db.models import Prefetch 

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related(
    Prefetch(
     "photo_set", 
     queryset=Photo.objects.filter(format=1), 
     to_attr="some_photos" 
    ) 
) 
for a in someAlbums: 
    somePhotos = a.some_photos 

Per ulteriori esempi di come utilizzare l'oggetto Prefetch, vedere i prefetch_related documentazione.

5

Dal docs:

... come sempre con querysets, tutti i metodi incatenati successivi che comportano una query di database diverso ignorerà risultati precedentemente memorizzati nella cache, e recuperare i dati utilizzando una query di database fresca. Quindi, se si scrive la seguente:

pizze = Pizza.objects.prefetch_related ('ingredienti') [list (pizza.toppings.filter (piccante = True)) per la pizza in pizze]

... quindi il fatto che pizza.toppings.all() sia stato precaricato non ti aiuterà, anzi fa male le prestazioni, dal momento che hai fatto una query di database che non hai usato. Quindi usa questa funzione con cautela!

Nel tuo caso, "a.photo_set.filter (format = 1)" viene trattato come una nuova query.

Inoltre, "photo_set" è una ricerca inversa, implementata completamente da un gestore diverso.