Referencia del modelo circular de Django.

Estoy empezando a trabajar en un pequeño sitio web de administración de la liga de fútbol (principalmente con fines de aprendizaje) y no puedo entender mi relación con los modelos de Django. Para simplificar, digamos que tengo 2 tipos de objetos: Jugador y Equipo. Naturalmente, un jugador pertenece a un equipo, por lo que es un ForeignKey (Equipo) en el modelo de Jugador. Entonces voy:

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

Luego quiero que cada equipo tenga un capitán, que sería uno de los jugadores, de modo que sería un ForeignKey (Jugador) en el modelo de Equipo. Pero eso crearía una dependencia circular. De acuerdo, mi experiencia con Django es limitada, pero parece un problema simple, aunque no puedo entender qué estoy haciendo mal conceptualmente.

Como puede ver en los documentos , por esta razón es posible especificar el modelo externo como una cadena.

 team = models.ForeignKey('Team') 

Aquí hay otra forma de abordar este problema. En lugar de crear una dependencia circular, creé una tabla adicional que almacena la relación entre jugadores y equipos. Así que al final se ve así:

 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") 

Puede ser un poco menos eficiente en términos de almacenamiento que la solución sugerida, pero evita la dependencia circular y mantiene la estructura de DB limpia y clara. Los comentarios son bienvenidos.

Puede usar la etiqueta completa de la aplicación en la clave externa para el modelo aún no definido, y usar related_name para evitar conflictos de nombres:

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

Tenga una mesa de Capitán que tenga columnas de jugador / equipo junto con sus otras mesas, y haga que el capitán sea un método de Equipo:

 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) 

Tendría que verificar que nunca haya más de un capitán en el mismo equipo, aunque … Pero no tiene ninguna referencia circular de esa manera. También podrías terminar con un capitán que no esté en el equipo del que está marcado como capitán. Así que hay algunas trampas de esta manera.

Aunque no hay nada de malo en tener dos referencias al mismo modelo, quizás haya una mejor manera de resolver este problema en particular.

Agrega un booleano al modelo de Team para identificar una combinación de jugador y equipo como capitán:

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

Para buscar al capitán de un equipo:

Team.objects.filter(is_captain=True)

Personalmente no me gusta este método porque la semántica de búsqueda no tiene sentido (es decir, un “equipo” no es un “capitán”).

El otro enfoque es identificar la posición de cada jugador:

 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 

Esto tiene un poco más de sentido cuando buscas:

Player.objects.filter(position=1) (devolver todos los capitanes)

Team.objects.get(pk=1).get_captain() (devolver el capitán para este equipo)

En cualquier caso, sin embargo, tiene que hacer algunas comprobaciones previas a guardar para asegurarse de que solo haya un jugador para una posición en particular.

Esto es lo que estabas buscando:

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

Ninguna de las respuestas aquí es realmente tan genial: crear referencias circulares nunca es una buena idea. Imagina que tu base de datos fallara y tuvieras que crearla desde cero. ¿Cómo crearías un jugador antes de crear el equipo y viceversa? Mire una pregunta aquí: ForeignKey campo con relación primaria uno que pregunté hace unos días. Ponga un Boolean en el Jugador que especifique al capitán, y ponga algunos ganchos de pre-guardado que validen que cada equipo debe tener uno y solo un capitán.