试图了解Django外键是如何工作的

时间:2017-09-03 18:36:29

标签: python sql django generic-foreign-key

所以我现在正在建立我的第一个Django项目,一个双人棋的网站。我差不多完成了,现在我正在写测试。该项目有两个原始模型:玩家和游戏。 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执行数据库查找并返回一个带有最新字段的Game实例。由于self.game和self.player2.current_game对应于数据库中的相同游戏记录,并且刚刚调用了self.game.refresh_from_db(),您会认为self.game.draw_offered == self.player2.current_game.draw_offered将评估为True--两个游戏实例都引用相同的数据库记录并具有最新的字段。事实上,我必须调用self.player2.current_game.refresh_from_db()来使断言传递对我没有意义 - 根据我对Django外键的理解,self.player2.current_game应该自动更新与数据库。

2 个答案:

答案 0 :(得分:2)

这不是关于外键的。

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

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

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

答案 1 :(得分:0)

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

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