You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
555 lines
22 KiB
555 lines
22 KiB
from django.db import models
|
|
# from tournaments.models import group_stage
|
|
from . import TournamentSubModel, Round, GroupStage, FederalMatchCategory
|
|
from django.utils import timezone, formats
|
|
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)
|
|
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"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.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):
|
|
#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.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)
|
|
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) 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), team_scores[0].live_team(self)]
|
|
else:
|
|
teams = [team_scores[0].live_team(self), team_scores[1].live_team(self)]
|
|
|
|
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.get_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.get_tournament().timezone()
|
|
local_start = self.start_date.astimezone(timezone)
|
|
time_format ='l H:i'
|
|
if self.get_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
|
|
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 " + FederalMatchCategory(self.format).format_label_short
|
|
|
|
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())
|
|
|
|
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 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):
|
|
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
|
|
|
|
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,
|
|
}
|
|
|
|
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
|
|
|