O django prefetch_related deve funcionar com GenericRelation

ATUALIZAR: Um Open Ticked sobre este problema:24272

O que é isso tudo?

O Django tem umGenericRelation classe, que adiciona umRelacionamento genérico "reverso" para ativar um adicionalAPI.

Acontece que podemos usar issoreverse-generic-relation parafiltering ouordering, mas não podemos usá-lo dentroprefetch_related.

Eu queria saber se isso é um bug, ou não deveria funcionar, ou é algo que pode ser implementado no recurso.

Deixe-me mostrar alguns exemplos do que quero dizer.

Vamos dizer que temos dois modelos principais:Movies eBooks.

Movies tenha umDirectorBooks tem umAuthor

E queremos atribuir tags ao nossoMovies eBooks, mas em vez de usarMovieTag eBookTag modelos, queremos usar um únicoTaggedItem aula com umGFK paraMovie ouBook.

Aqui está a estrutura do modelo:

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

E alguns dados iniciais:

>>> from tags.models import Book, Movie, Author, Director, TaggedItem
>>> 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')

Então, como odocs mostrar que podemos fazer coisas assim.

>>> b1.tags.all()
[<TaggedItem: roman>]
>>> m1.tags.all()
[<TaggedItem: action movie>]
>>> TaggedItem.objects.filter(books__author__name='E L James')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>]
>>> TaggedItem.objects.filter(movies__director__name='James Gunn')
[<TaggedItem: action movie>]
>>> Book.objects.all().prefetch_related('tags')
[<Book: Fifty Shades of Grey>, <Book: Fifty Shades Darker>, <Book: Fifty Shades Freed>]
>>> Book.objects.filter(tags__tag='roman')
[<Book: Fifty Shades of Grey>, <Book: Fifty Shades Darker>, <Book: Fifty Shades Freed>]

Mas, se tentarmosprefetch&nbsp;algunsrelated data&nbsp;doTaggedItem&nbsp;através destereverse generic relation, nós vamos obter umAttributeError.

>>> TaggedItem.objects.all().prefetch_related('books')
Traceback (most recent call last):
  ...
AttributeError: 'Book' object has no attribute 'object_id'

Alguns de vocês podem perguntar, por que eu simplesmente não usocontent_object&nbsp;ao invés debooks&nbsp;aqui? O motivo é que isso só funciona quando queremos:

1)prefetch&nbsp;apenas um nível profundo dequerysets&nbsp;contendo diferentes tipos decontent_object.

>>> TaggedItem.objects.all().prefetch_related('content_object')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: action movie>]

2)prefetch&nbsp;muitos níveis, mas dequerysets&nbsp;contendo apenas um tipo decontent_object.

>>> TaggedItem.objects.filter(books__author__name='E L James').prefetch_related('content_object__author')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>]

Mas, se quisermos 1) e 2) (paraprefetch&nbsp;muitos níveis dequeryset&nbsp;contendo diferentes tipos decontent_objects, não podemos usarcontent_object.

>>> TaggedItem.objects.all().prefetch_related('content_object__author')
Traceback (most recent call last):
  ...
AttributeError: 'Movie' object has no attribute 'author_id'

Django&nbsp;pensa que tudocontent_objects&nbsp;estãoBookse, portanto, eles têm umAuthor.

Agora imagine a situação em que queremosprefetch&nbsp;não apenas obooks&nbsp;com a suaauthor, mas também omovies&nbsp;com a suadirector. Aqui estão algumas tentativas.

A maneira boba:

>>> TaggedItem.objects.all().prefetch_related(
...     'content_object__author',
...     'content_object__director',
... )
Traceback (most recent call last):
  ...
AttributeError: 'Movie' object has no attribute 'author_id'

Talvez com costumePrefetch&nbsp;objeto?

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

Algumas soluções deste problema são mostradasaqui. Mas isso é muita massagem nos dados que eu quero evitar. Eu realmente gosto da API vinda doreversed generic relations, seria muito bom poder fazerprefetchs&nbsp;Curtiu isso:

>>> TaggedItem.objects.all().prefetch_related(
...     'books__author',
...     'movies__director',
... )
Traceback (most recent call last):
  ...
AttributeError: 'Book' object has no attribute 'object_id'

Ou assim:

>>> 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'

Mas como você pode ver, sempre conseguimos issoAttributeError. Estou usando Django1.7.3&nbsp;e Python2.7.6. E estou curioso por que o Django está lançando esse erro? Por que o Django está procurando por umobject_id&nbsp;noBook&nbsp;modelo?Por que acho que isso pode ser um bug?&nbsp;Geralmente quando perguntamosprefetch_related&nbsp;para resolver algo que não pode, vemos:

>>> TaggedItem.objects.all().prefetch_related('some_field')
Traceback (most recent call last):
  ...
AttributeError: Cannot find 'some_field' on TaggedItem object, 'some_field' is an invalid parameter to prefetch_related()

Mas aqui, é diferente. O Django realmente tenta resolver a relação ... e falha. Este é um bug que deve ser relatado? Eu nunca relatei nada ao Django, por isso estou perguntando aqui primeiro. Não consigo rastrear o erro e decidir por mim mesmo se isso é um bug ou um recurso que pode ser implementado.