djbrown/hbscorez

View on GitHub
src/games/models.py

Summary

Maintainability
A
2 hrs
Test Coverage
D
61%
from enum import Enum, auto

from django.conf import settings
from django.db import models
from returns.maybe import Maybe

from leagues.models import League
from sports_halls.models import SportsHall
from teams.models import Team


class GameOutcome(Enum):
    HOME_WIN = auto()
    AWAY_WIN = auto()
    TIE = auto()
    OPEN = auto()


class TeamOutcome(Enum):
    WIN = auto()
    LOSS = auto()
    TIE = auto()
    OPEN = auto()


class Leg(Enum):
    FIRST = auto()
    BEWTEEN = auto()
    SECOND = auto()
    UNKNOWN = auto()


class Game(models.Model):
    number = models.IntegerField()
    league = models.ForeignKey(League, on_delete=models.CASCADE)
    opening_whistle = models.DateTimeField(blank=True, null=True)
    sports_hall = models.ForeignKey(SportsHall, on_delete=models.SET_NULL, blank=True, null=True)
    home_team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="home_team")
    guest_team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="guest_team")
    home_goals = models.IntegerField(blank=True, null=True)
    guest_goals = models.IntegerField(blank=True, null=True)
    report_number = models.IntegerField(blank=True, null=True, unique=True)
    forfeiting_team = models.ForeignKey(
        Team,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        related_name="forfeiting_team",
    )
    spectators = models.IntegerField(blank=True, null=True)

    class Meta:
        unique_together = ("number", "league")

    def __str__(self):
        return f"{self.number} {self.league} {self.home_team.short_name} vs. {self.guest_team.short_name}"

    @staticmethod
    def build_report_source_url(report_number):
        return settings.ROOT_SOURCE_URL + f"misc/sboPublicReports.php?sGID={report_number}"

    def report_source_url(self):
        if self.report_number is None:
            return None
        return self.build_report_source_url(self.report_number)

    def opponent_of(self, team):
        if team == self.home_team:
            return self.guest_team
        if team == self.guest_team:
            return self.home_team
        raise ValueError(f"neither home or guest is team: {team}")

    def other_games(self):
        return Game.objects.filter(
            ~models.Q(number=self.number),
            home_team__in=(self.home_team, self.guest_team),
            guest_team__in=(self.home_team, self.guest_team),
        )

    def leg(self) -> Leg:
        other_games = self.other_games()
        if (
            not other_games
            or len(other_games) > 2
            or self.opening_whistle is None
            or list(filter(lambda g: g.opening_whistle is None, other_games))
        ):
            return Leg.UNKNOWN

        if len(other_games) == 1:
            first_leg = self.opening_whistle < other_games[0].opening_whistle
            return Leg.FIRST if first_leg else Leg.SECOND

        # len(other_games) == 2
        if (
            self.opening_whistle < other_games[0].opening_whistle
            and self.opening_whistle < other_games[1].opening_whistle
        ):
            return Leg.FIRST
        if (
            self.opening_whistle > other_games[0].opening_whistle
            and self.opening_whistle > other_games[1].opening_whistle
        ):
            return Leg.SECOND

        return Leg.BEWTEEN

    def leg_title(self) -> str:
        mapping = {
            Leg.FIRST: "Hinspiel",
            Leg.BEWTEEN: "Zwischenspiel",
            Leg.SECOND: "Rückspiel",
            Leg.UNKNOWN: "Spiel",
        }
        return mapping[self.leg()]

    def outcome(self) -> GameOutcome:
        if self.home_goals is None and self.guest_goals is None:
            return GameOutcome.OPEN
        home_goals: int = Maybe.from_optional(self.home_goals).unwrap()
        guest_goals: int = Maybe.from_optional(self.guest_goals).unwrap()
        if home_goals > guest_goals or self.forfeiting_team == self.guest_team:
            return GameOutcome.HOME_WIN
        if home_goals < guest_goals or self.forfeiting_team == self.home_team:
            return GameOutcome.AWAY_WIN
        if home_goals == guest_goals:
            return GameOutcome.TIE
        raise ValueError("no matching outcome")

    def outcome_for(self, team) -> TeamOutcome:
        if self.outcome() == GameOutcome.OPEN:
            return TeamOutcome.OPEN
        if self.outcome() == GameOutcome.TIE:
            return TeamOutcome.TIE
        if (
            team == self.home_team
            and self.outcome() == GameOutcome.HOME_WIN
            or team == self.guest_team
            and self.outcome() == GameOutcome.AWAY_WIN
        ):
            return TeamOutcome.WIN
        if (
            team == self.home_team
            and self.outcome() == GameOutcome.AWAY_WIN
            or team == self.guest_team
            and self.outcome() == GameOutcome.HOME_WIN
        ):
            return TeamOutcome.LOSS
        raise ValueError(f"no matching outcome for team: {team}")

    def goals_of(self, team):
        if team == self.home_team:
            return self.home_goals
        if team == self.guest_team:
            return self.guest_goals
        return 0