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