75

I've tried prefetch_related() in django 1.4 from trunk and can't make it to prefetch reverse lookup.

My simplified models (each book has many prices):

class Book(models.Model):
    # some fields

class Price(models.Model):
    book = models.ForeignKey(Book)

My view's query:

books = Book.objects.prefetch_related('price')

Then, I got the AttributeError message:

AttributeError: Cannot find 'price' on Book object, 'price' is an invalid parameter to prefetch_related()

How to make it work? Thanks.

Tianissimo
  • 1,240
  • 1
  • 11
  • 16

2 Answers2

108

Define a related name:

class Price(models.Model):
    book = models.ForeignKey(Book, related_name='prices')

and then use it:

books = Book.objects.prefetch_related('prices')
Jan Pöschko
  • 5,412
  • 1
  • 28
  • 28
  • 47
    Your answer reminds me of the FOO_set so the correction can also be .prefetch_related('price_set'). BTW, Thanks a lot – Tianissimo Feb 07 '12 at 12:58
  • 1
    I am not able to reproduce this in the shell. Still does separate queries, I was expecting joins. – pdvyas May 30 '13 at 06:05
  • Hello! I have also tried this, but as @pdvyas said, there are seperate queries. This does not make a join... – Anton Jul 01 '13 at 15:02
  • 8
    `prefetch_related` does not make joins, it just collects related objects IDs and then fetches those objects in a single query and generated these 'joins' inside Python. See: https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.prefetch_related – aherok Aug 28 '13 at 09:00
  • @jan-pöschko, it seems an (easier) alternative to defining a `related_name`, is to use the `price_set` name to refer to the relation. I've added an answer describing this, but perhaps it would be good to refer to my answer in your (accepted) anser, or include my text in yours? – Matthijs Kooijman Feb 15 '18 at 19:25
31

When you do not have defined a related_name for the relation, the reverse relation has _set appended. This is the case when accessing the reverse relation from an object (e.g. some_book.price_set.all()), but this also works on prefetch_related:

books = Book.objects.prefetch_related('price_set')

Note that this seems different from filter, which does accept the name of the other model without _set (e.g. Books.objects.filter(price__currency='EUR')).

The above was tested with 1.11.8 (not on this specific code, but on my own, similar code).

Alternatively, you can add a related_name, as shown by Jan Pöschko above.

Matthijs Kooijman
  • 2,498
  • 23
  • 30