Django 关系后向使用 _set 不起作用的情况
我有以下这些数据库表:
class Story(models.Model):
user = models.ForeignKey(User)
group = models.ForeignKey(Group, blank=True, null=True)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
location = models.CharField(max_length=100)
title = models.CharField(max_length=150)
description = models.CharField(blank=True, null=True, max_length=2000)
exp_text = models.TextField()
category = models.ForeignKey(Category, blank=True, null=True)
def __unicode__(self):
return self.title
class Comment(models.Model):
user = models.ForeignKey(User)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
emailOnReply = models.NullBooleanField(blank=True, null=True)
comment_text = models.TextField()
story = models.ForeignKey(Story)
def __unicode__(self):
return self.comment_text
当我创建了一个评论对象(Comment)并想要访问一个故事的标题时,我本来希望能这样做:
c = Comment.objects.all()
for com in c:
com.comment_set.title
但是Django却说comment_set没有定义。在文档中提到,如果你想访问另一个数据库表中的字段,而这个字段的外键没有定义,你可以使用_set方法,前面那个词是类的名称。
经过尝试不同的方法,我发现这样做是有效的:
c = Comment.objects.all()
for com in c:
com.story.title
因为外键是在Comment中定义的,我不明白这怎么能工作,但确实有效,为什么_set不工作呢?因为我是在一个外键已经定义的模型对象上工作,而我需要去访问故事,所以根据文档我应该使用_set……当我处理故事对象时,我能直接引用评论模型,并在定义related_name属性时不需要使用_set,为什么在这里_set不工作呢?
更新:
我现在在使用故事对象时,成功地反向建立了关系,通过以下方式引用评论类:
s = Story.objects.all()
for st in s:
print st.comment_set.all()
我之前用的是st.story_set而不是st.comment_set,但我还是觉得这很奇怪,为什么这样就能工作:
c = Comment.objects.all()
for com in c:
print com.story.title
当我试图从故事对象出发时,我并没有任何指向评论表的外键(只有评论表中外键的related name),所以看起来我没有相同的访问权限。
2 个回答
在Django中,处理关系有点隐晦。下面是你模型的简化版本:
class Story(models.Model): # instances have access to Manager comment_set (backward)
title = models.CharField(max_length=150)
description = models.CharField(blank=True, null=True, max_length=2000)
class Comment(models.Model): # has access to Story (forward)
comment_text = models.TextField()
story = models.ForeignKey(Story) # stored in database as story_id, which refers to (implicit) id column in Story
你的数据库看起来会是这样的(除非你另有指定):
Story (table)
id # automatically inserted by Django, unless you specify otherwise
title
description
Comment (table)
id # automatically inserted by Django, unless you specify otherwise
comment_text
story_id # points back to id in Story table
正向关系
Comment
(评论)实例可以通过将Comment
的story_id列与Story
(故事)的id列匹配来访问Story
。这里的id列是Django表格中隐含的,不在你的模型里,但肯定在你的数据库表中,除非你另有指定。
>>> c = Comment.objects.get(comment_text='hello, world')
>>> c.story.title # c => story_id => row in Story where id == c.story_id
'first post'
反向关系
Comment
有一个外键指向Story
,所以Story
的实例可以通过一个叫comment_set
的管理器来访问与Comment
的关系。
>>> s = Story.objects.get(title='first post')
>>> s.comment_set.comment_text # accesses Comment model from Story instance
'hello, world'
如果你想遍历所有的comment_set
,就像你在评论中提到的,可以试试这个:
>>> s = Story.objects.get(title='first post') # returns a single, non-iterable query object
>>> for story in s.comment_set.all(): # each comment_set object can have more than one item
print story.comment_text # story does not have a comment_set attribute
'hello, world' # my comment set here just happens to have one item
更新
或者,基于你的评论,如果你想把遍历提升到一个更高的层次,可以试试以下方法:
>>> s = Story.objects.all()
>>> for story in s: # iterate over top level
>>> for row in story.comment_set.all():
>>> print row.comment_text # again, row does not have a comment_set attribute
'hello, world' # again, my comment set has only one item
其实一切都按预期在运行。
评论对象并没有一个评论的外键,它有一个“故事”的外键。评论是“指向”故事的。这样一来,一个评论只对应一个故事,但一个故事可以有一组评论。
这就是为什么 st.comment_set
可以工作的原因——因为它是“向后查找”的,查看指向它的评论,而一个评论则直接指向它相关的故事(也就是 com.story
)。
如果你想了解为什么这样工作,可以看看这里:
https://docs.djangoproject.com/en/dev/topics/db/queries/#how-are-the-backward-relationships-possible
这也是让Django的ORM如此酷炫的一部分……