diff --git a/shop/models.py b/shop/models.py index 14f64ed..3c9d86e 100644 --- a/shop/models.py +++ b/shop/models.py @@ -1,5 +1,6 @@ from django.db import models from django.conf import settings +from django.utils import timezone class OrderStatus(models.TextChoices): PENDING = 'PENDING', 'Pending' @@ -94,7 +95,6 @@ class Coupon(models.Model): return self.code def is_valid(self): - from django.utils import timezone now = timezone.now() if not self.is_active: return False diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index b7eb6e0..5fad8bb 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -609,7 +609,7 @@ class Tournament(BaseModel): # 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. - current_time = timezone.now() + current_time = timezone.localtime() tournament_start = self.local_start_date() one_hour_before_start = tournament_start - timedelta(hours=1) @@ -852,17 +852,17 @@ class Tournament(BaseModel): if self.end_date is not None: return is_build_and_not_empty - if timezone.now() >= self.local_start_date(): + if self.has_started(): return is_build_and_not_empty - minimum_publish_date = self.creation_date.replace(hour=9, minute=0) + timedelta(days=1) - return timezone.now() >= timezone.localtime(minimum_publish_date) + minimum_publish_date = self.creation_date.replace(hour=7, 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() >= self.local_start_date(): + if self.has_started(): return self.has_team_registrations() return False @@ -874,7 +874,7 @@ class Tournament(BaseModel): return False if self.publish_summons: return self.has_summons() - if timezone.now() >= self.local_start_date(): + if self.has_started(): return self.has_summons() return False @@ -888,7 +888,7 @@ class Tournament(BaseModel): first_group_stage_start_date = self.group_stage_start_date() if first_group_stage_start_date is None: - return timezone.now() >= self.local_start_date() + return self.has_started() else: return timezone.now() >= first_group_stage_start_date @@ -896,9 +896,7 @@ class Tournament(BaseModel): group_stages = [gs for gs in self.group_stages.all() if gs.start_date is not None] if len(group_stages) == 0: return None - - timezone = self.timezone() - return min(group_stages, key=lambda gs: gs.start_date).start_date.astimezone(timezone) + return min(group_stages, key=lambda gs: gs.start_date).start_date def display_matches(self): if self.end_date is not None: @@ -911,19 +909,22 @@ class Tournament(BaseModel): first_match_start_date = self.first_match_start_date(bracket_matches) if first_match_start_date is None: - return timezone.now() >= self.local_start_date() + return self.has_started() bracket_start_date = self.getEightAm(first_match_start_date) - if bracket_start_date < self.local_start_date(): - bracket_start_date = self.local_start_date() + if bracket_start_date < self.start_date: + bracket_start_date = self.start_date group_stage_start_date = self.group_stage_start_date() + + now = timezone.now() + if group_stage_start_date is not None: if bracket_start_date < group_stage_start_date: - return timezone.now() >=first_match_start_date + return now >=first_match_start_date - if timezone.now() >= bracket_start_date: + if now >= bracket_start_date: return True return False @@ -939,45 +940,59 @@ class Tournament(BaseModel): 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).local_start_date() + return min(matches, key=lambda m: m.start_date) def getEightAm(self, date): return date.replace(hour=8, minute=0, second=0, microsecond=0, tzinfo=date.tzinfo) + def has_started(self, hour_delta=None): + timezoned_datetime = self.local_start_date() + now_utc = timezone.now() + now = now_utc.astimezone(self.timezone()) + if hour_delta is not None: + timezoned_datetime -= timedelta(hours=hour_delta) + return now >= timezoned_datetime + + def will_start_soon(self): + return self.has_started(hour_delta=2) + 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 timezoned_datetime = self.local_start_date() end = timezoned_datetime + timedelta(days=self.day_duration + 1) - now = timezone.now() - + now_utc = timezone.now() + now = now_utc.astimezone(self.timezone()) start = timezoned_datetime.replace(hour=0, minute=0) - print(f"timezoned_datetime: {timezoned_datetime}") - print(f"tournament end date: {end}") - print(f"current time: {now}") - print(f"tournament start: {start}") - print(f"start <= now <= end: {start <= now <= end}") + # print(f"timezoned_datetime: {timezoned_datetime}") + # print(f"tournament end date: {end}") + # print(f"current time: {now}") + # print(f"tournament start: {start}") + # print(f"start <= now <= end: {start <= now <= end}") return start <= now <= end def starts_in_the_future(self): # tomorrow = datetime.now().date() + timedelta(days=1) - timezoned_datetime = self.local_start_date() start = timezoned_datetime.replace(hour=0, minute=0) - now = timezone.now() - + now_utc = timezone.now() + now = now_utc.astimezone(self.timezone()) return start >= now + def has_ended(self): + return self.end_date is not None + def should_be_over(self): - if self.end_date is not None: + if self.has_ended(): return True timezoned_datetime = self.local_start_date() end = timezoned_datetime + timedelta(days=self.day_duration + 1) - now = timezone.now() + now_utc = timezone.now() + now = now_utc.astimezone(self.timezone()) return now >= end and self.is_build_and_not_empty() and self.nearly_over() def nearly_over(self): @@ -1096,6 +1111,33 @@ class Tournament(BaseModel): else: return "La sélection se fait par date d'inscription" + def automatic_waiting_list(self): + """ + Determines if automatic waiting list processing should be applied based on the tournament's registration status. + Returns True if automatic waiting list processing should be applied, False otherwise. + """ + + if self.enable_time_to_confirm is False: + return False + # Get the current registration status + status = self.get_online_registration_status() + + # Define which status values should allow automatic waiting list + status_map = { + OnlineRegistrationStatus.OPEN: True, + OnlineRegistrationStatus.NOT_ENABLED: False, + OnlineRegistrationStatus.NOT_STARTED: False, + OnlineRegistrationStatus.ENDED: False, + OnlineRegistrationStatus.WAITING_LIST_POSSIBLE: False, + OnlineRegistrationStatus.WAITING_LIST_FULL: True, # Still manage in case spots open up + OnlineRegistrationStatus.IN_PROGRESS: False, # Allow for last-minute changes + OnlineRegistrationStatus.ENDED_WITH_RESULTS: False, + OnlineRegistrationStatus.CANCELED: False + } + + # Return the mapped value or False as default for any unmapped status + return status_map.get(status, False) + def get_online_registration_status(self): if self.is_canceled(): return OnlineRegistrationStatus.CANCELED @@ -1103,7 +1145,7 @@ class Tournament(BaseModel): return OnlineRegistrationStatus.ENDED_WITH_RESULTS if self.enable_online_registration is False: return OnlineRegistrationStatus.NOT_ENABLED - if self.supposedly_in_progress(): + if self.has_started(): return OnlineRegistrationStatus.ENDED if self.closed_registration_date is not None: return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE @@ -1111,13 +1153,11 @@ class Tournament(BaseModel): now = timezone.now() if self.opening_registration_date is not None: - timezoned_datetime = timezone.localtime(self.opening_registration_date) - if now < timezoned_datetime: + if now < self.opening_registration_date: return OnlineRegistrationStatus.NOT_STARTED if self.registration_date_limit is not None: - timezoned_datetime = timezone.localtime(self.registration_date_limit) - if now > timezoned_datetime: + if now > self.registration_date_limit: return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE if self.team_sorting == TeamSortingType.RANK: @@ -1163,14 +1203,15 @@ class Tournament(BaseModel): # Check if registration is closed if self.registration_date_limit is not None: - if timezone.now() > timezone.localtime(self.registration_date_limit): + if timezone.now() > self.registration_date_limit: return False # Otherwise unregistration is allowed return True def get_waiting_list_position(self): - current_time = timezone.now() + now_utc = timezone.now() + current_time = now_utc.astimezone(self.timezone()) 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: @@ -1315,7 +1356,8 @@ class Tournament(BaseModel): return None def waiting_list_teams(self, teams): - current_time = timezone.now() + now_utc = timezone.now() + current_time = now_utc.astimezone(self.timezone()) 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: @@ -1349,6 +1391,7 @@ class Tournament(BaseModel): and m.court_index is not None ] + now = timezone.now() # Group matches by court matches_by_court = {} courts = set() @@ -1362,7 +1405,7 @@ class Tournament(BaseModel): for court in matches_by_court: matches_by_court[court].sort(key=lambda m: ( m.start_date is None, # None dates come last - m.start_date if m.start_date else timezone.now() + m.start_date if m.start_date else now )) # Sort courts and organize them into groups of 4 @@ -1535,14 +1578,29 @@ class Tournament(BaseModel): # 7. Check urgency overrides apply_business_rules = True - for hours_threshold, override in URGENCY_OVERRIDE["thresholds"].items(): - if hours_until_tournament <= hours_threshold: - apply_business_rules = False - # Ensure minimum response time - minutes_to_confirm = max(minutes_to_confirm, - URGENCY_OVERRIDE["minimum_response_time"] / 10 if getattr(settings, 'LIVE_TESTING', False) - else URGENCY_OVERRIDE["minimum_response_time"]) - break + + # Default business hours + business_start_hour = BUSINESS_RULES["hours"]["start"] + business_end_hour = BUSINESS_RULES["hours"]["end"] + # for hours_threshold, override in URGENCY_OVERRIDE["thresholds"].items(): + # if hours_until_tournament <= hours_threshold: + # apply_business_rules = False + # # Ensure minimum response time + # minutes_to_confirm = max(minutes_to_confirm, + # URGENCY_OVERRIDE["minimum_response_time"] / 10 if getattr(settings, 'LIVE_TESTING', False) + # else URGENCY_OVERRIDE["minimum_response_time"]) + # break + + # Adjust business hours based on tournament proximity + if hours_until_tournament <= 24: + # 24 hours before tournament: 7am - 10pm + business_start_hour = 7 + business_end_hour = 22 + + if hours_until_tournament <= 12: + # 12 hours before tournament: 6am - 1am (next day) + business_start_hour = 6 + business_end_hour = 25 # 1am next day (25 in 24-hour format) # 8. Calculate raw deadline raw_deadline = current_time + timezone.timedelta(minutes=minutes_to_confirm) @@ -1558,23 +1616,14 @@ class Tournament(BaseModel): # 10. Apply business hours rules if needed if apply_business_rules and live_testing is False: # Check if deadline falls outside business hours - is_weekend = raw_deadline.weekday() in BUSINESS_RULES["days"]["weekend"] - before_hours = raw_deadline.hour < BUSINESS_RULES["hours"]["start"] - after_hours = raw_deadline.hour >= BUSINESS_RULES["hours"]["end"] + before_hours = raw_deadline.hour < business_start_hour + after_hours = raw_deadline.hour >= business_end_hour - if is_weekend or before_hours or after_hours: + if before_hours or after_hours: # Extend to next business day - if after_hours or is_weekend: + if after_hours: # Move to next day days_to_add = 1 - if is_weekend: - # If Saturday, move to Monday - if raw_deadline.weekday() == 5: # Saturday - days_to_add = 2 - # If Sunday, move to Monday - elif raw_deadline.weekday() == 6: # Sunday - days_to_add = 1 - raw_deadline += timezone.timedelta(days=days_to_add) # Set to business start hour @@ -1584,7 +1633,7 @@ class Tournament(BaseModel): second=0, microsecond=0 ) - print(f"Is weekend: {is_weekend}, Before hours: {before_hours}, After hours: {after_hours}") + print(f"Before hours: {before_hours}, After hours: {after_hours}") print(f"Live testing: {live_testing}") print(f"Current time: {current_time}") diff --git a/tournaments/signals.py b/tournaments/signals.py index 76aac37..f3c42ec 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -49,18 +49,18 @@ def notify_object_creation_on_discord(created, instance): def notify_team(team, tournament, message_type): print(team, message_type) - if tournament.enable_online_registration is False: - print("returning because tournament.enable_online_registration false") - return + # if tournament.enable_online_registration is False: + # print("returning because tournament.enable_online_registration false") + # return if team.has_registered_online() is False: print("returning because team.has_registered_online false") return - if tournament.should_be_over(): - print("returning becausetournament.should_be_over") - return - if tournament.supposedly_in_progress(): - print("returning because tournament.supposedly_in_progress") + if tournament.has_ended(): + print("returning because tournament.has_ended") return + # if tournament.will_start_soon(): + # print("returning because tournament.supposedly_in_progress") + # return if is_not_sqlite_backend(): print("is_not_sqlite_backend") diff --git a/tournaments/tasks.py b/tournaments/tasks.py index 189581b..d9b5f31 100644 --- a/tournaments/tasks.py +++ b/tournaments/tasks.py @@ -43,7 +43,7 @@ def check_confirmation_deadlines(): processed_teams.add(team_registration.id) tournament = team_registration.tournament - if not tournament or not tournament.enable_online_registration: + if not tournament or not tournament.automatic_waiting_list(): continue teams = tournament.teams(True)