试图了解 Django 外键的工作原理

Trying to understand how a Django foreign key works

所以我现在正在构建我的第一个 Django 项目,一个双人国际象棋网站。我快完成了,现在我正在编写测试。该项目有两个原始模型:Player 和 Game。 Player 模型与 User 模型有一个一对一的字段 Player.user,还有一个 Game 的外键 Player.current_game,表示玩家当前所在的游戏实例。 Game.draw_offered CharField 取值 ""(默认值)、"w" 或 "b",指示是否提供平局以及提供平局的玩家的颜色。我有一个执行以下操作的测试用例:

class Draw_ViewTestCase(TestCase):

    def setUp(self):
        self.user1 = User.objects.create_user(username="Test_User_1", password="test1")
        self.user2 = User.objects.create_user(username="Test_User_2", password="test2")
        self.factory = RequestFactory()
        self.game = Game.objects.create()
        self.player1 = Player.objects.create(user=self.user1, current_game=self.game, color="w")
        self.player2 = Player.objects.create(user=self.user2, current_game=self.game, color="b")

    def test_draw(self): 
        request = self.factory.get(reverse('draw'))
        request.user = self.user1
        #The draw view changes the draw_offered field of the player's current game to the player's color, and saves the current_game to the database
        response = draw(request)
        self.game.refresh_from_db()
        assert self.game.draw_offered == self.player1.color
        assert self.game == self.player2.current_game
        #self.player2.current_game.refresh_from_db()
        assert self.game.draw_offered == self.player2.current_game.draw_offered

除最后一个断言外,所有断言都通过了,但是如果您取消对倒数第二行的注释,它就会通过。

这是怎么回事?据我了解,当您引用外键属性 self.player2.current_game 时,Django 执行数据库查找并 returns 具有最新字段的 Game 实例。由于 self.game 和 self.player2.current_game 对应数据库中同一条 Game 记录,而 self.game.refresh_from_db() 只是被调用,你会认为 self.game.draw_offered == self.player2.current_game.draw_offered 将评估为 True——两个 Game 实例都引用相同的数据库记录并具有最新字段。我必须调用 self.player2.current_game.refresh_from_db() 才能使断言通过这一事实对我来说没有意义——根据我对 Django 外键的理解,self.player2.current_game 应该自动与数据库保持同步。

测试 运行ner 将在每次测试之前 运行 setUp 方法,因此您在 运行ning 代码之前对实例进行操作。 Unitest 文档中对此进行了很好的解释。

您没有从数据库访问新的 player2 实例,只是加载它,正如 setUp 方法中定义的那样。

这与外键无关。

您错过的是仅仅因为两个实例指向同一个数据库记录,并不意味着它们是同一个对象。正如您必须刷新 self.game 以查看对 draw 视图中的基础记录所做的更改一样,您还需要刷新 self.player2.current_game 以便它也获得那些更新的值.

如果 Django 认为它已经拥有外键,则不会进行数据库调用以查找外键:而且确实如此,因为您最初创建 self.player1self.player2 是通过传入整个游戏对象。所以是的,您需要显式刷新 current_game 对象以查看所做的更改。

如果您真的想要,您可以通过传递 ID 而不是完整的对象来创建对象而无需执行此操作:self.player2 = Player.objects.create(...current_game_id=self.game.id...)。这样,Django 实际上不会缓存对象,因此它需要在测试结束时查询它。