¿Se supone que django prefetch_related funciona con GenericRelation?
ACTUALIZAR: Un Open Ticked sobre este tema:24272
¿De qué se trata todo esto?
Django tiene unRelación genérica clase, que agrega unRelación genérica "inversa" para habilitar un adicionalAPI.
Resulta que podemos usar estoreverse-generic-relation
parafiltering
oordering
, pero no podemos usarlo adentroprefetch_related
.
Me preguntaba si esto es un error, o no se supone que funcione, o es algo que se puede implementar en la función.
Déjame mostrarte con algunos ejemplos lo que quiero decir.
Digamos que tenemos dos modelos principales:Movies
yBooks
.
Movies
tener unDirector
Books
tienen unaAuthor
Y queremos asignar etiquetas a nuestroMovies
yBooks
, pero en lugar de usarMovieTag
yBookTag
modelos, queremos usar un soloTaggedItem
clase con unGFK
aMovie
oBook
.
Aquí está la estructura del 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
Y algunos datos iniciales:
>>> 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')
Entonces como eldocs mostrar que podemos hacer cosas como esta.
>>> 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>]
Pero si tratamos deprefetch
algunosrelated data
deTaggedItem
a través de estoreverse generic relation
vamos a obtener unAttributeError.
>>> TaggedItem.objects.all().prefetch_related('books')
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
Algunos de ustedes pueden preguntar, ¿por qué simplemente no usocontent_object
en lugar debooks
¿aquí? La razón es porque esto solo funciona cuando queremos:
1)prefetch
solo un nivel de profundidad desdequerysets
que contiene diferentes tipos decontent_object
.
>>> TaggedItem.objects.all().prefetch_related('content_object')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: action movie>]
2)prefetch
muchos niveles pero desdequerysets
que contiene solo un tipo decontent_object
.
>>> TaggedItem.objects.filter(books__author__name='E L James').prefetch_related('content_object__author')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>]
Pero, si queremos tanto 1) como 2) (paraprefetch
muchos niveles dequeryset
que contiene diferentes tipos decontent_objects
, no 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
piensa que todoscontent_objects
sonBooks
y por lo tanto tienen unAuthor
.
Ahora imagine la situación donde queremosprefetch
no solo elbooks
con suauthor
, pero también elmovies
con sudirector
. Aquí hay algunos intentos.
La manera tonta:
>>> TaggedItem.objects.all().prefetch_related(
... 'content_object__author',
... 'content_object__director',
... )
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'author_id'
Tal vez con costumbrePrefetch
¿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.
Se muestran algunas soluciones a este problema.aquí. Pero eso es mucho masaje sobre los datos que quiero evitar. Realmente me gusta la API que viene dereversed generic relations
, sería muy bueno poder hacerprefetchs
como eso:
>>> TaggedItem.objects.all().prefetch_related(
... 'books__author',
... 'movies__director',
... )
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
O asi:
>>> 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'
Pero como puedes ver, siempre lo entendemosAttributeError. Estoy usando Django1.7.3
y Python2.7.6
. ¿Y tengo curiosidad por qué Django está arrojando ese error? ¿Por qué Django está buscando unobject_id
en elBook
¿modelo?¿Por qué creo que esto puede ser un error? Usualmente cuando preguntamosprefetch_related
para resolver algo que no puede, 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()
Pero aquí, es diferente. Django en realidad intenta resolver la relación ... y falla. ¿Es este un error que se debe informar? Nunca le he informado nada a Django, por eso pregunto aquí primero. No puedo rastrear el error y decidir por mí mismo si se trata de un error o de una característica que podría implementarse.