#24272: prefetch_related GenericRelation via related_query_name
-------------------------------------+-------------------------------------
     Reporter:  asdator              |      Owner:  nobody
         Type:  New feature          |     Status:  new
    Component:  Database layer       |    Version:  1.7
  (models, ORM)                      |   Keywords:  prefetch_related,
     Severity:  Normal               |  GenericRelation, related_query_name
 Triage Stage:  Unreviewed           |  Has patch:  0
Easy pickings:  0                    |      UI/UX:  0
-------------------------------------+-------------------------------------
 Consider the following model structure:


 {{{#!python
 from django.db import models
 from django.contrib.contenttypes.fields import GenericForeignKey,
 GenericRelation
 from django.contrib.contenttypes.models import ContentType


 class TaggedItem(models.Model):
     tag = models.SlugField()
     content_type = models.ForeignKey(ContentType)
     object_id = models.PositiveIntegerField()
     content_object = GenericForeignKey('content_type', 'object_id')

     def __unicode__(self):
         return self.tag


 class Director(models.Model):
     name = models.CharField(max_length=100)

     def __unicode__(self):
         return self.name


 class Movie(models.Model):
     name = models.CharField(max_length=100)
     director = models.ForeignKey(Director)
     tags = GenericRelation(TaggedItem, related_query_name='movies')

     def __unicode__(self):
         return self.name


 class Author(models.Model):
     name = models.CharField(max_length=100)

     def __unicode__(self):
         return self.name


 class Book(models.Model):
     name = models.CharField(max_length=100)
     author = models.ForeignKey(Author)
     tags = GenericRelation(TaggedItem, related_query_name='books')

     def __unicode__(self):
         return self.name
 }}}

 And some initial data:



 {{{#!python
 >>> a = Author.objects.create(name='E L James')
 >>> b1 = Book.objects.create(name='Fifty Shades of Grey', author=a)
 >>> b2 = Book.objects.create(name='Fifty Shades Darker', author=a)
 >>> b3 = Book.objects.create(name='Fifty Shades Freed', author=a)
 >>> d = Director.objects.create(name='James Gunn')
 >>> m1 = Movie.objects.create(name='Guardians of the Galaxy', director=d)
 >>> t1 = TaggedItem.objects.create(content_object=b1, tag='roman')
 >>> t2 = TaggedItem.objects.create(content_object=b2, tag='roman')
 >>> t3 = TaggedItem.objects.create(content_object=b3, tag='roman')
 >>> t4 = TaggedItem.objects.create(content_object=m1, tag='action movie')
 }}}

 Now using the `GenericForeignKey` we are able to:
 1. `prefetch` only one level deep from `querysets` containing different
 type of `content_object`
 {{{#!python
 >>> TaggedItem.objects.all().prefetch_related('content_object')
 [<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>,
 <TaggedItem: action movie>]
 }}}
 2. `prefetch` many levels but from `querysets` containing only one type of
 `content_object`.
 {{{#!python
 >>> TaggedItem.objects.filter(books__author__name='E L
 James').prefetch_related('content_object__author')
 [<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>]
 }}}
 But we can't do 1) and 2) together (//`prefetch` many levels from
 `querysets` containing different types of `content_objects`//)
 {{{#!python
 >>> TaggedItem.objects.all().prefetch_related('content_object__author')
 Traceback (most recent call last):
   ...
 AttributeError: 'Movie' object has no attribute 'author_id'
 }}}
 For such tasks this API is inconvenient and became unconvincing in more
 complex examples. For example if we want all `TaggedItems` with
 `prefetched` `movies` with their `directors` and `prefetched` `books` with
 their `author`.
 One silly attempt would look like this:
 {{{#!python
 >>> TaggedItem.objects.all().prefetch_related(
 ...     'content_object__author',
 ...     'content_object__director',
 ... )
 Traceback (most recent call last):
   ...
 AttributeError: 'Movie' object has no attribute 'author_id'
 }}}
 Or like this:
 {{{#!python
 >>> TaggedItem.objects.all().prefetch_related(
 ...     Prefetch('content_object',
 queryset=Book.objects.all().select_related('author')),
 ...     Prefetch('content_object',
 queryset=Movie.objects.all().select_related('director')),
 ... )
 Traceback (most recent call last):
   ...
 ValueError: Custom queryset can't be used for this lookup.
 }}}

 What I suggest is to use the API which we used to filter `TaggedItems` by
 their `book` `author`. This is not working right now.
 {{{#!python
 >>> TaggedItem.objects.filter(books__author__name='E L
 James').prefetch_related('books')
 Traceback (most recent call last):
   ...
 AttributeError: 'Book' object has no attribute 'object_id'
 }}}
 This way we would have and a nice solution for the more complex example
 mentioned above:
 {{{#!python
 >>> TaggedItem.objects.all().prefetch_related(
 ...     'books__author',
 ...     'movies__director',
 ... )
 Traceback (most recent call last):
   ...
 AttributeError: 'Book' object has no attribute 'object_id'
 }}}
 Or like this:
 {{{#!python
 >>> TaggedItem.objects.all().prefetch_related(
 ...     Prefetch('books',
 queryset=Book.objects.all().select_related('author')),
 ...     Prefetch('movies',
 queryset=Movie.objects.all().select_related('director')),
 ... )
 Traceback (most recent call last):
   ...
 AttributeError: 'Book' object has no attribute 'object_id'
 }}}

--
Ticket URL: <https://code.djangoproject.com/ticket/24272>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/050.74525858d55b92260bff5424f68817c4%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to