python - example - Se supone que django prefetch_related funciona con GenericRelation
django generic relation (1)
Si desea recuperar instancias de Book
y Book.objects.prefetch_related(''tags'')
las etiquetas relacionadas, use Book.objects.prefetch_related(''tags'')
. No es necesario usar la relación inversa aquí.
También puede echar un vistazo a las pruebas relacionadas en el código fuente de Django .
Además, la documentación de Django establece que se supone que prefetch_related()
funciona con GenericForeignKey
y GenericRelation
:
prefetch_related
, por otro lado, hace una búsqueda separada para cada relación, y hace la ''unión'' en Python. Esto le permite captar previamente objetos de muchos a muchos y muchos a uno, lo que no se puede hacer usando select_related, además de la clave externa y las relaciones uno a uno que son compatibles con select_related. También es compatible con laGenericRelation
deGenericRelation
yGenericForeignKey
.
ACTUALIZACIÓN: para TaggedItem
para un elemento TaggedItem
, puede usar TaggedItem.objects.all().prefetch_related(''content_object'')
, si desea limitar el resultado a solo objetos del Book
etiquetados, también podría filtrar para el ContentType
(no estoy seguro si prefetch_related
funciona con el related_query_name
). Si también desea obtener el Author
junto con el libro, necesita usar select_related()
no prefetch_related()
ya que esta es una relación ForeignKey
, puede combinar esto en una consulta prefetch_related()
:
from django.contrib.contenttypes.models import ContentType
from django.db.models import Prefetch
book_ct = ContentType.objects.get_for_model(Book)
TaggedItem.objects.filter(content_type=book_ct).prefetch_related(
Prefetch(
''content_object'',
queryset=Book.objects.all().select_related(''author'')
)
)
ACTUALIZACIÓN: una respuesta abierta sobre este problema: 24272
¿De qué se trata?
Django tiene una clase GenericRelation , que agrega una relación genérica "inversa" para habilitar una API adicional.
Resulta que podemos usar esta reverse-generic-relation
para filtering
u ordering
, pero no podemos usarla dentro de prefetch_related
.
Me preguntaba si esto es un error, o no debería funcionar, o es algo que se puede implementar en la función.
Déjame mostrarte algunos ejemplos a lo que me refiero.
Digamos que tenemos dos modelos principales: Movies
y Books
.
-
Movies
tienen unDirector
-
Books
tienen unAuthor
Y queremos asignar etiquetas a nuestras Movies
y Books
, pero en lugar de utilizar los modelos MovieTag
y BookTag
, queremos utilizar una sola clase TaggedItem
con un GFK
para Movie
o Book
.
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 muestran los docs , 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 de TaggedItem
algunos related data
de TaggedItem
través de esta reverse generic relation
, vamos a obtener un AttributeError .
>>> 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 uso content_object
lugar de books
aquí? La razón es que esto solo funciona cuando queremos:
1) querysets
solo un nivel de profundidad de querysets
de querysets
contengan diferentes tipos de content_object
.
>>> TaggedItem.objects.all().prefetch_related(''content_object'')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: action movie>]
2) prefetch
muchos niveles, pero desde querysets
contienen solo un tipo de content_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) (para queryset
muchos niveles de queryset
contienen diferentes tipos de content_objects
, no podemos usar content_object
.
>>> TaggedItem.objects.all().prefetch_related(''content_object__author'')
Traceback (most recent call last):
...
AttributeError: ''Movie'' object has no attribute ''author_id''
Django
cree que todos los content_objects
son Books
y, por lo tanto, tienen un Author
.
Ahora imagine la situación en la que queremos prefetch
no solo los books
con su author
, sino también las movies
con su director
. 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 un objeto Prefetch
personalizado?
>>>
>>> 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.
Algunas soluciones de este problema se muestran here . Pero eso es mucho masaje sobre los datos que quiero evitar. Realmente me gusta que la API provenga de las reversed generic relations
, sería muy bueno poder hacer prefetchs
como ese:
>>> TaggedItem.objects.all().prefetch_related(
... ''books__author'',
... ''movies__director'',
... )
Traceback (most recent call last):
...
AttributeError: ''Book'' object has no attribute ''object_id''
O así:
>>> 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 puede ver, siempre obtenemos AttributeError . Estoy usando Django 1.7.3
y Python 2.7.6
. Y me pregunto por qué Django está arrojando ese error. ¿Por qué Django está buscando un object_id
en el modelo Book
? ¿Por qué creo que esto puede ser un error? Por lo general, cuando solicitamos que prefetch_related
resuelva 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 realmente intenta resolver la relación ... y falla. ¿Es esto un error que debería ser reportado? Nunca le informé nada a Django, así que es por eso que pregunto aquí primero. No puedo rastrear el error y decidir por mí mismo si se trata de un error o una característica que podría implementarse.