Ist django prefetch_related funktionieren soll mit GenericRelation
UPDATE: Eine Offene Angekreuzt zu diesem Thema: Vier und zwanzig tausend zwei hundert zwei und siebzig
Was überhaupt geht?
Django hat eine GenericRelation Klasse, das fügt eine "reverse" generische Beziehung zu aktivieren, eine zusätzliche API.
Es stellt sich heraus, wir können mit dieser reverse-generic-relation
für filtering
oder ordering
, aber wir können Sie nicht verwenden es im prefetch_related
.
Ich Frage mich, ob das ein bug ist, oder nicht funktionieren sollte, oder Ihr etwas, das umgesetzt werden kann in der Funktion.
Lassen Sie mich Ihnen zeigen einige Beispiele, was ich meine.
Können sagen, wir haben zwei Modelle: Movies
und Books
.
Movies
haben eineDirector
Books
haben eineAuthor
Und wir möchten, zuweisen von tags zu unserem Movies
und Books
, aber statt mit MovieTag
und BookTag
Modelle, die wir wollen, zu einem einzigen TaggedItem
Klasse mit einem GFK
zu Movie
oder Book
.
Hier ist der Modell-Struktur:
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
Sowie einige erste Daten:
>>> 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')
So, wie die docs show, die wir tun können, Dinge wie diese.
>>> 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>]
Aber, wenn wir versuchen, uns prefetch
einige related data
von TaggedItem
über diese reverse generic relation
wir werden gehen, um eine AttributeError.
>>> TaggedItem.objects.all().prefetch_related('books')
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
Einige von Euch werden vielleicht Fragen, warum ich einfach nicht mit content_object
statt books
hier? Der Grund ist, weil diese nur arbeiten, wenn wir wollen:
1) prefetch
nur eine Ebene tief aus querysets
mit verschiedenen Art von content_object
.
>>> TaggedItem.objects.all().prefetch_related('content_object')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: action movie>]
2) prefetch
vielen Ebenen, aber von querysets
mit nur einem Typ von content_object
.
>>> TaggedItem.objects.filter(books__author__name='E L James').prefetch_related('content_object__author')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>]
Aber, wenn wir wollen, dass beide 1) und 2) (zu prefetch
vielen Ebenen von queryset
mit verschiedenen Arten von content_objects
, wir können nicht content_object
.
>>> TaggedItem.objects.all().prefetch_related('content_object__author')
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'author_id'
Django
denkt, dass alle content_objects
sind Books
, und so haben Sie eine Author
.
Stellen Sie sich nun vor der situation, wo wir wollen prefetch
nicht nur die books
mit Ihren author
, aber auch die movies
mit Ihren director
. Hier sind paar versuche.
Den dummen Weg:
>>> TaggedItem.objects.all().prefetch_related(
... 'content_object__author',
... 'content_object__director',
... )
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'author_id'
Vielleicht mit custom Prefetch
Objekt?
>>>
>>> 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.
Einige Lösungen für dieses problem sind gezeigt hier. Aber das ist eine Menge von massage über die Daten, die ich vermeiden möchte.
Ich mag die API kommt aus der reversed generic relations
wäre es sehr schön, in der Lage zu tun prefetchs
so:
>>> TaggedItem.objects.all().prefetch_related(
... 'books__author',
... 'movies__director',
... )
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
Oder so:
>>> 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'
Aber wie Sie sehen können, haben wir Erkenntnisse bekommen, dass AttributeError.
Ich bin mit Django 1.7.3
- und Python -2.7.6
. Und ich bin neugierig, warum Django wirft den Fehler? Warum ist Django auf der Suche für ein object_id
im Book
Modell?
, Warum ich denke, dass dies möglicherweise ein bug?
Normalerweise, wenn wir Fragen prefetch_related
zu lösen, etwas, was es nicht kann, sehen wir:
>>> 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()
Aber hier ist es anders. Django tatsächlich versucht aufzulösen, die Beziehung... - und scheitert. Ist das ein bug, der gemeldet werden sollte? Ich habe nie gemeldet, alles zu Django-deshalb Frage ich hier zuerst. Ich bin nicht in der Lage zu verfolgen, die Fehler und für mich entscheiden ob das ein bug ist oder ein feature, welches implementiert werden konnte.
- Ok, Blick auf die Django-Quelle, die ich würde sagen, dass dies nicht ein bug, sondern einfach nicht unterstützt... Wenn Sie wollen, um die Autoren der Bücher würden Sie verwenden müssen
select_related()
da dies einForeignKey
Beziehung. Verwenden Sie das zusammen mitprefetch_related
würden Sie brauchen, um eine benutzerdefinierte queryset, das ist derzeit nicht unterstützt von Django für generische Beziehungen. - Ok, danke dir. Ich ein ticket eröffnet über diese. Hoffe, eines Tages diese Funktion wird es durch, um den ORM : )
Du musst angemeldet sein, um einen Kommentar abzugeben.
Wenn Sie abrufen möchten
Book
Instanzen und prefetch-der Verwandte tags verwendenBook.objects.prefetch_related('tags')
. Keine Notwendigkeit, den umgekehrten Verhältnis hier.Können Sie auch einen Blick auf die zugehörigen tests in der Django-source-code.
Auch die Django-Dokumentation besagt, dass
prefetch_related()
funktionieren soll mitGenericForeignKey
undGenericRelation
:UPDATE: Zu prefetch-die
content_object
für eineTaggedItem
können SieTaggedItem.objects.all().prefetch_related('content_object')
, wenn Sie beschränken wollen, ist das Ergebnis nur taggedBook
Objekte, die Sie könnte zusätzlich filter für dieContentType
(nicht sicher, obprefetch_related
arbeitet mit derrelated_query_name
). Wenn Sie wollen auch Holen Sie sich dieAuthor
zusammen mit dem Buch, das Sie verwenden müssen,select_related()
nichtprefetch_related()
da dies einForeignKey
Beziehung, die Sie kombinieren können, diese in einem customprefetch_related()
query:prefetch
books
mit Ihrenauthor
aus einerTaggetItem
queryset
enthält nicht nurbooks
. Bedeutet dies, ich brauche so etwas wie dieses: TaggedItem.Objekte.alle().prefetch_related('books__author'). Aber das wirft eine seltsame Fehlermeldung: AttributeError: 'Buch' - Objekt hat kein Attribut 'object_id'. Darf ich Fragen, Eure Meinung über Sie? Es tut sieht Sie, wie ein bug, der gemeldet werden sollte?reversed generic relations
, und ich glaube, das ist die Möglichkeit, eine solche Funktionalität.related_query_name
sowie mit filtern für die content_type...related_query_name
funktionieren soll mitprefetch_related
. Ich habe es getestet mit Filterung aufcontent_type
, aber das problem ist, dass die ORM Suche nach einemobject_id
Attribut inBook
Modell anstatt inTaggetItem
Modell. Ich denke, ich bin goingo öffnen Sie ein ticket.content_type
. Ich habe die Arbeit mitTaggedItems
mitcontent_types
TypBook
oderMovie
oder etwas anderes.content_type
weil ich das Gefühl das nicht viel ist klar jetzt.Custom queryset can't be used for this lookup.
prefetch_related_objects
zur Rettung.Ab 1.10 Django (Hinweis: es noch Geschenke in den früheren Versionen, aber war nicht Teil der öffentlichen API.), wir können prefetch_related_objects zu teilen und zu erobern ist unser problem.
prefetch_related
ist eine operation, wo Django holt bezogenen Daten nach den queryset bewertet wurde (mache eine zweite Abfrage nach der wichtigste bewertet wurde). Und um zu arbeiten, es erwartet, dass die Elemente in den queryset homogen sein (die gleiche Art). Der Hauptgrund der Rückseite generic generation jetzt nicht direkt funktioniert, ist, dass wir Objekte aus verschiedenen Arten von Inhalten, und der code ist noch nicht smart genug ist, zu trennen die Strömung für die verschiedenen Arten von Inhalten.Nun mit
prefetch_related_objects
wir tun, holt sich nur auf eine Teilmenge unserer queryset, wo alle Elemente homogen sein. Hier ist ein Beispiel:ValueError: Custom queryset can't be used for this lookup.
. Und auch Sie haben syntax-Fehler mit den fehlenden Klammern.Prefetch
Objekt scheint nicht die Arbeit mitGenericForeignKey
, daher können wir nicht tunselect_related
auf dem Buch/Film querysets abholen, Autor/Regisseur.