UPDATE arbeiten: An Open tickte zu diesem Problem: 24272Ist prefetch_related django soll mit GenericRelation
Was überhaupt geht?
Django hat eine GenericRelation-Klasse, die eine „reverse“ generische Beziehung fügt ein zusätzliches API zu ermöglichen.
Es stellt sich heraus, dass wir diese reverse-generic-relation
für filtering
oder ordering
verwenden können, aber wir können es nicht innerhalb prefetch_related
verwenden.
Ich frage mich, ob dies ein Fehler ist, oder es sollte nicht funktionieren, oder es ist etwas, das in der Funktion implementiert werden kann.
Lassen Sie mich Ihnen mit einigen Beispielen zeigen, was ich meine.
Sagen wir, wir haben zwei Hauptmodelle: Movies
und Books
.
Movies
haben eineDirector
Books
haben eineAuthor
Und wir wollen Tags in unseren Movies
und Books
, zuweisen, aber statt mit MovieTag
und BookTag
Modelle wollen wir einen einzigen verwenden TaggedItem
Klasse mit GFK
bis Movie
oder Book
. Hier
ist die Modellstruktur:
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
Und einige Anfangsdaten:
>>> 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 zeigen wir können Sachen wie diese tun.
>>> 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 zu prefetch
einige related data
von TaggedItem
über diese reverse generic relation
versuchen, werden wir eine Attribute bekommen.
>>> TaggedItem.objects.all().prefetch_related('books')
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
Einige von euch fragen, warum ich content_object
statt books
hier nicht verwenden nur? Der Grund ist, weil dies nur funktioniert, wenn wir wollen:
1) prefetch
nur eine Ebene tief von enthält verschiedene Arten von content_object
.
>>> TaggedItem.objects.all().prefetch_related('content_object')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: action movie>]
2) prefetch
viele Ebenen, sondern aus querysets
nur eine Art von content_object
enthält.
Aber, wenn wir beide 1 wollen) und 2) (bis prefetch
viele Ebenen von queryset
verschiedenen Arten von content_objects
enthalten, können wir nicht content_object
verwenden.
>>> 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 jetzt die Situation vor, wo wirwollennicht nur die books
mit ihrer author
, sondern auch die movies
mit ihrer director
. Hier sind einige Versuche.
Die dumme Art und Weise:
>>> 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 Objekt mit benutzerdefinierten Prefetch
?
>>>
>>> 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 dieses Problems werden here gezeigt. Aber das ist eine Menge Massage über die Daten, die ich vermeiden möchte. Ich mag die API von der reversed generic relations
kommen, wäre es sehr schön sein, um prefetchs
wie das zu tun:
>>> TaggedItem.objects.all().prefetch_related(
... 'books__author',
... 'movies__director',
...)
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
Oder wie folgt aus:
>>> 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, wir aways bekommen das AttributeError. Ich benutze Django 1.7.3
und Python 2.7.6
. Und ich bin neugierig, warum Django diesen Fehler wirft? Warum sucht Django im Book
Modell nach einem object_id
? Warum denke ich, dass dies ein Fehler sein könnte? Normalerweise wenn wir prefetch_related
bitten, etwas zu lösen, es kann nicht, wir sehen:
>>> 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 versucht tatsächlich, die Beziehung aufzulösen ... und scheitert. Ist das ein Fehler, der gemeldet werden sollte? Ich habe Django noch nie etwas gemeldet, deshalb frage ich zuerst hier. Ich kann den Fehler nicht nachvollziehen und entscheide selbst, ob es sich um einen Fehler oder um ein Feature handelt, das implementiert werden kann.
Ok am Django Quelle suchen Ich würde sagen, Dies ist kein Bug, aber einfach nicht unterstützt ... Wenn Sie die Autoren mit den Büchern erhalten möchten, müssten Sie 'select_related()' verwenden, da dies eine 'ForeignKey'-Beziehung ist. Um dies zu nutzen, zusammen mit 'prefetch_related' müssen Sie eine benutzerdefinierte queryset verwenden, die derzeit [nicht unterstützt] (https://github.com/django/django/blob/84b6c768301096849f5589d7612b129c5445abd3/django/contrib/contenttypes/fields.py# L170) von Django für generische Beziehungen. –
Ok, danke. Ich habe [ein Ticket geöffnet] (https://code.djangoproject.com/ticket/24272) darüber. Hoffe eines Tages wird diese Funktion es durch das ORM machen:) – Todor