@ -1,4 +1,3 @@
from time import daylight
from zoneinfo import ZoneInfo
from zoneinfo import ZoneInfo
from django . db import models
from django . db import models
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING
@ -8,12 +7,13 @@ if TYPE_CHECKING:
from . import BaseModel , Event , TournamentPayment , FederalMatchCategory , FederalCategory , FederalLevelCategory , FederalAgeCategory , OnlineRegistrationStatus
from . import BaseModel , Event , TournamentPayment , FederalMatchCategory , FederalCategory , FederalLevelCategory , FederalAgeCategory , OnlineRegistrationStatus
import uuid
import uuid
from django . utils import timezone , formats
from django . utils import timezone , formats
from datetime import datetime , timedelta
from datetime import datetime , timedelta , time
from zoneinfo import ZoneInfo
from tournaments . utils . player_search import get_player_name_from_csv
from tournaments . utils . player_search import get_player_name_from_csv
from shared . cryptography import encryption_util
from shared . cryptography import encryption_util
from . . utils . extensions import plural_format
from . . utils . extensions import plural_format
from django . utils . formats import date_format
from . . utils . licence_validator import LicenseValidator
class TeamSortingType ( models . IntegerChoices ) :
class TeamSortingType ( models . IntegerChoices ) :
RANK = 1 , ' Rank '
RANK = 1 , ' Rank '
@ -67,6 +67,7 @@ class Tournament(BaseModel):
initial_seed_round = models . IntegerField ( default = 0 )
initial_seed_round = models . IntegerField ( default = 0 )
initial_seed_count = models . IntegerField ( default = 0 )
initial_seed_count = models . IntegerField ( default = 0 )
enable_online_registration = models . BooleanField ( default = False ) # Equivalent to Bool = false
enable_online_registration = models . BooleanField ( default = False ) # Equivalent to Bool = false
team_count_limit = models . BooleanField ( default = True )
registration_date_limit = models . DateTimeField ( null = True , blank = True ) # Equivalent to Date? = nil
registration_date_limit = models . DateTimeField ( null = True , blank = True ) # Equivalent to Date? = nil
opening_registration_date = models . DateTimeField ( null = True , blank = True ) # Equivalent to Date? = nil
opening_registration_date = models . DateTimeField ( null = True , blank = True ) # Equivalent to Date? = nil
waiting_list_limit = models . IntegerField ( null = True , blank = True ) # Equivalent to Int? = nil
waiting_list_limit = models . IntegerField ( null = True , blank = True ) # Equivalent to Int? = nil
@ -230,36 +231,12 @@ class Tournament(BaseModel):
else :
else :
return None
return None
def tournament_status_display ( self ) :
def get_tournament_status ( self ) :
if self . is_canceled ( ) is True :
return self . get_online_registration_status ( ) . status_localized ( )
return " Annulé "
teams = self . teams ( True )
def get_tournament_status_team_count ( self ) :
if self . supposedly_in_progress ( ) or self . end_date is not None or self . should_be_over ( ) :
active_teams_count = self . team_registrations . filter ( walk_out = False ) . count ( )
teams = [ t for t in teams if t . stage != " Attente " ]
return min ( active_teams_count , self . team_count )
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
registration_status = None
if self . enable_online_registration == True :
registration_status = self . get_online_registration_status ( ) . status_localized ( )
if teams is not None and len ( teams ) > 0 :
word = " inscription "
if len ( teams ) > 1 :
word = word + " s "
if registration_status is not None :
return f " { registration_status } \n { len ( teams ) } { word } "
else :
return f " { len ( teams ) } { word } "
else :
if registration_status is not None :
return f " { registration_status } "
return None
def name_and_event ( self ) :
def name_and_event ( self ) :
event_name = None
event_name = None
@ -331,9 +308,9 @@ class Tournament(BaseModel):
index = i
index = i
# Check if team_count exists
# Check if team_count exists
if self . team_count :
if self . team_count_limit == True :
# Team is not in list
# Team is not in list
if index < self . team_count :
if index < 0 :
print ( " Team is not in list " , index , self . team_count )
print ( " Team is not in list " , index , self . team_count )
return - 1
return - 1
# Return position in waiting list relative to target count
# Return position in waiting list relative to target count
@ -397,7 +374,7 @@ class Tournament(BaseModel):
complete_teams . append ( team )
complete_teams . append ( team )
else :
else :
waiting_teams . append ( team )
waiting_teams . append ( team )
wildcard_bracket = [ ]
return complete_teams , wildcard_bracket , wildcard_group_stage , waiting_teams
return complete_teams , wildcard_bracket , wildcard_group_stage , waiting_teams
def sort_teams ( self , include_waiting_list , complete_teams , wildcard_bracket , wildcard_group_stage , waiting_teams ) :
def sort_teams ( self , include_waiting_list , complete_teams , wildcard_bracket , wildcard_group_stage , waiting_teams ) :
@ -1051,19 +1028,22 @@ class Tournament(BaseModel):
def options_online_registration ( self ) :
def options_online_registration ( self ) :
options = [ ]
options = [ ]
timezone = self . timezone ( )
# Date d'ouverture
# Date d'ouverture
if self . opening_registration_date :
if self . opening_registration_date :
date = formats . date_format ( timezone . localtime ( self . opening_registration_dat e) , format = ' j F Y H:i ' )
date = formats . date_format ( self . opening_registration_date . astimezone ( timezon e) , format = ' j F Y H:i ' )
options . append ( f " Ouverture des inscriptions le { date } " )
options . append ( f " Ouverture des inscriptions le { date } " )
# Date limite
# Date limite
if self . registration_date_limit :
if self . registration_date_limit :
date = formats . date_format ( timezone . localtime ( self . registration_date_limit ) , format = ' j F Y H:i ' )
date = formats . date_format ( self . registration_date_limit . astimezone ( timezone ) , format = ' j F Y H:i ' )
options . append ( f " Clôture des inscriptions le { date } " )
options . append ( f " Clôture des inscriptions le { date } " )
options . append ( self . get_selection_status_localized )
# Cible d'équipes
# Cible d'équipes
if self . team_count :
if self . team_count_limit is True :
options . append ( f " Maximum { self . team_count } équipes " )
options . append ( f " Maximum { self . team_count } équipes " )
# Liste d'attente
# Liste d'attente
@ -1122,13 +1102,23 @@ class Tournament(BaseModel):
return False
return False
return True
return True
def get_selection_status_localized ( self ) :
if self . team_sorting == TeamSortingType . RANK :
return " La sélection se fait par le poids de l ' équipe "
else :
return " La sélection se fait par date d ' inscription "
def get_online_registration_status ( self ) :
def get_online_registration_status ( self ) :
if self . is_canceled ( ) :
return OnlineRegistrationStatus . CANCELED
if self . end_date is not None :
return OnlineRegistrationStatus . ENDED_WITH_RESULTS
if self . enable_online_registration is False :
return OnlineRegistrationStatus . NOT_ENABLED
if self . supposedly_in_progress ( ) :
if self . supposedly_in_progress ( ) :
return OnlineRegistrationStatus . ENDED
return OnlineRegistrationStatus . ENDED
if self . closed_registration_date is not None :
if self . closed_registration_date is not None :
return OnlineRegistrationStatus . ENDED
return OnlineRegistrationStatus . WAITING_LIST_POSSIBLE
if self . end_date is not None :
return OnlineRegistrationStatus . ENDED_WITH_RESULTS
now = timezone . now ( )
now = timezone . now ( )
@ -1140,9 +1130,12 @@ class Tournament(BaseModel):
if self . registration_date_limit is not None :
if self . registration_date_limit is not None :
timezoned_datetime = timezone . localtime ( self . registration_date_limit )
timezoned_datetime = timezone . localtime ( self . registration_date_limit )
if now > timezoned_datetime :
if now > timezoned_datetime :
return OnlineRegistrationStatus . ENDED
return OnlineRegistrationStatus . WAITING_LIST_POSSIBLE
if self . team_count is not None :
if self . team_sorting == TeamSortingType . RANK :
return OnlineRegistrationStatus . OPEN
if self . team_count_limit is True :
# Get all team registrations excluding walk_outs
# Get all team registrations excluding walk_outs
current_team_count = self . team_registrations . exclude ( walk_out = True ) . count ( )
current_team_count = self . team_registrations . exclude ( walk_out = True ) . count ( )
if current_team_count > = self . team_count :
if current_team_count > = self . team_count :
@ -1153,6 +1146,21 @@ class Tournament(BaseModel):
return OnlineRegistrationStatus . WAITING_LIST_POSSIBLE
return OnlineRegistrationStatus . WAITING_LIST_POSSIBLE
return OnlineRegistrationStatus . OPEN
return OnlineRegistrationStatus . OPEN
def get_registration_status_short_label ( self ) :
""" Returns a short label for the registration status """
status = self . get_online_registration_status ( )
return status . short_label ( )
def get_registration_status_class ( self ) :
""" Returns the CSS class for the registration status box """
status = self . get_online_registration_status ( )
return status . box_class ( )
def should_display_status_box ( self ) :
""" Returns whether the registration status box should be displayed """
status = self . get_online_registration_status ( )
return status . display_box ( )
def is_unregistration_possible ( self ) :
def is_unregistration_possible ( self ) :
# Check if tournament has started
# Check if tournament has started
if self . supposedly_in_progress ( ) :
if self . supposedly_in_progress ( ) :
@ -1174,8 +1182,14 @@ class Tournament(BaseModel):
return True
return True
def get_waiting_list_position ( self ) :
def get_waiting_list_position ( self ) :
current_time = timezone . now ( )
local_registration_federal_limit = self . local_registration_federal_limit ( )
if self . team_sorting == TeamSortingType . RANK and local_registration_federal_limit is not None :
if current_time < local_registration_federal_limit :
return - 1
# If no target team count exists, no one goes to waiting list
# If no target team count exists, no one goes to waiting list
if self . team_count is None :
if self . team_count_limit is Fals e:
return - 1
return - 1
# Get count of active teams (not walked out)
# Get count of active teams (not walked out)
@ -1257,7 +1271,6 @@ class Tournament(BaseModel):
current_year + = 1
current_year + = 1
user_age = current_year - int ( birth_year )
user_age = current_year - int ( birth_year )
print ( " user_age " , user_age )
# Check age category restrictions
# Check age category restrictions
if self . federal_age_category == FederalAgeCategory . A11_12 and user_age > 12 :
if self . federal_age_category == FederalAgeCategory . A11_12 and user_age > 12 :
@ -1290,12 +1303,49 @@ class Tournament(BaseModel):
def min_player_rank ( self ) :
def min_player_rank ( self ) :
return FederalLevelCategory . min_player_rank ( self . federal_level_category , self . federal_category , self . federal_age_category )
return FederalLevelCategory . min_player_rank ( self . federal_level_category , self . federal_category , self . federal_age_category )
def first_waiting_list_team ( self , teams ) :
def local_registration_federal_limit ( self ) :
timezone = self . timezone ( )
if self . registration_date_limit is not None :
return self . registration_date_limit . astimezone ( timezone )
if self . closed_registration_date is not None :
return self . closed_registration_date . astimezone ( timezone )
local_start_date = self . local_start_date ( )
if local_start_date is None :
return None
if self . federal_level_category == FederalLevelCategory . P500 :
# 7 days before at 23:59
return ( local_start_date - timedelta ( days = 7 ) ) . replace ( hour = 23 , minute = 59 , second = 59 )
elif self . federal_level_category in [ FederalLevelCategory . P1000 ,
FederalLevelCategory . P1500 ,
FederalLevelCategory . P2000 ] :
# 14 days before at 23:59
return ( local_start_date - timedelta ( days = 14 ) ) . replace ( hour = 23 , minute = 59 , second = 59 )
return None
def waiting_list_teams ( self , teams ) :
current_time = timezone . now ( )
local_registration_federal_limit = self . local_registration_federal_limit ( )
if self . team_sorting == TeamSortingType . RANK and local_registration_federal_limit is not None :
if current_time < local_registration_federal_limit :
return None
if len ( teams ) < = self . team_count :
if len ( teams ) < = self . team_count :
return None
return None
waiting_teams = [ team for team in teams if team . stage == " Attente " ]
waiting_teams = [ team for team in teams if team . stage == " Attente " ]
if len ( waiting_teams ) > 0 :
return waiting_teams
return waiting_teams [ 0 ] . team_registration
def first_waiting_list_team ( self , teams ) :
waiting_list_team = self . waiting_list_teams ( teams )
if waiting_list_team is None :
return None
if len ( waiting_list_team ) > 0 :
return waiting_list_team [ 0 ] . team_registration
else :
return None
def broadcasted_prog ( self ) :
def broadcasted_prog ( self ) :
# Get matches from broadcasted_matches_and_group_stages
# Get matches from broadcasted_matches_and_group_stages
@ -1418,7 +1468,6 @@ class Tournament(BaseModel):
def umpire_contact ( self ) :
def umpire_contact ( self ) :
if self . umpire_custom_contact is not None :
if self . umpire_custom_contact is not None :
print ( self . umpire_custom_contact )
return self . umpire_custom_contact
return self . umpire_custom_contact
if self . event and self . event . creator :
if self . event and self . event . creator :
return self . event . creator . full_name ( )
return self . event . creator . full_name ( )
@ -1427,17 +1476,96 @@ class Tournament(BaseModel):
def umpire_mail ( self ) :
def umpire_mail ( self ) :
if self . umpire_custom_mail is not None :
if self . umpire_custom_mail is not None :
print ( self . umpire_custom_mail )
return self . umpire_custom_mail
return self . umpire_custom_mail
return self . event . creator . email
return self . event . creator . email
def umpire_phone ( self ) :
def umpire_phone ( self ) :
if self . umpire_custom_phone is not None :
if self . umpire_custom_phone is not None :
print ( self . umpire_custom_phone )
return self . umpire_custom_phone
return self . umpire_custom_phone
return self . event . creator . phone
return self . event . creator . phone
@property
def week_day ( self ) :
""" Return the weekday name (e.g., ' Monday ' ) """
date = self . local_start_date ( )
return date_format ( date , format = ' D ' ) + ' . ' # 'l' gives full weekday name
@property
def day ( self ) :
""" Return the day of the month """
date = self . local_start_date ( )
return date . day
@property
def month ( self ) :
"""
Return the month name in lowercase :
- If full month name is 4 letters or fewer , return as is
- If more than 4 letters , return first 4 letters with a dot
"""
date = self . local_start_date ( )
# Get full month name and convert to lowercase
full_month = date_format ( date , format = ' F ' ) . lower ( )
# Check if the month name is 5 letters or fewer
if len ( full_month ) < = 5 :
return full_month
else :
# Truncate to 5 letters and add a dot
return f " { full_month [ : 5 ] } . "
@property
def year ( self ) :
""" Return the year """
date = self . local_start_date ( )
return date . year
@property
def localized_day_duration ( self ) :
"""
Return localized day duration in French :
- If multiple days : ' 2 jours ' , ' 3 jours ' , etc .
- If 1 day and starts after 18 : 00 : ' soirée '
- If 1 day and starts before 18 : 00 : ' journée '
"""
# Assuming day_duration is a property or field that returns the number of days
days = self . day_duration
if days > 1 :
return f " { days } jours "
else :
# For single day events, check the starting hour
start_time = self . local_start_date ( ) . time ( )
evening_threshold = time ( 18 , 0 ) # 18:00 (6 PM)
if start_time > = evening_threshold :
return " soirée "
else :
return " journée "
def get_player_registration_status_by_licence ( self , user ) :
from . import PlayerRegistration
licence_id = user . licence_id
if not licence_id :
return None
validator = LicenseValidator ( licence_id )
stripped_license = validator . stripped_license
# Check if there is a PlayerRegistration for this user in this tournament
user_player = PlayerRegistration . objects . filter (
licence_id__icontains = stripped_license ,
team_registration__tournament = self ,
) . first ( )
if user_player :
return user_player . get_registration_status ( )
else :
return None
class MatchGroup :
class MatchGroup :
def __init__ ( self , name , matches , formatted_schedule , round_id = None ) :
def __init__ ( self , name , matches , formatted_schedule , round_id = None ) :
self . name = name
self . name = name
@ -1489,6 +1617,12 @@ class TeamSummon:
}
}
class TeamItem :
class TeamItem :
def __str__ ( self ) :
return f " TeamItem( { self . team_registration . id } , names= { self . names } , stage= { self . stage } ) "
def __repr__ ( self ) :
return self . __str__ ( )
def __init__ ( self , team_registration ) :
def __init__ ( self , team_registration ) :
self . names = team_registration . team_names ( )
self . names = team_registration . team_names ( )
self . date = team_registration . local_call_date ( )
self . date = team_registration . local_call_date ( )