phase3: add normalized domain schema, admin, services, and multistage docker build
This commit is contained in:
191
apps/players/models.py
Normal file
191
apps/players/models.py
Normal file
@ -0,0 +1,191 @@
|
||||
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",
|
||||
)
|
||||
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=["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(
|
||||
check=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 '-'}"
|
||||
Reference in New Issue
Block a user