Bar Gans
Bar Gans

Reputation: 1663

Django model: How to avoid referencing the same record when using with 2 foreign keys from the same table

I have two Django models that look like this:

class Team(models.Model):
    name = models.CharField(max_length=30)
    ...


class Game(models.Model):
    teamA = models.ForeignKey(Team, on_delete=CASCADE, related_name='teamA')
    teamB = models.ForeignKey(Team, on_delete=CASCADE, related_name='teamB')
    ....

Game model has 2 foreign keys to Team model.

Of course, it is not possible for a team to play against itself, so I would like to prevent teamA and teamB from referring to the same record in DB.

So you can't create a game like this:

Game.objects.create(teamA=ChicagoBulls, teamB=ChicagoBulls)

What is the best way to do this?

Upvotes: 1

Views: 57

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476719

You can implement a constraint in the .clean() method [Django-doc], and also at the database level with a CheckConstraint [Django-doc] (not all databases will however enforce this):

from django.core.exceptions import ValidationError
from django.db.models import F, Q

class Game(models.Model):
    teamA = models.ForeignKey(Team, on_delete=CASCADE, related_name='teamA')
    teamB = models.ForeignKey(Team, on_delete=CASCADE, related_name='teamB')
    # …
    
    def clean(self, *args, **kwargs):
        if self.teamA_id == self.teamB_id:
            raise ValidationError('The two teams should be different')
        super().clean(*args, **kwargs)

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=~Q(teamA=F('teamB')),
                name='different_teams'
            )
        ]

The .clean() method is not checked when you create an object with the model layer. It is however validated when you create a Game through a ModelForm [Django-doc].

You probably also might want to make the name of the Team unique, such that there can not be two teams with the same name:

class Team(models.Model):
    name = models.CharField(max_length=30, unique=True)
    # …

Upvotes: 2

Related Questions