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

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 ein ForeignKey Beziehung. Verwenden Sie das zusammen mit prefetch_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 : )
InformationsquelleAutor Todor | 2015-01-24
Schreibe einen Kommentar