带有指定字段的 Django select_related 打破了多个一对一关系

Posted

技术标签:

【中文标题】带有指定字段的 Django select_related 打破了多个一对一关系【英文标题】:Django select_related with fields specified breaks over multiple one to one relationships 【发布时间】:2011-10-16 19:08:01 【问题描述】:

我在尝试 select_related 多个 OneToOneField 关系时遇到了一个奇怪的错误,例如在目标字段是孙子类的情况下。我希望有人能帮助我了解发生了什么(或确认这是 Django 中的一个错误)。

插图:

# models.py
from django.db import models

class A(models.Model):
    pass

class B(A):
    pass

class C(B):
    pass

够简单吧?现在我用干净的数据库打开 Django shell:

>>> C().save()
>>> A.objects.select_related('b__c')
[]

等等,什么?为什么那个查询集是空的?快速健全性检查:

>>> A.objects.select_related('b')[0].b.c
<C: C object>

那么为什么 select_related 调用不起作用?好吧,看这个:

>>> A.objects.select_related('b__c').__iter__().next()
... 
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/query.py", line 107, in _result_iter
    self._fill_cache()
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/query.py", line 772, in _fill_cache
    self._result_cache.append(self._iter.next())
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/query.py", line 273, in iterator
    for row in compiler.results_iter():
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/sql/compiler.py", line 680, in results_iter
    for rows in self.execute_sql(MULTI):
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/sql/compiler.py", line 725, in execute_sql
    sql, params = self.as_sql()
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/sql/compiler.py", line 58, in as_sql
    self.pre_sql_setup()
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/sql/compiler.py", line 29, in pre_sql_setup
    self.fill_related_selections()
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/sql/compiler.py", line 661, in fill_related_selections
    used, next, restricted, new_nullable)
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/sql/compiler.py", line 617, in fill_related_selections
    chain = opts.get_base_chain(f.rel.to)
  File "/opt/webapps/asdf/lib/python2.6/site-packages/django/db/models/options.py", line 452, in get_base_chain
    % model._meta.module_name,)
TypeError: 'b' is not an ancestor of this model
>>> 

那么,这是一个 Django 错误,还是我不明白什么?

【问题讨论】:

鉴于对此保持沉默,我提交了一个错误:code.djangoproject.com/ticket/16572 遇到了完全相同的问题。奇怪的行为,确实像一个 django 错误。 【参考方案1】:

我将 name=CharField 添加到 A 并在 shell 中运行以下测试:

>>> a = A(name='a_object')
>>> a.save()
>>> b = B(name='b_object')
>>> b.save()
>>> c = C(name='c_object')
>>> c.save()

>>> A.objects.all()
[<A: A object>, <A: A object>, <A: A object>]
>>> B.objects.all()
[<B: B object>, <B: B object>]
>>> C.objects.all()
[<C: C object>]

>>> A.objects.select_related('b__c')
[]
>>> A.objects.select_related('b__c').__iter__().next()
Traceback ....
    ...
    TypeError: 'b' is not an ancestor of this model

>>> d = A.objects.select_related('B__C')
>>> for item in d:
...     print item.name
... 
a_object
b_object
c_object

>>> test = A.objects.select_related('B__C').__iter__().next()
>>> test.name
u'a_object'

我知道这不是一个答案,我不知道该怎么做。但基本上我发现小写字母似乎没有模型中没有字段的任何含义。

【讨论】:

小写字母是用于将对象向下转换为子类实例的字段。例如,A.objects.get(id=2).b 将返回 id=2 的 B 对象(如果存在),否则引发异常。同样, A.objects.get(id=3).b.c 将返回一个 C 对象(假设它存在)。我提交的票实际上已被确认并接受,所以我认为这是一个错误。【参考方案2】:

遇到同样的问题,但模型结构有很大不同:

class A(models.Model):
    b = models.ForeignKey(B)
....
class B(models.Model):
....
class C(models.Model):
    b = models.OneToOneField(B)
    d = models.OneToOneField(D)
....
class D(models.Model):
....

所以,当我写作时

A.objects.select_related('b__c__d').all()

我没有看到所有 A - 对象,只有那些 C 不为空的对象。 但是,当我写

A.objects.select_related('b__c').all()

Django ORM 返回所有 A 对象,包括 C 为空的对象。

对我来说似乎是一个错误,select_related 不应该用作过滤器。

【讨论】:

以上是关于带有指定字段的 Django select_related 打破了多个一对一关系的主要内容,如果未能解决你的问题,请参考以下文章

如何使用'select_related'从相关(ForeignKey)django模型中接收并非所有字段

Django:select_related到一个表vs表的字段

Django select_related 和字段查找

Django select_related 不显示所有字段,但原始 SQL 显示

Django 管理员内联:select_related

如何使用 select_related 获取 ManyToMany 字段值