[Django]-Django circular model reference

12đź‘Ť

âś…

Here is another way to tackle this problem.
Instead of creating a circular dependency, I created an additional table that stores the relationship between players and teams. So in the end it looks like this:

class Team(Model):
    name = CharField(max_length=50)

    def get_captain(self):
        return PlayerRole.objects.get(team=self).player

class Player(Model):
    first_name = CharField(max_length=50)
    last_name = CharField(max_length=50, blank=True)

    def get_team(self):
        return PlayerRole.objects.get(player=self).team

PLAYER_ROLES = (
    ("Regular", "Regular"),
    ("Captain", "Captain")
    )

class PlayerRole(Model):
    player = OneToOneField(Player, primary_key=True)
    team = ForeignKey(Team, null=True)
    role = CharField(max_length=20, choices=PLAYER_ROLES, default=PLAYER_ROLES[0][0])
    class Meta:
        unique_together = ("player", "team")

It might be slightly less efficient in terms of storage than the suggested workaround, but it avoids the circular dependency and keeps the DB structure clean and clear.
Comments are welcome.

👤exfizik

58đź‘Ť

as you can see in the docs, for exactly this reason it is possible to specify the foreign model as a string.

team = models.ForeignKey('Team')
👤second

12đź‘Ť

You could use the full application label in the foreign key to the model not yet defined, and use related_name to avoid name conflict:

class Team(models.Model):
    captain = models.ForeignKey('myapp.Player', related_name="team_captain")

class Player(models.Model):
    team = models.ForeignKey(Team)
👤Micah Carrick

5đź‘Ť

This is what you were looking for:

class Team(models.Model):
    name = models.CharField()
    captain = models.ForeignKey('Player')
class Player(models.Model):
    name = models.CharField()
    team = models.ForeignKey(Team)
👤mimoralea

1đź‘Ť

Neither of the answers here are really that great – creating circular references is never a good idea. Imagine if your database crashed and you had to create it from scratch – how would you create player before team is created, and vice versa? Look a question here: ForeignKey field with primary relationship one I asked a few days ago. Put a Boolean on Player that specifies the captain, and put some pre-save hooks that validate every team must have one-and-only-one captain.

👤Ben

1đź‘Ť

Have a Captain table that has player/team columns along with your other tables, and make captain a method of Team:

class Team(models.Model):
    name = models.CharField()
    def captain(self):
      [search Captain table]
      return thePlayer

class Player(models.Model):
    name = models.CharField()
    team = models.ForeignKey(Team)

class Captain(models.Model):
    player = models.ForeignKey(Player)
    team = models.ForeignKey(Team)

You’d have to check that you never have more than one captain in the same team though… But you don’t have any circular references that way. You could also end up with a captain who isn’t in the team he’s flagged as captain of. So there’s a few gotchas this way.

👤Spacedman

1đź‘Ť

Although there is nothing wrong with having two references to the same model, perhaps there is a better way to solve this particular problem.

Add a boolean to the Team model to identify a player + team combination as captain:

class Team(models.Model):
  player = models.ForeignKey(Player)
  name = models.CharField(max_length=50)
  is_captain = models.BooleanField(default=False)

To search for a team’s captain:

Team.objects.filter(is_captain=True)

Personally I don’t like this method because the search semantics don’t make sense (ie, a “team” is not a “captain”).

The other approach is to identify each player’s position:

class Player(models.Model):
   name = models.CharField(max_length=50)
   position = models.IntegerField(choices=((1,'Captain'),(2,'Goal Keeper'))
   jersey = models.IntegerField()

   def is_captain(self):
     return self.position == 1

class Team(models.Model):
   name = models.CharField(max_length=50)
   player = models.ForeignKey(Player)

   def get_captain(self):
      return self.player if self.player.position == 1 else None

This makes a bit more sense when you search:

Player.objects.filter(position=1) (return all captains)

Team.objects.get(pk=1).get_captain() (return the captain for this team)

In either case, however you have to do some pre save checks to make sure there is only one player for a particular position.

👤Burhan Khalid

0đź‘Ť

Now can be used the AppConfig feature to import models:

Retailer = apps.get_model('retailers', 'Retailer')
retailer = Retailer.objects.get(id=id)            

Leave a comment