Django 关系后向使用 _set 不起作用的情况

3 投票
2 回答
953 浏览
提问于 2025-04-17 22:26

我有以下这些数据库表:

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 个回答

0

在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
1

其实一切都按预期在运行。
评论对象并没有一个评论的外键,它有一个“故事”的外键。评论是“指向”故事的。这样一来,一个评论只对应一个故事,但一个故事可以有一组评论。

这就是为什么 st.comment_set 可以工作的原因——因为它是“向后查找”的,查看指向它的评论,而一个评论则直接指向它相关的故事(也就是 com.story)。

如果你想了解为什么这样工作,可以看看这里:
https://docs.djangoproject.com/en/dev/topics/db/queries/#how-are-the-backward-relationships-possible 这也是让Django的ORM如此酷炫的一部分……

撰写回答