from django.db import models from tournaments.models import group_stage from . import Round, GroupStage, FederalMatchCategory from django.utils import timezone, formats from datetime import datetime, timedelta import uuid from ..utils.extensions import format_seconds class Match(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) round = models.ForeignKey(Round, null=True, blank=True, on_delete=models.CASCADE) group_stage = models.ForeignKey(GroupStage, null=True, blank=True, on_delete=models.CASCADE) name = models.CharField(max_length=200, null=True, blank=True) start_date = models.DateTimeField(null=True, blank=True) end_date = models.DateTimeField(null=True, blank=True) index = models.IntegerField(default=0) #order = models.IntegerField(default=0) format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True) #court = models.CharField(max_length=50, null=True, blank=True) serving_team_id = models.UUIDField(null=True, blank=True) winning_team_id = models.UUIDField(null=True, blank=True) losing_team_id = models.UUIDField(null=True, blank=True) #broadcasted = models.BooleanField(default=False) disabled = models.BooleanField(default=False) court_index = models.IntegerField(null=True, blank=True) confirmed = models.BooleanField(default=False) def __str__(self): names = " / ".join(self.player_names()) return f"{self.stage_name()} #{self.index}: {names}" def tournament(self): if self.round: return self.round.tournament else: return self.group_stage.tournament def court_name(self, index): club = None if self.tournament().event: club = self.tournament().event.club if club: return club.court_name(index) elif index is not None: return f"Terrain {index + 1}" return "" def backup_name(self): items = [] if self.round: items.append(self.round.name()) if self.round.index > 0: items.append(f" #{self.index_in_round() + 1}") elif self.group_stage: items.append(self.group_stage.name()) items.append(f"Match #{self.index + 1}") return " ".join(items) def summon_stage_name(self): if self.round: return self.round.name() elif self.group_stage: return "Poule" else: return '--' def stage_name(self): if self.name: return self.name elif self.round: return self.round.name() elif self.group_stage: return self.group_stage.display_name() else: return '--' def get_previous_round(self): # Calculate the next index if self.round is None: return None previous_index = self.round.index + 1 # Check if the next index is within the bounds of the rounds array return self.round.tournament.round_set.filter(index=previous_index, parent = self.round.parent).first() # Return None or an appropriate value if the index is out of bounds return None def get_loser_previous_round(self): # Calculate the next index if self.round is None: return None previous_index = self.round.index + 1 previous_round = None # Check if the next index is within the bounds of the rounds array previous_round = self.round.tournament.round_set.filter(index=previous_index, parent = self.round.parent).first() if previous_round is None and self.round.parent is not None: previous_round = self.round.tournament.round_set.filter(id=self.round.parent.id).first() return previous_round return None def precedent_match(self, top): previous_round = self.get_previous_round() #print(previous_round) match = None if previous_round: matches = previous_round.match_set.all() # Retrieve the QuerySet match_index = self.index * 2 + 1 if top == False: match_index += 1 match = matches.filter(index=match_index).first() return match def loser_precedent_match(self, top): previous_round = self.get_loser_previous_round() match = None if previous_round: matches = previous_round.match_set.all() # Retrieve the QuerySet match_index = self.index * 2 + 1 if top == False: match_index += 1 match = matches.filter(index=match_index).first() return match def computed_name(self): if self.round and self.round.parent is None: return self.backup_name() title = self.name if self.name else self.backup_name() return title def index_in_round(self): if self.round is not None: matches = sorted( self.round.match_set.filter(disabled=False), key=lambda match: match.index ) try: index_of_self = matches.index(self) return index_of_self except ValueError: # `self` is not in `matches`, return self.index as a fallback return self.index return self.index def player_names(self): return map(lambda ts: ts.player_names(), self.team_scores.all()) def default_live_team(self, names): image = None weight= None is_winner = False scores = [] walk_out = None team = Team(None, image, names, scores, weight, is_winner, walk_out, False) return team def is_ready(self): return self.team_scores.count() == 2 def live_teams(self): #print('player names from match') ##return map(lambda ts: ts.player_names(), self.team_scores.all()) # List to hold the names of the teams teams = [] # Check if team scores exist team_scores = list(self.team_scores.all()) previous_top_match = self.precedent_match(True) previous_bottom_match = self.precedent_match(False) loser_top_match = self.loser_precedent_match(True) loser_bottom_match = self.loser_precedent_match(False) if len(team_scores) == 0: if (self.round and self.round.parent is None and self.round.tournament.round_set.filter(parent__isnull=True, group_stage_loser_bracket=False).count() - 1 == self.round.index): names = ["Qualifié", ''] team = self.default_live_team(names) teams.append(team) names = ["Qualifié", ''] team = self.default_live_team(names) teams.append(team) return teams if (self.group_stage): names = ["Équipe de poule", ''] team = self.default_live_team(names) teams.append(team) names = ["Équipe de poule", ''] team = self.default_live_team(names) teams.append(team) return teams elif self.round and self.round.parent: if loser_top_match: names = [f"Perdant {loser_top_match.computed_name()}", ''] team = self.default_live_team(names) teams.append(team) if loser_bottom_match: names = [f"Perdant {loser_bottom_match.computed_name()}", ''] team = self.default_live_team(names) teams.append(team) elif self.round and self.round.parent is None: if previous_top_match: names = [f"Gagnant {previous_top_match.computed_name()}", ''] team = self.default_live_team(names) teams.append(team) if previous_bottom_match: names = [f"Gagnant {previous_bottom_match.computed_name()}", ''] team = self.default_live_team(names) teams.append(team) elif len(team_scores) == 1: # Only one team score, handle missing one existing_team = team_scores[0].live_team(self) if (self.group_stage): teams.append(existing_team) names = ["Équipe de poule", ''] team = self.default_live_team(names) teams.append(team) elif self.round: if loser_top_match and loser_top_match.disabled == False and loser_top_match.end_date is None: names = [f"Perdant {loser_top_match.computed_name()}", ''] team = self.default_live_team(names) teams.append(team) teams.append(existing_team) elif loser_bottom_match: names = [f"Perdant {loser_bottom_match.computed_name()}", ''] team = self.default_live_team(names) teams.append(existing_team) teams.append(team) elif previous_top_match and previous_top_match.disabled == False and previous_top_match.end_date is None: names = [f"Gagnant {previous_top_match.computed_name()}", ''] team = self.default_live_team(names) teams.append(team) teams.append(existing_team) elif previous_bottom_match: names = [f"Gagnant {previous_bottom_match.computed_name()}", ''] team = self.default_live_team(names) teams.append(existing_team) teams.append(team) elif (self.round.parent is None and self.round.tournament.round_set.filter(parent__isnull=True, group_stage_loser_bracket=False).count() - 1 == self.round.index): match_index_within_round = self.index - (int(2 ** self.round.index) - 1) names = ["Qualifié", ''] team = self.default_live_team(names) if match_index_within_round < int(2 ** self.round.index) / 2: teams.append(existing_team) teams.append(team) else: teams.append(team) teams.append(existing_team) elif len(team_scores) == 2: # Both team scores present teams.extend([team_score.live_team(self) for team_score in team_scores]) else: teams.extend([team_score.live_team(self) for team_score in team_scores if team_score.walk_out != 1]) return teams def local_start_date(self): timezone = self.tournament().timezone() return self.start_date.astimezone(timezone) def formatted_start_date(self): if self.start_date: local_start = self.local_start_date() return formats.date_format(local_start, format='H:i') else: return '' def time_indication(self): if self.end_date: if self.start_date: return self.magic_duration() else: return '' elif self.start_date: if self.started(): if self.confirmed: return self.formatted_duration() else: return 'À suivre' else: # timezoned_datetime = timezone.localtime(self.start_date) timezone = self.tournament().timezone() local_start = self.start_date.astimezone(timezone) time_format ='l H:i' if self.tournament().day_duration >= 7: time_format = 'l d M à H:i' if self.confirmed: return formats.date_format(local_start, format=time_format) else: return f"Estimée : {formats.date_format(local_start, format=time_format)}" else: return 'À venir...' def magic_duration(self): seconds = (self.end_date - self.start_date).total_seconds() average_duration = self.average_seconds_duration() if (average_duration / 2) > seconds or seconds > (average_duration * 2): seconds = average_duration return format_seconds(seconds) def average_seconds_duration(self): return 3 * 60 * self.total_number_of_games() def total_number_of_games(self): games = 0.0 for team_score in self.team_scores.all(): games += team_score.estimated_number_of_games() return games def current_duration(self): if self.confirmed and self.start_date: if self.end_date: return (self.end_date - self.start_date).total_seconds() else: current = (timezone.now() - self.start_date).total_seconds() if self.total_number_of_games() > 0 and current < self.average_seconds_duration() * 4: return current elif self.total_number_of_games() == 0 and current < 3 * 60 * 60: return current else: return None # print(current) # print(self.average_seconds_duration()) # print('*******') # if current < self.average_seconds_duration() * 4: # return current # else: # return None # return (timezone.now() - self.start_date).total_seconds() else: return None def started(self): if self.confirmed: if self.end_date: return True elif self.start_date: return timezone.now() > self.start_date return False def should_appear(self): if self.disabled is True: return False return self.team_scores.count() > 0 # elif self.group_stage is None: # if len(self.team_scores.all()) == 2: # return True # else: # return (self.start_date or self.end_date) and len(self.team_scores.all()) > 0 # else: # return len(self.team_scores.all()) > 0 def formatted_duration(self): seconds = self.current_duration() if seconds is None: return '' if seconds > 0: return format_seconds(seconds) # _hours = int(_seconds / 3600) # _minutes = int((_seconds % 3600) / 60) # return f"{_hours:02d}h{_minutes:02d}min" else : seconds = seconds * -1 return format_seconds(seconds) # _hours = int(_seconds / 3600) # _minutes = int((_seconds % 3600) / 60) # return f"{_hours:02d}h{_minutes:02d}min" def live_match(self): title = self.computed_name() date = self.formatted_start_date() time_indication = self.time_indication() court = self.court_name(self.court_index) group_stage_name = None if self.group_stage: group_stage_name = self.group_stage.display_name() ended = self.end_date is not None live_format = "Format " + FederalMatchCategory(self.format).format_label_short livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name, live_format, self.start_date, self.court_index, self.disabled) for team in self.live_teams(): livematch.add_team(team) return livematch def sorted_team_scores(self): if self.group_stage: return self.team_scores.order_by('team_registration__group_stage_position') else: return self.team_scores.order_by('team_registration__bracket_position') # def non_null_start_date(self): # if self.start_date: # return self.start_date # else: # return timezone.now() + timedelta(days=7) # def sort_value(self): # sort_score = 0 # if self.round.index: # sort_score += 1 / (self.round.index + 1) * 1000 ** 2 # if self.group_stage.index: # sort_score += 1 / (self.group_stage.index + 1) * 1000 # sort_score += self.index # print(sort_score) # return sort_score class Team: def __init__(self, id, image, names, scores, weight, is_winner, walk_out, is_lucky_loser): # print(f"image = {image}, names= {names}, scores ={scores}, weight={weight}, win={is_winner}") self.id = str(id) self.image = image self.names = names self.scores = scores self.weight = weight self.is_winner = is_winner self.walk_out = walk_out self.is_lucky_loser = is_lucky_loser def is_walk_out(self): return self.walk_out is not None def to_dict(self): return { "image": self.image, "names": self.names, "scores": self.scores, "weight": self.weight, "is_winner": self.is_winner, "walk_out": self.walk_out, "is_walk_out": self.is_walk_out(), "is_lucky_loser": self.is_lucky_loser } class LiveMatch: def __init__(self, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled): self.title = title self.date = date self.teams = [] self.time_indication = time_indication self.court = court self.started = started self.ended = ended self.has_walk_out = False self.group_stage_name = group_stage_name self.format = format self.disabled = disabled self.start_date = start_date self.court_index = court_index def add_team(self, team): self.teams.append(team) if team.is_walk_out() is True: self.has_walk_out = True def to_dict(self): return { "title": self.title, "date": self.date, "teams": [team.to_dict() for team in self.teams], "time_indication": self.time_indication, "court": self.court, "started": self.started, "ended": self.ended, "has_walk_out": self.has_walk_out, "group_stage_name": self.group_stage_name, "format": self.format, "disabled": self.disabled, "court_index": self.court_index } def show_time_indication(self): for team in self.teams: if team.is_walk_out() and len(team.scores) == 0: return False return True def should_show_scores(self): for team in self.teams: if len(team.scores) > 0: return True return False