2015-01-24 5 views
24

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 eine Director
  • Books haben eine Author

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.

+2

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. –

+0

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

Antwort

20

Wenn Sie Book Instanzen abrufen und die zugehörigen Tags vorab abrufen möchten, verwenden Sie Book.objects.prefetch_related('tags'). Keine Notwendigkeit, die umgekehrte Beziehung hier zu verwenden.

Sie können sich auch die entsprechenden Tests in der Django source code ansehen.

Auch die Django documentation besagt, dass prefetch_related() soll mit GenericForeignKey und GenericRelation arbeiten:

prefetch_related , on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related. It also supports prefetching of GenericRelation and GenericForeignKey .

UPDATE: die content_object für eine TaggedItem Um Prefetch Sie TaggedItem.objects.all().prefetch_related('content_object') verwenden können, wenn Sie das Ergebnis begrenzen möchten nur getaggt Book Objekte, die Sie zusätzlich für die ContentType (nicht sicher, ob prefetch_related Arbeiten mit dem related_query_name) filtern könnte. Wenn Sie auch die Author zusammen mit dem Buch bekommen wollen, müssen Sie select_related() nicht prefetch_related() verwenden, da dies eine ist ForeignKey Beziehung, können Sie dies in einem custom prefetch_related() query kombinieren:

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') 
    ) 
) 
+0

Vielen Dank für Ihre Antwort @Bernhard. Das Problem ist, dass ich "Bücher" mit ihrem "Autor" aus einem "TaggetItem" 'queryset' **, das ** nicht nur 'Bücher' enthält," vorfabrizieren "muss. Dies bedeutet, ich brauche etwas wie dieses: TaggedItem.objects.all(). Prefetch_related ('books__author'). Aber das wirft einen seltsamen Fehler auf: ** AttributeError: 'Book'-Objekt hat kein Attribut' object_id '**. Darf ich um Ihre Meinung bitten? Sieht es für Sie wie ein Fehler aus, der gemeldet werden sollte? – Todor

+0

Einige Problemumgehungen dieses Problems sind [hier] (http://stackoverflow.com/questions/12466945/django-prefetch-related-objects-of-a-genericforeignkey) dargestellt. Aber das ist eine Menge von benutzerdefinierten Massage der Daten, die ich vermeiden möchte. Ich mag die API, die von den "umgekehrten generischen Beziehungen" kommt, und ich glaube, dass dies der Weg ist, eine solche Funktionalität zu erreichen. – Todor

+0

@Todor Ich habe meine Antwort aktualisiert, kann es jetzt nicht ausprobieren, aber ich hoffe, dass es Sie in die richtige Richtung weist ... Sie können es mit dem 'verwandten_Query_Name' ausprobieren sowie mit dem Filtern nach dem content_type ... –

Verwandte Themen