Django ORM玩家模型save方法查询逻辑问题及重复插入错误求助
Django ORM玩家模型save方法查询逻辑问题及重复插入错误求助
我有一个Django模型Player,它的save方法是这样的:
def save(self, *args, **kwargs): """ Overrides the save method to convert the 'name' attribute to uppercase before saving. """ player = Player.objects.filter( Q(fide_id=self.fide_id, fide_id__isnull=False) | Q(lichess_username=self.lichess_username, lichess_username__isnull=False) | Q( name=self.name, email=self.email, email__isnull=False, name__isnull=False, ) ) if player.exists(): p = player.first() for k, v in self.__dict__.items(): if k == "_state": continue if v is None: self.__dict__[k] = p.__dict__[k] if self.check_lichess_user_exists(): self.get_lichess_user_ratings() print(f"id: {self.id}") super().save(*args, **kwargs)
这段代码的意图是查找符合以下任一条件的玩家:
- 相同的
fide_id(且该字段不为空,避免把两个fide_id为空的玩家判定为同一个) - 相同的
lichess_username(同上) - 相同的
name和email组合(两个字段都不为空)
如果找到这样的玩家,就把当前要添加的玩家中所有值为None的字段,替换成数据库中已有玩家的对应字段值。按照Django的文档说明,调用super().save()应该会覆盖同id的玩家——因为当对象的主键不为None时,Django会执行UPDATE操作。
但我运行测试时遇到了问题,我的测试代码是这样的:
def setUp(self): # create Tournament tournament_name = 'tournament_01' tournament = Tournament.objects.create( name=tournament_name, tournament_type=TournamentType.DOUBLEROUNDROBIN) # create round round_name = 'round_01' self.round = Round.objects.create( name=round_name, tournament=tournament) # create two players self.players = [] player = Player.objects.create( lichess_username='alpega') tournament.players.add(player) self.players.append(player) player = Player.objects.create( lichess_username='fernanfer') tournament.players.add(player) self.players.append(player) tournament.save()
测试时多次出现如下错误:
Traceback (most recent call last): File "test_models_game.py", line 27, in setUp player = Player.objects.create( ^^^^^^^^^^^^^^^^^^^^^^ (... SQL cursor errors) django.db.utils.IntegrityError: UNIQUE constraint failed: chess_models_player.id
错误发生在第27行:player = Player.objects.create(lichess_username='fernanfer')
不过,如果我把查询逻辑换成更直观的写法,代码就能正常运行:
query = Q() if self.fide_id: query |= Q(fide_id=self.fide_id) if self.lichess_username: query |= Q(lichess_username=self.lichess_username) if self.name and self.email: query |= Q(name=self.name) & Q(email=self.email) player = Player.objects.filter(query)
现在我有两个疑问:
- 为什么第一种查询逻辑会出错,第二种却正常?看起来两种逻辑是等价的啊?
- 为什么Django没有执行UPDATE操作来覆盖已有主键的玩家,反而尝试插入新记录?
我查到了Django文档里的这段内容:
The primary key field is read-only. If you change the value of the primary key on an existing object and then save it, a new object will be created alongside the old one.
但不确定这是否适用于我的情况。
备注:内容来源于stack exchange,提问作者Chuwee
相关产品推荐
相关产品推荐

