UPDATE:关于这个问题的公开信:24272
这是怎么回事?
Django有一个GenericRelation类,它增加了一个“reverse” generic relationship类来启用另一个API类.
事实证明,我们可以用reverse-generic-relation
来表示filtering
或ordering
,但不能用prefetch_related
来表示.
我想知道这是不是一个错误,或者它不应该工作,或者这是可以在功能中实现的东西.
让我举几个例子告诉你我的意思.
假设我们有两个主要型号:Movies
和Books
.
-
Movies
分有Director
分 -
Books
分有Author
分
我们希望为Movies
和Books
分配标签,但不使用MovieTag
和BookTag
型号,而是使用GFK
到Movie
或Book
的单个TaggedItem
类.
以下是模型 struct :
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>]
但是,如果我们试着用这reverse generic relation
来计算TaggedItem
中的related data
,我们会得到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)(从queryset
到prefetch
多个级别,包含不同类型的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
.
现在想象一下这样的情况,我们想要的不仅仅是books
与他们的author
,还有movies
与他们的director
.这里有几次try .
愚蠢的方式:
>>> 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.
这个问题的一些解决方案如图here所示.但这是我想要避免的数据上的大量信息.
我真的很喜欢来自reversed generic relations
的API,如果能像这样做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.
>>> 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 报告过任何事情,所以这就是为什么我先在这里问的原因.我无法跟踪错误,无法自己决定这是一个错误,还是可以实现的功能.