[python] Предполагается, что django prefetch_related работает с GenericRelation


0 Answers

Question

ОБНОВЛЕНИЕ: Открыт Отметить эту проблему: 24272

Что это?

Django имеет класс GenericRelation , который добавляет «обратное» общее отношение, чтобы включить дополнительный API .

Оказывается, мы можем использовать это reverse-generic-relation для filtering или ordering , но мы не можем использовать его внутри prefetch_related .

Мне было интересно, если это ошибка, или ее не должно работать, или что-то, что может быть реализовано в этой функции.

Позвольте мне показать вам несколько примеров, что я имею в виду.

Допустим, у нас есть две основные модели: Movies и Books .

  • Movies есть Director
  • Books есть Author

И мы хотим назначать теги нашим MovieTag и Books , но вместо использования MovieTag и BookTag мы хотим использовать один класс TaggedItem с GFK для Movie или Book .

Вот структура модели:

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

И некоторые исходные данные:

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

Итак, как показано в docs мы можем делать такие вещи.

>>> 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>]

Но, если мы попытаемся выполнить prefetch TaggedItem некоторых related data TaggedItem помощью этого reverse generic relation , мы собираемся получить AttributeError .

>>> TaggedItem.objects.all().prefetch_related('books')
Traceback (most recent call last):
  ...
AttributeError: 'Book' object has no attribute 'object_id'

Некоторые из вас могут спросить, почему я просто не использую content_object вместо books здесь? Причина в том, что это работает только тогда, когда мы хотим:

1) prefetch только один уровень в глубину от querysets содержащих различный тип content_object .

>>> TaggedItem.objects.all().prefetch_related('content_object')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: action movie>]

2) prefetch много уровней, но из querysets содержащих только один тип content_object .

>>> TaggedItem.objects.filter(books__author__name='E L James').prefetch_related('content_object__author')
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>]

Но, если мы хотим как 1), так и 2) (для prefetch многих уровней из queryset содержащих разные типы content_objects , мы не можем использовать content_object .

>>> TaggedItem.objects.all().prefetch_related('content_object__author')
Traceback (most recent call last):
  ...
AttributeError: 'Movie' object has no attribute 'author_id'

Django считает, что все content_objects являются Books , и, следовательно, у них есть Author .

Теперь представьте себе ситуацию, когда мы хотим prefetch не только books с их author , но и movies с их director . Вот несколько попыток.

Глупый способ:

>>> TaggedItem.objects.all().prefetch_related(
...     'content_object__author',
...     'content_object__director',
... )
Traceback (most recent call last):
  ...
AttributeError: 'Movie' object has no attribute 'author_id'

Может быть, с пользовательским объектом 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.

Здесь показаны некоторые решения этой проблемы. Но это масса массажа, который я хочу избежать. Мне очень нравится API, исходящий из reversed generic relations , было бы очень хорошо, если бы можно было делать prefetchs :

>>> TaggedItem.objects.all().prefetch_related(
...     'books__author',
...     'movies__director',
... )
Traceback (most recent call last):
  ...
AttributeError: 'Book' object has no attribute 'object_id'

Или вот так:

>>> 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'

Но, как вы можете видеть, мы получаем AttributeError . Я использую Django 1.7.3 и Python 2.7.6 . И мне интересно, почему Django бросает эту ошибку? Почему Django ищет object_id в модели Book ? Почему я думаю, что это может быть ошибка? Обычно, когда мы запрашиваем prefetch_related чтобы что-то решить, он не может, мы видим:

>>> 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()

Но здесь все иначе. Django фактически пытается разрешить отношение ... и терпит неудачу. Это ошибка, о которой следует сообщить? Я никогда ничего не сообщал Django, поэтому я сначала прошу здесь. Я не могу отследить ошибку и решить для себя, если это ошибка, или функция, которая может быть реализована.




Related