Django Валидаторы ManyToManyField
Какое решение лучше всего подходит для выполнения следующего требования?
В моей модели есть 2 массивных поля (team1 и team2), которые содержат оба списка игроков. Если игрок находится в команде1, он не может быть также в команде2. Как реализовать эту логику?
Первая идея - использовать валидаторы, но они не поддерживаются manytomanyfield, как в django doc, поэтому я смиренно прошу вашей поддержки для лучшего решения, пожалуйста.
На данный момент ни одно представление не выставляет POST для создания матчей, и только администратор через django admin должен иметь возможность создавать матчи.
Здесь модель
class Match(models.Model):
date=models.DateField(auto_now_add=False,auto_now=False)
team1=models.ManyToManyField(Player,related_name='team_1')
team2=models.ManyToManyField(Player,related_name='team_2')
score_team1=models.IntegerField(
validators=[MinValueValidator(0)]
)
score_team2=models.IntegerField(
validators=[MinValueValidator(0)]
)
Идея может заключаться в том, чтобы работать с одним ManyToManyField
, и добавить пользовательскую таблицу переходов, которая определяет, какое поле используется, например:
class Match(models.Model):
date = models.DateField(auto_now_add=False, auto_now=False)
players = models.ManyToManyField(
Player, through='MatchPlay', related_name='matches'
)
score_team1 = models.IntegerField(validators=[MinValueValidator(0)])
score_team2 = models.IntegerField(validators=[MinValueValidator(0)])
@property
def players_team_1(self):
return self.players.filter(plays__team=False)
@property
def players_team_2(self):
return self.players.filter(plays__team=True)
class MatchPlay(models.Model):
match = models.ForeignKey(
Match, on_delete=models.CASCADE, related_name='plays'
)
player = models.ForeignKey(
Player, on_delete=models.CASCADE, related_name='plays'
)
team = models.BooleanField() # 0/1
class Meta:
constraints = [
models.UniqueConstraint(
fields=('match', 'player'), name='player_only_plays_on_one_team'
)
]
Здесь мы даже усиливаем это моделированием и через UniqueConstraint
, что вы не можете добавить одного и того же игрока второй раз, поэтому Player
обязательно будет играть за одну команду, за какую именно, зависит от BooleanField
, то есть False
за первую команду, а True
за вторую.
Это также может облегчить запросы для некоторых случаев, например, для поиска всех Match
игр, в которых играл Player
, независимо от того, была ли это первая или вторая команда.
Вы можете переопределить функцию сохранения и проверить, не назначен ли игрок в команду. Это первый подход, который я нашел:
def save(self, *args, **kwargs):
if not self.pk:
# Only execute this logic if the Match instance is being created (not updated)
players_in_team1 = self.team1.all()
players_in_team2 = self.team2.all()
for player in players_in_team1:
if player not in players_in_team2:
self.team2.add(player)
break
for player in players_in_team2:
if player not in players_in_team1:
self.team1.add(player)
break
else:
team1_players = self.team1.all()
team2_players = self.team2.all()
all_players = list(team1_players) + list(team2_players)
if len(set(all_players)) != len(all_players):
raise ValueError("Each player must be unique across both teams.")
super(Match, self).save(*args, **kwargs)