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)
Вернуться на верх