You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

现在我有两个疑问:

  1. 为什么第一种查询逻辑会出错,第二种却正常?看起来两种逻辑是等价的啊?
  2. 为什么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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 19:33:14