select_related generic example python django django-models django-orm

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 la GenericRelation de GenericRelation y GenericForeignKey .

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 un Director
  • Books tienen un Author

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.