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.
936 lines
36 KiB
936 lines
36 KiB
from zoneinfo import ZoneInfo
|
|
from django.db import models
|
|
from typing import TYPE_CHECKING
|
|
if TYPE_CHECKING:
|
|
from tournaments.models import group_stage
|
|
|
|
from . import Event, TournamentPayment, FederalMatchCategory, FederalCategory, FederalLevelCategory, FederalAgeCategory
|
|
import uuid
|
|
from django.utils import timezone, formats
|
|
from datetime import datetime, timedelta
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from shared.cryptography import encryption_util
|
|
from ..utils.extensions import plural_format
|
|
|
|
class TeamSortingType(models.IntegerChoices):
|
|
RANK = 1, 'Rank'
|
|
INSCRIPTION_DATE = 2, 'Inscription Date'
|
|
|
|
class Tournament(models.Model):
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
|
|
event = models.ForeignKey(Event, blank=True, null=True, on_delete=models.CASCADE)
|
|
name = models.CharField(max_length=200, null=True, blank=True)
|
|
start_date = models.DateTimeField()
|
|
end_date = models.DateTimeField(null=True, blank=True)
|
|
creation_date = models.DateTimeField()
|
|
is_private = models.BooleanField(default=False)
|
|
# format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True)
|
|
round_format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True)
|
|
group_stage_format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True)
|
|
loser_round_format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True)
|
|
group_stage_sort_mode = models.IntegerField(default=0)
|
|
group_stage_count = models.IntegerField(default=0)
|
|
rank_source_date = models.DateTimeField(null=True, blank=True)
|
|
day_duration = models.IntegerField(default=0)
|
|
team_count = models.IntegerField(default=0)
|
|
team_sorting = models.IntegerField(default=TeamSortingType.INSCRIPTION_DATE, choices=TeamSortingType.choices)
|
|
federal_category = models.IntegerField(default=FederalCategory.MEN, choices=FederalCategory.choices) # optional ?
|
|
federal_level_category = models.IntegerField(default=FederalLevelCategory.P100, choices=FederalLevelCategory.choices)
|
|
federal_age_category = models.IntegerField(default=FederalAgeCategory.SENIOR, choices=FederalAgeCategory.choices)
|
|
#group_stage_court_count = models.IntegerField(null=True, blank=True)
|
|
#seed_count = models.IntegerField(default=0)
|
|
closed_registration_date = models.DateTimeField(null=True, blank=True)
|
|
group_stage_additional_qualified = models.IntegerField(default=0)
|
|
court_count = models.IntegerField(default=2)
|
|
prioritize_club_members = models.BooleanField()
|
|
qualified_per_group_stage = models.IntegerField(default=0)
|
|
teams_per_group_stage = models.IntegerField(default=0)
|
|
entry_fee = models.FloatField(default=20.0, null=True, blank=True)
|
|
global_id = models.CharField(max_length=100, null=True, blank=True) #represent the payment crypted string
|
|
is_deleted = models.BooleanField(default=False)
|
|
local_id = models.CharField(max_length=100, null=True, blank=True) #represent the is_canceled crypted string
|
|
additional_estimation_duration = models.IntegerField(default=0)
|
|
publish_teams = models.BooleanField(default=False)
|
|
hide_teams_weight = models.BooleanField(default=False)
|
|
publish_summons = models.BooleanField(default=False)
|
|
publish_group_stages = models.BooleanField(default=False)
|
|
publish_brackets = models.BooleanField(default=False)
|
|
should_verify_bracket = models.BooleanField(default=False)
|
|
should_verify_group_stage = models.BooleanField(default=False)
|
|
publish_tournament = models.BooleanField(default=False)
|
|
hide_points_earned = models.BooleanField(default=False)
|
|
publish_rankings = models.BooleanField(default=False)
|
|
loser_bracket_mode = models.IntegerField(default=0)
|
|
initial_seed_round = models.IntegerField(default=0)
|
|
initial_seed_count = models.IntegerField(default=0)
|
|
|
|
def __str__(self):
|
|
if self.name:
|
|
return self.name
|
|
else:
|
|
return self.display_name()
|
|
|
|
def is_canceled(self):
|
|
if self.local_id:
|
|
decrypted = encryption_util.decrypt_aes_gcm(self.local_id)
|
|
value = int(decrypted[18])
|
|
if 0 <= value <= 4:
|
|
return True
|
|
else:
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
def payment(self):
|
|
if self.global_id:
|
|
decrypted = encryption_util.decrypt_aes_gcm(self.global_id)
|
|
value = int(decrypted[18])
|
|
return TournamentPayment(value)
|
|
else:
|
|
return None
|
|
|
|
def display_name(self):
|
|
if self.name:
|
|
if self.federal_level_category == FederalLevelCategory.UNLISTED:
|
|
return self.name
|
|
return self.base_name() + " " + self.name
|
|
else:
|
|
return self.base_name()
|
|
|
|
def broadcast_display_name(self):
|
|
if self.name:
|
|
if self.federal_level_category == FederalLevelCategory.UNLISTED:
|
|
return self.name
|
|
return self.short_base_name() + " " + self.name
|
|
else:
|
|
return self.base_name()
|
|
|
|
def broadcast_event_display_name(self):
|
|
if self.event is not None:
|
|
return self.event.display_name()
|
|
else:
|
|
return " "
|
|
|
|
def base_name(self):
|
|
return f"{self.level()} {self.category()}"
|
|
|
|
def short_base_name(self):
|
|
category = self.category()
|
|
if len(category) > 0:
|
|
return f"{self.level()}{category[0]}"
|
|
else:
|
|
return self.level()
|
|
|
|
def filter_name(self):
|
|
components = [self.formatted_start_date(), self.short_base_name()]
|
|
if self.event and self.event.club and self.event.club.name:
|
|
components.append(self.event.club.name)
|
|
elif self.event.name:
|
|
components.append(self.event.name)
|
|
elif self.name:
|
|
components.append(self.name)
|
|
return (' ').join(components)
|
|
|
|
def timezone(self):
|
|
tz = 'CET'
|
|
if self.event and self.event.club:
|
|
tz = self.event.club.timezone
|
|
return ZoneInfo(tz)
|
|
|
|
def local_start_date(self):
|
|
timezone = self.timezone()
|
|
return self.start_date.astimezone(timezone)
|
|
|
|
def local_start_date_formatted(self):
|
|
return formats.date_format(self.local_start_date(), format='j F Y H:i')
|
|
|
|
def level(self):
|
|
if self.federal_level_category == 0:
|
|
return "Anim."
|
|
return self.get_federal_level_category_display()
|
|
|
|
def category(self):
|
|
return self.get_federal_category_display()
|
|
|
|
def age(self):
|
|
if self.federal_age_category == 0:
|
|
return None
|
|
return self.get_federal_age_category_display()
|
|
|
|
def formatted_start_date(self):
|
|
return self.start_date.strftime("%d/%m/%y")
|
|
|
|
def in_progress(self):
|
|
return self.end_date is None
|
|
|
|
def creator(self):
|
|
return self.event.creator.username
|
|
|
|
def private_label(self):
|
|
if self.is_private:
|
|
return "Privé"
|
|
else:
|
|
return "Public"
|
|
|
|
def summon_count_display(self):
|
|
teams = self.team_summons()
|
|
if teams is not None and len(teams) > 0:
|
|
return f"{len(teams)} équipes convoquées"
|
|
else:
|
|
return None
|
|
|
|
def ranking_count_display(self):
|
|
teams = self.rankings()
|
|
if teams is not None and len(teams) > 0:
|
|
return f"{len(teams)} équipes"
|
|
else:
|
|
return None
|
|
|
|
def registration_count_display(self):
|
|
teams = self.teams(True)
|
|
if teams is not None and len(teams) > 0:
|
|
return f"{len(teams)} équipes inscrites"
|
|
else:
|
|
return None
|
|
|
|
def tournament_status_display(self):
|
|
if self.is_canceled() is True:
|
|
return "Annulé"
|
|
|
|
teams = self.teams(True)
|
|
if self.supposedly_in_progress() or self.end_date is not None:
|
|
teams = [t for t in teams if t.stage != "Attente"]
|
|
if teams is not None and len(teams) > 0:
|
|
word = "équipe"
|
|
if len(teams) > 1:
|
|
word = word + "s"
|
|
return f"{len(teams)} {word}"
|
|
else:
|
|
return None
|
|
if teams is not None and len(teams) > 0:
|
|
word = "inscription"
|
|
if len(teams) > 1:
|
|
word = word + "s"
|
|
return f"{len(teams)} {word}"
|
|
else:
|
|
return None
|
|
|
|
def name_and_event(self):
|
|
event_name = None
|
|
if self.event:
|
|
event_name = self.event.name
|
|
|
|
if event_name and self.name:
|
|
return event_name + " : " + self.name
|
|
elif event_name:
|
|
return event_name
|
|
elif self.name:
|
|
return self.name
|
|
else:
|
|
return None
|
|
|
|
def team_summons(self):
|
|
summons = []
|
|
if self.supposedly_in_progress() and self.end_date is None:
|
|
for team in self.teams(False):
|
|
names = team.names
|
|
stage = team.stage
|
|
weight = team.weight
|
|
summon = TeamSummon(names, team.date, weight, stage, "", team.image)
|
|
summons.append(summon)
|
|
else:
|
|
for team_registration in self.teamregistration_set.all():
|
|
if team_registration.is_valid_for_summon():
|
|
next_match = team_registration.next_match()
|
|
if next_match and next_match.start_date is not None:
|
|
names = team_registration.team_names()
|
|
stage = next_match.summon_stage_name()
|
|
weight = team_registration.weight
|
|
summon = TeamSummon(names, next_match.local_start_date(), weight, stage, next_match.court_name(next_match.court_index), team_registration.logo)
|
|
summons.append(summon)
|
|
|
|
summons.sort(key=lambda s: (s.date is None, s.date or datetime.min))
|
|
return summons
|
|
|
|
def has_summons(self):
|
|
for team_registration in self.teamregistration_set.all():
|
|
if team_registration.is_valid_for_summon():
|
|
next_match = team_registration.next_match()
|
|
if next_match and next_match.start_date is not None:
|
|
return True
|
|
return False
|
|
|
|
def rankings(self):
|
|
rankings = []
|
|
for team_registration in self.teamregistration_set.all():
|
|
if team_registration.walk_out is False and team_registration.final_ranking is not None:
|
|
names = team_registration.team_names()
|
|
ranking = team_registration.final_ranking
|
|
points = team_registration.points_earned
|
|
team = TeamRanking(names, ranking, points, team_registration.logo)
|
|
rankings.append(team)
|
|
|
|
rankings.sort(key=lambda r: r.ranking)
|
|
return rankings
|
|
|
|
def teams(self, includeWaitingList):
|
|
print("Starting teams method")
|
|
bracket_teams = []
|
|
group_stage_teams = []
|
|
waiting_teams = []
|
|
teams = []
|
|
wildcard_bracket = []
|
|
wildcard_group_stage = []
|
|
complete_teams = []
|
|
closed_registration_date = self.closed_registration_date
|
|
print(f"Closed registration date: {closed_registration_date}")
|
|
|
|
for team_registration in self.teamregistration_set.all():
|
|
print(f"Processing team registration: {team_registration}")
|
|
is_valid = False
|
|
if closed_registration_date is not None and team_registration.registration_date is not None and team_registration.registration_date <= closed_registration_date:
|
|
is_valid = True
|
|
if closed_registration_date is None:
|
|
is_valid = True
|
|
if team_registration.registration_date is None:
|
|
is_valid = True
|
|
print(f"Is valid: {is_valid}")
|
|
|
|
if team_registration.walk_out is False:
|
|
names = team_registration.team_names()
|
|
weight = team_registration.weight
|
|
initial_weight = team_registration.initial_weight()
|
|
date = team_registration.call_date
|
|
team = TeamList(names, weight, date, initial_weight, team_registration.wild_card_bracket, team_registration.wild_card_group_stage, team_registration.logo)
|
|
print(f"Created team: {team}")
|
|
if team_registration.group_stage_position is not None:
|
|
team.set_stage("Poule")
|
|
elif team_registration.bracket_position is not None:
|
|
team.set_stage("Tableau")
|
|
else:
|
|
team.set_stage("Attente")
|
|
print(f"Team stage: {team.stage}")
|
|
|
|
teams.append(team)
|
|
if team_registration.wild_card_bracket:
|
|
wildcard_bracket.append(team)
|
|
elif team_registration.wild_card_group_stage:
|
|
wildcard_group_stage.append(team)
|
|
elif is_valid is True:
|
|
complete_teams.append(team)
|
|
else:
|
|
waiting_teams.append(team)
|
|
|
|
print(f"Total teams: {len(teams)}")
|
|
print(f"Wildcard bracket: {len(wildcard_bracket)}")
|
|
print(f"Wildcard group stage: {len(wildcard_group_stage)}")
|
|
print(f"Complete teams: {len(complete_teams)}")
|
|
print(f"Waiting teams: {len(waiting_teams)}")
|
|
|
|
if len(teams) < self.team_count:
|
|
teams.sort(key=lambda s: (s.initial_weight, s.date))
|
|
print("Returning early due to insufficient teams")
|
|
return teams
|
|
|
|
seeds_count = min(self.team_count, len(teams)) - self.group_stage_count * self.teams_per_group_stage - len(wildcard_bracket)
|
|
group_stage_members_count = self.group_stage_count * self.teams_per_group_stage - len(wildcard_group_stage)
|
|
if group_stage_members_count < 0:
|
|
group_stage_members_count = 0
|
|
if seeds_count < 0:
|
|
seeds_count = 0
|
|
print(f"Seeds count: {seeds_count}")
|
|
print(f"Group stage members count: {group_stage_members_count}")
|
|
|
|
if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
|
|
complete_teams.sort(key=lambda s: (s.date is None, s.date or datetime.min, s.initial_weight))
|
|
else:
|
|
complete_teams.sort(key=lambda s: (s.initial_weight, s.date is None, s.date or datetime.min))
|
|
|
|
selected_teams = complete_teams[:self.team_count]
|
|
selected_teams.sort(key=lambda s: s.initial_weight)
|
|
print(f"Selected teams: {len(selected_teams)}")
|
|
|
|
if seeds_count > 0:
|
|
bracket_teams = selected_teams[:seeds_count] + wildcard_bracket
|
|
else:
|
|
bracket_teams = []
|
|
print(f"Bracket teams: {len(bracket_teams)}")
|
|
|
|
if group_stage_members_count:
|
|
group_stage_end = seeds_count + group_stage_members_count
|
|
group_stage_teams = selected_teams[seeds_count:group_stage_end] + wildcard_group_stage
|
|
else:
|
|
group_stage_teams = []
|
|
print(f"Group stage teams: {len(group_stage_teams)}")
|
|
|
|
waiting_list_count = len(teams) - self.team_count
|
|
if waiting_list_count < 0:
|
|
waiting_list_count = 0
|
|
print(f"Waiting list count: {waiting_list_count}")
|
|
|
|
if waiting_list_count > 0 or len(waiting_teams) > 0:
|
|
if waiting_list_count > 0:
|
|
waiting_teams = waiting_teams + complete_teams[-waiting_list_count:]
|
|
if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
|
|
waiting_teams.sort(key=lambda s: (s.date, s.initial_weight))
|
|
else:
|
|
waiting_teams.sort(key=lambda s: (s.initial_weight, s.date))
|
|
else:
|
|
waiting_teams = []
|
|
print(f"Final waiting teams: {len(waiting_teams)}")
|
|
|
|
bracket_teams.sort(key=lambda s: s.weight)
|
|
group_stage_teams.sort(key=lambda s: s.weight)
|
|
|
|
for team in bracket_teams:
|
|
if team.stage == "Attente":
|
|
team.set_stage("Tableau")
|
|
|
|
for team in group_stage_teams:
|
|
if team.stage == "Attente":
|
|
team.set_stage("Poule")
|
|
|
|
for team in waiting_teams:
|
|
team.set_stage("Attente")
|
|
|
|
if includeWaitingList is True:
|
|
final_teams = bracket_teams + group_stage_teams + waiting_teams
|
|
print(f"Final teams with waiting list: {len(final_teams)}")
|
|
else:
|
|
final_teams = bracket_teams + group_stage_teams
|
|
print(f"Final teams without waiting list: {len(final_teams)}")
|
|
return final_teams
|
|
|
|
|
|
def match_groups(self, broadcasted, group_stage_id, round_id):
|
|
|
|
display_brackets = self.display_matches()
|
|
|
|
match_groups = []
|
|
if group_stage_id:
|
|
group_stage = self.groupstage_set.filter(id=group_stage_id).first()
|
|
match_groups.append(self.group_stage_match_group(group_stage, broadcasted, hide_empty_matches=False))
|
|
elif round_id:
|
|
round = self.round_set.filter(id=round_id).first()
|
|
if round and display_brackets is True:
|
|
match_groups = self.round_match_groups(round, broadcasted, hide_empty_matches=False)
|
|
else:
|
|
match_groups = self.all_groups(broadcasted)
|
|
|
|
return match_groups
|
|
|
|
def all_groups(self, broadcasted):
|
|
groups = []
|
|
|
|
if self.display_matches():
|
|
for round in self.round_set.filter(parent=None, group_stage_loser_bracket=False).all().order_by('index'):
|
|
groups.extend(self.round_match_groups(round, broadcasted, hide_empty_matches=True))
|
|
|
|
if self.display_group_stages():
|
|
for round in self.round_set.filter(parent=None, group_stage_loser_bracket=True).all().order_by('index'):
|
|
groups.extend(self.round_match_groups(round, broadcasted, hide_empty_matches=True))
|
|
|
|
ordered = sorted(self.get_computed_group_stage(), key=lambda s: (-s.step, s.index))
|
|
for group_stage in ordered:
|
|
group = self.group_stage_match_group(group_stage, broadcasted, hide_empty_matches=True)
|
|
if group:
|
|
groups.append(group)
|
|
|
|
return groups
|
|
|
|
def group_stage_match_group(self, group_stage, broadcasted, hide_empty_matches):
|
|
matches = group_stage.match_set.all()
|
|
if hide_empty_matches:
|
|
matches = [m for m in matches if m.should_appear()]
|
|
else:
|
|
matches = [m for m in matches if m.disabled is False]
|
|
|
|
matches.sort(key=lambda m: (m.start_date is None, m.end_date is not None, m.start_date, m.index))
|
|
|
|
if matches:
|
|
return self.create_match_group(group_stage.display_name(), matches)
|
|
else:
|
|
return None
|
|
|
|
def round_match_groups(self, round, broadcasted, hide_empty_matches):
|
|
groups = []
|
|
|
|
matches = round.match_set.order_by('index').all()
|
|
if hide_empty_matches:
|
|
matches = [m for m in matches if m.should_appear()]
|
|
else:
|
|
matches = [m for m in matches if m.disabled is False]
|
|
|
|
if matches:
|
|
matches.sort(key=lambda m: m.index)
|
|
group = self.create_match_group(round.name(), matches)
|
|
groups.append(group)
|
|
|
|
ranking_matches = round.ranking_matches(hide_empty_matches)
|
|
if hide_empty_matches:
|
|
ranking_matches = [m for m in ranking_matches if m.should_appear()]
|
|
else:
|
|
ranking_matches = [m for m in ranking_matches if m.disabled is False]
|
|
|
|
if len(ranking_matches) > 0:
|
|
ranking_matches.sort(key=lambda m: m.index)
|
|
group = self.create_match_group('Matchs de classement', ranking_matches)
|
|
groups.append(group)
|
|
|
|
return groups
|
|
|
|
def create_match_group(self, name, matches):
|
|
matches = list(matches)
|
|
live_matches = [match.live_match() for match in matches]
|
|
return MatchGroup(name, live_matches)
|
|
|
|
def live_group_stages(self):
|
|
group_stages = self.get_computed_group_stage()
|
|
return [gs.live_group_stages() for gs in group_stages]
|
|
|
|
def get_computed_group_stage(self):
|
|
# Get all group stages and sort by step (descending) and index (ascending)
|
|
group_stages = self.groupstage_set.all().order_by('-step', 'index')
|
|
|
|
# List to collect live group stages from finished steps
|
|
filtered = []
|
|
|
|
for group_stage in group_stages:
|
|
if group_stage.step > 0:
|
|
# Check the previous step's group stages
|
|
previous_step_group_stages = self.groupstage_set.filter(step=group_stage.step - 1)
|
|
|
|
# Check if all previous step group stages are completed
|
|
if all(gs.is_completed() for gs in previous_step_group_stages):
|
|
filtered.append(group_stage)
|
|
else:
|
|
# Always include step 0
|
|
filtered.append(group_stage)
|
|
|
|
return filtered
|
|
|
|
def get_previous_live_group_stages(self, step):
|
|
previous_step_group_stages = self.groupstage_set.filter(step=step).order_by('index')
|
|
return [gs.live_group_stages() for gs in previous_step_group_stages]
|
|
|
|
def last_group_stage_step(self):
|
|
live_group_stages = self.get_computed_group_stage()
|
|
|
|
# Filter to find the last running step
|
|
last_running_step = max(gs.step for gs in live_group_stages) if live_group_stages else None
|
|
|
|
if last_running_step is not None:
|
|
# Get only group stages from the last running step
|
|
group_stages_last_step = [gs for gs in live_group_stages if gs.step == last_running_step]
|
|
return group_stages_last_step
|
|
else:
|
|
return []
|
|
|
|
def broadcast_content(self):
|
|
|
|
matches, group_stages = self.broadcasted_matches_and_group_stages()
|
|
group_stages_dicts = [gs.to_dict() for gs in group_stages]
|
|
|
|
# if now is before the first match, we want to show the summons + group stage or first matches
|
|
# change timezone to datetime to avoid the bug RuntimeWarning: DateTimeField Tournament.start_date received a naive datetime (2024-05-16 00:00:00) while time zone support is active.
|
|
if timezone.now() < self.start_date:
|
|
team_summons_dicts = [summon.to_dict() for summon in self.team_summons()]
|
|
if group_stages:
|
|
return {
|
|
'matches': [],
|
|
'group_stages': group_stages_dicts,
|
|
'summons': team_summons_dicts,
|
|
'event_title' : self.broadcast_event_display_name(),
|
|
'tournament_title' : self.broadcast_display_name(),
|
|
'rankings' : []
|
|
}
|
|
else:
|
|
live_matches_dicts = [match.live_match().to_dict() for match in matches]
|
|
return {
|
|
'matches': live_matches_dicts,
|
|
'group_stages': [],
|
|
'summons': team_summons_dicts,
|
|
'event_title' : self.broadcast_event_display_name(),
|
|
'tournament_title' : self.broadcast_display_name(),
|
|
'rankings' : []
|
|
}
|
|
elif self.end_date is not None:
|
|
live_matches_dicts = [match.live_match().to_dict() for match in matches]
|
|
team_rankings_dicts = [ranking.to_dict() for ranking in self.rankings()]
|
|
return {
|
|
'matches': live_matches_dicts,
|
|
'group_stages': [],
|
|
'summons': [],
|
|
'event_title' : self.broadcast_event_display_name(),
|
|
'tournament_title' : self.broadcast_display_name(),
|
|
'rankings' : team_rankings_dicts
|
|
}
|
|
else: # we want to display the broadcasted content
|
|
live_matches_dicts = [match.live_match().to_dict() for match in matches]
|
|
return {
|
|
'matches': live_matches_dicts,
|
|
'group_stages': group_stages_dicts,
|
|
'summons': [],
|
|
'rankings' : [],
|
|
'event_title' : self.broadcast_event_display_name(),
|
|
'tournament_title' : self.broadcast_display_name(),
|
|
}
|
|
|
|
def broadcasted_matches_and_group_stages(self):
|
|
matches = []
|
|
group_stages = []
|
|
|
|
if len(self.groupstage_set.all()) > 0 and self.no_bracket_match_has_started():
|
|
group_stages = [gs.live_group_stages() for gs in self.last_group_stage_step()]
|
|
matches = self.broadcasted_group_stages_matches()
|
|
first_round = self.first_round()
|
|
if first_round and self.has_all_group_stages_started():
|
|
matches.extend(first_round.get_matches_recursive(True))
|
|
else:
|
|
current_round = self.round_to_show()
|
|
if current_round:
|
|
|
|
# Add full matches from the next rounds
|
|
next_round = self.round_for_index(current_round.index - 1)
|
|
if next_round:
|
|
matches.extend(next_round.get_matches_recursive(True))
|
|
|
|
# Add matches from the previous round or group_stages
|
|
previous_round = self.round_for_index(current_round.index + 1)
|
|
if previous_round:
|
|
matches.extend(current_round.get_matches_recursive(True))
|
|
matches.extend(previous_round.get_matches_recursive(True))
|
|
else:
|
|
matches.extend(current_round.all_matches(True))
|
|
group_stages = [gs.live_group_stages() for gs in self.last_group_stage_step()]
|
|
|
|
return matches, group_stages
|
|
|
|
def no_bracket_match_has_started(self):
|
|
matches = []
|
|
for round in self.round_set.all():
|
|
for match in round.match_set.all():
|
|
if match.started():
|
|
return False
|
|
return True
|
|
|
|
def all_matches(self, hide_empty_matches):
|
|
matches = []
|
|
for round in self.round_set.all():
|
|
matches.extend(round.all_matches(hide_empty_matches))
|
|
for group_stage in self.groupstage_set.all():
|
|
matches.extend(group_stage.match_set.all())
|
|
|
|
matches = [m for m in matches if m.should_appear()]
|
|
|
|
return matches
|
|
|
|
def group_stage_matches(self):
|
|
matches = []
|
|
for group_stage in self.groupstage_set.all():
|
|
matches.extend(group_stage.match_set.all())
|
|
return matches
|
|
|
|
def group_stages_running(self):
|
|
if len(self.groupstage_set.all()) > 0:
|
|
# check le debut des match de Round
|
|
matches = self.group_stage_matches()
|
|
running_group_stage_matches = [m for m in matches if m.end_date is None]
|
|
return len(running_group_stage_matches) > 0
|
|
else:
|
|
return False
|
|
|
|
def first_unfinished_match(self):
|
|
matches = [m for m in self.all_matches(False) if m.start_date and m.end_date is None]
|
|
matches.sort(key=lambda m: m.start_date)
|
|
if matches:
|
|
return matches[0]
|
|
else:
|
|
return None
|
|
|
|
def round_to_show(self):
|
|
last_started_match = self.first_unfinished_match()
|
|
if last_started_match:
|
|
current_round = last_started_match.round.root_round()
|
|
if current_round:
|
|
return current_round
|
|
main_rounds = list(self.round_set.filter(parent=None).all())
|
|
main_rounds.sort(key=lambda r: r.index)
|
|
if main_rounds:
|
|
return main_rounds[0]
|
|
else:
|
|
return None
|
|
|
|
def last_started_match(self):
|
|
matches = [m for m in self.all_matches(False) if m.start_date]
|
|
matches.sort(key=lambda m: m.start_date, reverse=True)
|
|
return matches[0] if matches else None
|
|
|
|
def round_for_index(self, index):
|
|
return self.round_set.filter(index=index, parent=None).first()
|
|
|
|
def first_round(self):
|
|
main_rounds = list(self.round_set.filter(parent=None))
|
|
main_rounds.sort(key=lambda r: r.index, reverse=True)
|
|
return main_rounds[0] if main_rounds else None
|
|
|
|
def broadcasted_group_stages_matches(self):
|
|
matches = []
|
|
group_stages = self.elected_broadcast_group_stages()
|
|
group_stages.sort(key=lambda gs: (gs.index, gs.start_date is None, gs.start_date))
|
|
for group_stage in group_stages:
|
|
matches.extend(group_stage.match_set.all())
|
|
matches = [m for m in matches if m.should_appear()]
|
|
matches.sort(key=lambda m: (m.start_date is None, m.end_date is not None, m.start_date, m.index))
|
|
|
|
group_stage_loser_bracket = list(self.round_set.filter(parent=None, group_stage_loser_bracket=True).all())
|
|
if len(group_stage_loser_bracket) > 0:
|
|
loser_matches = group_stage_loser_bracket[0].all_matches(True)
|
|
loser_matches = [m for m in loser_matches if m.should_appear()]
|
|
loser_matches.sort(key=lambda m: (m.start_date is None, m.end_date is not None, m.start_date, m.index))
|
|
matches.extend(loser_matches)
|
|
|
|
return matches
|
|
|
|
def elected_broadcast_group_stages(self):
|
|
group_stages = list(self.last_group_stage_step())
|
|
started = [gs for gs in group_stages if gs.starts_soon()]
|
|
if len(started) > 0:
|
|
return started
|
|
else:
|
|
return group_stages
|
|
|
|
def display_rankings(self):
|
|
if self.publish_rankings is True and self.end_date is not None:
|
|
return True
|
|
return False
|
|
|
|
def display_tournament(self):
|
|
if self.publish_tournament:
|
|
return True
|
|
|
|
is_build_and_not_empty = self.is_build_and_not_empty()
|
|
|
|
if self.end_date is not None:
|
|
return is_build_and_not_empty
|
|
if datetime.now().date() >= self.start_date.date():
|
|
return is_build_and_not_empty
|
|
minimum_publish_date = self.creation_date.replace(hour=9, minute=0) + timedelta(days=1)
|
|
return timezone.now() >= minimum_publish_date
|
|
|
|
def display_teams(self):
|
|
if self.end_date is not None:
|
|
return self.has_team_registrations()
|
|
if self.publish_teams:
|
|
return self.has_team_registrations()
|
|
if timezone.now().date() >= self.start_date.date():
|
|
return self.has_team_registrations()
|
|
return False
|
|
|
|
def has_team_registrations(self):
|
|
return len(self.teamregistration_set.all()) > 0
|
|
|
|
def display_summons(self):
|
|
if self.end_date is not None:
|
|
return False
|
|
if self.publish_summons:
|
|
return self.has_summons()
|
|
if timezone.now() >= self.start_date:
|
|
return self.has_summons()
|
|
return False
|
|
|
|
def display_group_stages(self):
|
|
if self.end_date is not None:
|
|
return True
|
|
if len(self.groupstage_set.all()) == 0:
|
|
return False
|
|
if self.publish_group_stages:
|
|
return True
|
|
|
|
first_group_stage_start_date = self.group_stage_start_date()
|
|
if first_group_stage_start_date is None:
|
|
return timezone.now() >= self.start_date
|
|
else:
|
|
return timezone.now() >= first_group_stage_start_date
|
|
|
|
def group_stage_start_date(self):
|
|
group_stages = [gs for gs in self.groupstage_set.all() if gs.start_date is not None]
|
|
if len(group_stages) == 0:
|
|
return None
|
|
|
|
return min(group_stages, key=lambda gs: gs.start_date).start_date
|
|
|
|
def display_matches(self):
|
|
if self.end_date is not None:
|
|
return True
|
|
bracket_matches = self.bracket_matches()
|
|
if len(bracket_matches) == 0:
|
|
return self.display_group_stages()
|
|
if self.publish_brackets:
|
|
return True
|
|
|
|
first_match_start_date = self.first_match_start_date(bracket_matches)
|
|
if first_match_start_date is None:
|
|
return timezone.now() >= self.start_date
|
|
|
|
bracket_start_date = self.getEightAm(first_match_start_date)
|
|
|
|
if bracket_start_date < self.start_date:
|
|
bracket_start_date = self.start_date
|
|
|
|
group_stage_start_date = self.group_stage_start_date()
|
|
if group_stage_start_date is not None:
|
|
if bracket_start_date < group_stage_start_date:
|
|
return False
|
|
|
|
|
|
if timezone.now() >= bracket_start_date:
|
|
return True
|
|
|
|
return False
|
|
|
|
def bracket_matches(self):
|
|
matches = []
|
|
for round in self.round_set.all():
|
|
matches.extend(round.all_matches(False))
|
|
return matches
|
|
|
|
def first_match_start_date(self, bracket_matches):
|
|
matches = [m for m in bracket_matches if m.start_date is not None]
|
|
if len(matches) == 0:
|
|
return None
|
|
|
|
return min(matches, key=lambda m: m.start_date).start_date
|
|
|
|
def getEightAm(self, date):
|
|
return date.replace(hour=8, minute=0, second=0, microsecond=0, tzinfo=date.tzinfo)
|
|
|
|
def supposedly_in_progress(self):
|
|
end = self.start_date + timedelta(days=self.day_duration + 1)
|
|
return self.start_date.replace(hour=0, minute=0) <= timezone.now() <= end
|
|
|
|
def display_points_earned(self):
|
|
return self.federal_level_category != FederalLevelCategory.UNLISTED and self.hide_points_earned is False
|
|
|
|
def hide_weight(self):
|
|
return self.hide_teams_weight
|
|
|
|
def is_build_and_not_empty(self):
|
|
return (len(self.groupstage_set.all()) > 0 or len(self.round_set.all()) > 0) and len(self.teamregistration_set.all()) >= 4
|
|
|
|
def day_duration_formatted(self):
|
|
return plural_format("jour", self.day_duration)
|
|
|
|
def has_club_address(self):
|
|
if self.event.club:
|
|
return self.event.club.has_address()
|
|
else:
|
|
return False
|
|
|
|
def has_all_group_stages_started(self):
|
|
for group_stage in self.groupstage_set.all():
|
|
if group_stage.has_at_least_one_started_match() is False:
|
|
return False
|
|
return True
|
|
|
|
class MatchGroup:
|
|
def __init__(self, name, matches):
|
|
self.name = name
|
|
self.matches = matches
|
|
|
|
def add_match(self, match):
|
|
self.matches.append(match)
|
|
|
|
def add_matches(self, matches):
|
|
self.matches = matches
|
|
|
|
class TeamSummon:
|
|
def __init__(self, names, date, weight, stage, court, image):
|
|
self.names = names
|
|
self.date = date
|
|
self.weight = weight
|
|
self.stage = stage
|
|
self.court = court
|
|
self.image = image
|
|
|
|
def formatted_date(self):
|
|
if self.date:
|
|
return formats.date_format(self.date, format='l H:i')
|
|
else:
|
|
return None
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"names": self.names,
|
|
"date": self.formatted_date(),
|
|
"weight": self.weight,
|
|
"stage": self.stage,
|
|
"court": self.court,
|
|
"image": self.image,
|
|
}
|
|
|
|
class TeamList:
|
|
def __init__(self, names, weight, date, initial_weight, wildcard_bracket, wildcard_groupstage, image):
|
|
self.names = names
|
|
self.date = date
|
|
self.weight = weight
|
|
self.initial_weight = initial_weight
|
|
self.image = image
|
|
self.stage = ""
|
|
self.wildcard_bracket = wildcard_bracket
|
|
self.wildcard_groupstage = wildcard_groupstage
|
|
|
|
def set_stage(self, stage):
|
|
self.stage = stage
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"names": self.names,
|
|
"date": self.date,
|
|
"weight": self.weight,
|
|
"initial_weight": self.initial_weight,
|
|
"image": self.image,
|
|
"stage": self.stage,
|
|
"wildcard_bracket": self.wildcard_bracket,
|
|
"wildcard_groupstage": self.wildcard_groupstage,
|
|
}
|
|
|
|
class TeamRanking:
|
|
def __init__(self, names, ranking, points, image):
|
|
self.names = names
|
|
self.ranking = ranking
|
|
self.formatted_ranking = self.ordinal(ranking)
|
|
self.points = self.points_earned_display(points)
|
|
self.image = image
|
|
|
|
# def ranking_display(self):
|
|
# return self.ordinal(self.ranking)
|
|
|
|
def points_earned_display(self, points):
|
|
if points is None:
|
|
return ""
|
|
return f"+{points} pt{self.plural_suffix(points)}"
|
|
|
|
def plural_suffix(self, n):
|
|
if n > 1:
|
|
return 's'
|
|
else:
|
|
return ''
|
|
|
|
def ordinal(self, n):
|
|
suffixes = {1: 'er', 2: 'ème', 3: 'rd'}
|
|
if n == 1:
|
|
suffix = 'er'
|
|
else:
|
|
suffix = 'ème'
|
|
return str(n) + suffix
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"names": self.names,
|
|
"ranking": self.ranking,
|
|
"formatted_ranking": self.formatted_ranking,
|
|
"points": self.points,
|
|
"image": self.image,
|
|
}
|
|
|