【发布时间】:2015-03-23 12:13:16
【问题描述】:
更新:关于此问题的公开标记:24272
到底是怎么回事?
Django 有一个GenericRelation 类,它添加了一个“反向”通用关系以启用额外的API。
事实证明,我们可以将这个reverse-generic-relation 用于filtering 或ordering,但我们不能在prefetch_related 中使用它。
我想知道这是否是一个错误,或者它不应该工作,或者它可以在该功能中实现。
让我用一些例子告诉你我的意思。
假设我们有两个主要模型:Movies 和 Books。
-
Movies有一个Director -
Books有一个Author
我们想给我们的Movies 和Books 分配标签,但是我们不想使用MovieTag 和BookTag 模型,而是使用一个TaggedItem 类和GFK 到@987654343 @ 或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>]
但是,如果我们尝试通过这个reverse generic relation prefetch 或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 到包含不同类型 content_objects 的多个级别的 prefetch,我们不能使用 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.
这个问题的一些解决方案显示在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。
我正在使用 Django 1.7.3 和 Python 2.7.6。我很好奇为什么 Django 会抛出这个错误?为什么 Django 在 Book 模型中搜索 object_id?
为什么我认为这可能是一个错误?
通常,当我们要求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 报告过任何事情,所以这就是我首先在这里询问的原因。我无法追踪错误并自行决定这是错误还是可以实现的功能。
【问题讨论】:
-
好的,看看 Django Source 我会说这不是一个错误,但根本不支持......如果你想获得作者的书籍,你需要使用
select_related()这样是ForeignKey关系。要将其与prefetch_related一起使用,您需要使用自定义查询集,目前 Django 的 not supported 用于通用关系。 -
好的,谢谢。我opened a ticket 关于这个。希望有一天这个功能能够通过 ORM :)
标签: python django django-models django-orm