Должен ли django prefetch_related работать с GenericRelation
ОБНОВИТЬ: Open Ticked об этой проблеме:24272
О чем все?
Джанго имеетGenericRelation класс, который добавляет«Обратные» родовые отношения включить дополнительныйAPI.
Оказывается, мы можем использовать этоreverse-generic-relation
заfiltering
или жеordering
, но мы не можем использовать его внутриprefetch_related
.
Мне было интересно, если это ошибка, или она не должна работать, или это что-то, что может быть реализовано в функции.
Позвольте мне показать вам на некоторых примерах, что я имею в виду.
Допустим, у нас есть две основные модели:Movies
а такжеBooks
.
Movies
иметьDirector
Books
естьAuthor
И мы хотим присвоить теги нашимMovies
а также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')
Так какдокументы покажи, что мы можем делать такие вещи.
>>> 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
немного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, Я использую Django1.7.3
и питон2.7.6
, И мне любопытно, почему Джанго выбрасывает эту ошибку? Почему Джанго ищет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()
Но здесь все по-другому. Джанго на самом деле пытается разрешить отношения ... и терпит неудачу. Это ошибка, о которой следует сообщить? Я никогда ничего не сообщал Джанго, поэтому я сначала спрашиваю здесь. Я не могу отследить ошибку и решить для себя, является ли это ошибкой или функцией, которая может быть реализована.