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)
Тогда он работает просто отлично. Тогда у меня возникает два вопроса.
- Почему первый запрос не работает, а второй работает? Логика в нем вроде бы правильная.
- Почему Django не делает SQL UPDATE для игрока с id, который должен быть первичным ключом, вместо того, чтобы пытаться вставить нового?
Я нашел вот это: https://docs.djangoproject.com/en/2.2/ref/models/fields/#primary-key
Поле первичного ключа доступно только для чтения. Если вы измените значение первичного ключа в существующем объекте, а затем сохраните его, то наряду со старым объектом будет создан новый.
Но я не уверен, что это применимо здесь