from django.db import models from django.utils import timezone, formats from django.core.exceptions import ObjectDoesNotExist from . import TournamentSubModel, Round, GroupStage, FederalMatchCategory from datetime import datetime, timedelta import uuid from ..utils.extensions import format_seconds class Match(TournamentSubModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) round = models.ForeignKey(Round, null=True, blank=True, on_delete=models.SET_NULL, related_name='matches') group_stage = models.ForeignKey(GroupStage, null=True, blank=True, on_delete=models.SET_NULL, related_name='matches') name = models.CharField(max_length=200, null=True, blank=True) start_date = models.DateTimeField(null=True, blank=True) planned_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 delete_dependencies(self): for team_score in self.team_scores.all(): # team_score.delete_dependencies() team_score.delete() def __str__(self): names = " / ".join(self.player_names()) if names: return f"{self.stage_name()} : {names}" else: return f"{self.stage_name()}" # def save(self, *args, **kwargs): # self.store_id = str(self.get_tournament_id()) # super().save(*args, **kwargs) def get_tournament(self): # mandatory method for TournamentSubModel if self.round: return self.round.tournament elif self.group_stage: return self.group_stage.tournament return None # def get_tournament_id(self): # tournament = self.get_tournament() # if tournament: # return tournament.id # else: # return None def court_name(self, index): club = None tournament = self.get_tournament() if tournament and tournament.event: club = self.get_tournament().event.club if club: return club.court_name(index) elif index is not None: return f"Piste {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.display_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): try: if self.name: return self.name elif self.round: return self.round.name() elif self.group_stage: return self.group_stage.display_name() except ObjectDoesNotExist: pass 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.rounds.filter(index=previous_index, parent = self.round.parent).first() 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.rounds.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.rounds.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.matches.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.matches.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.matches.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, hide_names=False, short_names=False): #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 or hide_names is True: if (self.round and self.round.parent is None and self.round.tournament.rounds.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) if previous_top_match: names = [f"Gagnant {previous_top_match.computed_name()}"] if previous_top_match.disabled: names = ["Perdant tableau principal"] team = self.default_live_team(names) teams.append(team) if previous_bottom_match: names = [f"Gagnant {previous_bottom_match.computed_name()}"] if previous_bottom_match.disabled: names = ["Perdant tableau principal"] 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, short_names=short_names) 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.rounds.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, short_names=short_names) for team_score in team_scores]) if self.round is not None and self.round.parent is None: pos1 = team_scores[0].team_registration.bracket_position if hasattr(team_scores[0], 'team_registration') and team_scores[0].team_registration else None pos2 = team_scores[1].team_registration.bracket_position if hasattr(team_scores[1], 'team_registration') and team_scores[1].team_registration else None if pos1 is not None and pos2 is not None and pos1 // 2 == self.index and pos2 // 2 == self.index: if pos1 > pos2: teams = [team_scores[1].live_team(self, short_names=short_names), team_scores[0].live_team(self, short_names=short_names)] else: teams = [team_scores[0].live_team(self, short_names=short_names), team_scores[1].live_team(self, short_names=short_names)] else: teams.extend([team_score.live_team(self, short_names=short_names) for team_score in team_scores if team_score.walk_out != 1]) return teams def local_start_date(self): timezone = self.get_tournament().timezone() return self.start_date.astimezone(timezone) def local_planned_start_date(self): timezone = self.get_tournament().timezone() return self.planned_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.get_tournament().timezone() local_start = self.start_date.astimezone(timezone) time_format ='l H:i' if self.get_tournament().day_duration >= 7: time_format = 'D. d F à 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): return format_seconds(self.smart_time_played()) def smart_time_played(self): if self.start_date: if self.end_date: return self.__smart_duration() else: return (timezone.now() - self.start_date).total_seconds() else: return 0 def __smart_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 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 tournament_title(self, full_name=False): if self.group_stage: if full_name: return self.group_stage.tournament.full_name() else: return self.group_stage.tournament.short_full_name() else: if full_name: return self.round.tournament.full_name() else: return self.round.tournament.short_full_name() def live_match(self, hide_teams=False, event_mode=False, short_names=False, broadcast=False): 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 bracket_name = None if self.group_stage: group_stage_name = self.group_stage.display_name() else: bracket_name = "Match #" + f"{self.index_in_round() + 1}" ended = self.end_date is not None live_format = "Format " if self.get_tournament().day_duration >= 7: live_format = "" live_format = live_format + FederalMatchCategory(self.format).format_label_short tournament_title = None if event_mode is True: tournament_title = self.tournament_title(broadcast == False) livematch = LiveMatch(self.index, title, date, time_indication, court, self.started(), ended, group_stage_name, live_format, self.start_date, self.court_index, self.disabled, bracket_name, self.should_show_lucky_loser_status(), tournament_title) for team in self.live_teams(hide_teams, short_names): 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 should_show_lucky_loser_status(self): if self.group_stage is not None: return False if self.round and self.round.parent is None and self.round.group_stage_loser_bracket is False: return True return False # 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}") if id is not None: self.id = str(id) else: self.id = None 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, "id": self.id } class LiveMatch: def __init__(self, index, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled, bracket_name, should_show_lucky_loser_status, tournament_title): self.index = index 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 self.bracket_name = bracket_name self.should_show_lucky_loser_status = should_show_lucky_loser_status self.tournament_title = tournament_title 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 { "index": self.index, "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, "bracket_name": self.bracket_name, "should_show_lucky_loser_status": self.should_show_lucky_loser_status, "tournament_title": self.tournament_title, } 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