Django: Подстановка игроков в ORM, проверка __isnull=False против None

У меня есть 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 (который не должен быть null, так как тогда я буду считать двух игроков с null значениями в их fide id одним и тем же)
  • Одинаковое имя пользователя lichess_username (idem)
  • Одинаковая комбинация имени пользователя и электронной почты (idem)

Затем, если такой игрок существует, то установите каждое поле добавляемого игрока (если оно None) в поля игрока, уже находящегося в базе данных. Затем super().save() должен перезаписать игрока с тем же id, согласно документации Django:

ничего

не обновил

(например,

если первичный ключ установлен в значение, которое не существует в базе данных), Django выполняет INSERT.

Если атрибут первичного ключа объекта определяет значение по умолчанию или db_default то Django выполняет UPDATE, если это существующий экземпляр модели и первичный ключ установлен на значение, существующее в базе данных. В противном случае, Django выполняет INSERT.

Единственная загвоздка здесь заключается в том, что вы должны быть осторожны и не указывать значение primary-key явно при сохранении новых объектов, если вы не можете гарантировать, что значение первичного ключа не используется. Подробнее об этом нюансе, см. в разделах Явное указание значений автопервичного ключа выше и Принудительное выполнение INSERT или 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 не делает SQL UPDATE для игрока с id, который должен быть первичным ключом, вместо того, чтобы пытаться вставить нового?

Я нашел вот это: https://docs.djangoproject.com/en/2.2/ref/models/fields/#primary-key

Поле первичного ключа доступно только для чтения. Если вы измените значение первичного ключа в существующем объекте, а затем сохраните его, то наряду со старым объектом будет создан новый.

Но я не уверен, что это применимо здесь

Вернуться на верх