from django.db import models class TimeStampedModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True class Nationality(TimeStampedModel): name = models.CharField(max_length=120, unique=True) iso2_code = models.CharField(max_length=2, unique=True) iso3_code = models.CharField(max_length=3, unique=True, blank=True, null=True) class Meta: ordering = ["name"] indexes = [models.Index(fields=["name"]), models.Index(fields=["iso2_code"])] verbose_name_plural = "Nationalities" def __str__(self) -> str: return f"{self.name} ({self.iso2_code})" class Position(TimeStampedModel): code = models.CharField(max_length=10, unique=True) name = models.CharField(max_length=80, unique=True) class Meta: ordering = ["code"] indexes = [models.Index(fields=["code"]), models.Index(fields=["name"])] def __str__(self) -> str: return f"{self.code} - {self.name}" class Role(TimeStampedModel): code = models.CharField(max_length=32, unique=True) name = models.CharField(max_length=120, unique=True) description = models.TextField(blank=True) class Meta: ordering = ["name"] indexes = [models.Index(fields=["code"]), models.Index(fields=["name"])] def __str__(self) -> str: return self.name class Player(TimeStampedModel): class DominantHand(models.TextChoices): RIGHT = "right", "Right" LEFT = "left", "Left" BOTH = "both", "Both" UNKNOWN = "unknown", "Unknown" first_name = models.CharField(max_length=120) last_name = models.CharField(max_length=120) full_name = models.CharField(max_length=260) birth_date = models.DateField(blank=True, null=True) nationality = models.ForeignKey( "players.Nationality", on_delete=models.SET_NULL, blank=True, null=True, related_name="players", ) nominal_position = models.ForeignKey( "players.Position", on_delete=models.SET_NULL, blank=True, null=True, related_name="nominal_players", ) inferred_role = models.ForeignKey( "players.Role", on_delete=models.SET_NULL, blank=True, null=True, related_name="role_players", ) origin_competition = models.ForeignKey( "competitions.Competition", on_delete=models.SET_NULL, blank=True, null=True, related_name="origin_players", ) origin_team = models.ForeignKey( "teams.Team", on_delete=models.SET_NULL, blank=True, null=True, related_name="origin_players", ) height_cm = models.PositiveSmallIntegerField(blank=True, null=True) weight_kg = models.PositiveSmallIntegerField(blank=True, null=True) wingspan_cm = models.PositiveSmallIntegerField(blank=True, null=True) dominant_hand = models.CharField( max_length=16, choices=DominantHand.choices, default=DominantHand.UNKNOWN, ) is_active = models.BooleanField(default=True) class Meta: ordering = ["full_name", "id"] constraints = [ models.UniqueConstraint( fields=["full_name", "birth_date"], name="uq_player_full_name_birth_date", ) ] indexes = [ models.Index(fields=["full_name"]), models.Index(fields=["last_name", "first_name"]), models.Index(fields=["birth_date"]), models.Index(fields=["nationality"]), models.Index(fields=["nominal_position"]), models.Index(fields=["inferred_role"]), models.Index(fields=["origin_competition"]), models.Index(fields=["origin_team"]), models.Index(fields=["is_active"]), models.Index(fields=["height_cm"]), ] def __str__(self) -> str: return self.full_name class PlayerAlias(TimeStampedModel): player = models.ForeignKey("players.Player", on_delete=models.CASCADE, related_name="aliases") alias = models.CharField(max_length=260) source = models.CharField(max_length=80, blank=True) is_primary = models.BooleanField(default=False) class Meta: ordering = ["alias"] constraints = [ models.UniqueConstraint(fields=["player", "alias"], name="uq_player_alias_per_player") ] indexes = [models.Index(fields=["alias"]), models.Index(fields=["source"])] def __str__(self) -> str: return f"{self.alias} ({self.player_id})" class PlayerCareerEntry(TimeStampedModel): player = models.ForeignKey( "players.Player", on_delete=models.CASCADE, related_name="career_entries", ) team = models.ForeignKey( "teams.Team", on_delete=models.SET_NULL, blank=True, null=True, related_name="career_entries", ) competition = models.ForeignKey( "competitions.Competition", on_delete=models.SET_NULL, blank=True, null=True, related_name="career_entries", ) season = models.ForeignKey( "competitions.Season", on_delete=models.SET_NULL, blank=True, null=True, related_name="career_entries", ) role_snapshot = models.ForeignKey( "players.Role", on_delete=models.SET_NULL, blank=True, null=True, related_name="career_entries", ) shirt_number = models.PositiveSmallIntegerField(blank=True, null=True) start_date = models.DateField(blank=True, null=True) end_date = models.DateField(blank=True, null=True) notes = models.TextField(blank=True) class Meta: ordering = ["player", "-start_date", "-id"] constraints = [ models.UniqueConstraint( fields=["player", "team", "competition", "season", "start_date"], name="uq_player_career_entry_scope", ), models.CheckConstraint( condition=models.Q(end_date__isnull=True) | models.Q(start_date__isnull=True) | models.Q(end_date__gte=models.F("start_date")), name="ck_career_entry_dates", ), ] indexes = [ models.Index(fields=["player", "start_date"]), models.Index(fields=["team", "season"]), models.Index(fields=["competition", "season"]), ] def __str__(self) -> str: return f"{self.player} | {self.team or '-'} | {self.season or '-'}"