diff --git a/api/utils.py b/api/utils.py index bbe5dc2..71f145f 100644 --- a/api/utils.py +++ b/api/utils.py @@ -3,3 +3,17 @@ import re def is_valid_email(email): email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' return re.match(email_regex, email) is not None + +def check_version_smaller_than_1_1_12(version_str): + # Remove the parentheses part if it exists, example of version: 1.1.12 (2) + version_str = version_str.split()[0] + if version_str: + + # Split version into components + version_parts = [int(x) for x in version_str.split('.')] + target_parts = [1, 1, 12] + + # Compare version components + return version_parts < target_parts + else: + return False diff --git a/api/views.py b/api/views.py index de2f1d5..b904687 100644 --- a/api/views.py +++ b/api/views.py @@ -24,10 +24,12 @@ from django.utils.decorators import method_decorator from collections import defaultdict from .permissions import IsClubOwner -from .utils import is_valid_email +from .utils import is_valid_email, check_version_smaller_than_1_1_12 from sync.models import Device +from shared.discord import send_discord_log_message + @method_decorator(csrf_exempt, name='dispatch') class CustomAuthToken(APIView): permission_classes = [] @@ -135,6 +137,17 @@ class TournamentViewSet(SoftDeleteViewSet): return [] return self.queryset.filter(event__creator=self.request.user) + def perform_create(self, serializer): + serializer.save() + # version check + app_version = self.request.headers.get('App-Version') + self.warn_if_version_is_too_small(app_version) + + def warn_if_version_is_too_small(self, version): + if check_version_smaller_than_1_1_12(version): + message = f'{self.request.user.username} app version is {version}' + send_discord_log_message(message) + class PurchaseViewSet(SoftDeleteViewSet): queryset = Purchase.objects.all() serializer_class = PurchaseSerializer diff --git a/shared/discord.py b/shared/discord.py new file mode 100644 index 0000000..9b7e262 --- /dev/null +++ b/shared/discord.py @@ -0,0 +1,19 @@ +import requests + +DISCORD_FAILED_CALLS_WEBHOOK_URL = 'https://discord.com/api/webhooks/1248191778134163486/sSoTL6cULCElWr2YFwyllsg7IXxHcCx_YMDJA_cUHtVUU4WOfN-5M7drCJuwNBBfAk9a' +DISCORD_LOGS_WEBHOOK_URL = 'https://discord.com/api/webhooks/1257987637449588736/TtOUwzYgSlQH2d3Ps7SfIKRcFALQVa3hfkC-j9K4_UAcWtsfiw4v8NUPbnX2_ZPOYzuv' + +def send_discord_failed_calls_message(message): + send_discord_message(DISCORD_FAILED_CALLS_WEBHOOK_URL, message) + +def send_discord_log_message(message): + send_discord_message(DISCORD_LOGS_WEBHOOK_URL, message) + +def send_discord_message(webhook_url, content): + try: + data = { + "content": content + } + requests.post(webhook_url, json=data) + except Exception as e: + print(f"Failed to send Discord message: {str(e)}") diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 41d3150..c050ac1 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -337,6 +337,10 @@ class Tournament(BaseModel): print("else", index, self.team_count) return -1 + def group_stage_spots(self): + """Returns the total number of spots in all group stages.""" + return sum(gs.size for gs in self.group_stages.all()) + def teams(self, include_waiting_list): """ Get sorted list of teams for the tournament. @@ -371,6 +375,14 @@ class Tournament(BaseModel): (team_reg.registration_date and team_reg.registration_date <= closed_date) ) + # Set initial stage + if team_reg.group_stage_position is not None: + team.set_stage("Poule") + elif team_reg.bracket_position is not None: + team.set_stage("Tableau") + else: + team.set_stage("Attente") + # Categorize team if team_reg.wild_card_bracket: wildcard_bracket.append(team) @@ -381,13 +393,15 @@ class Tournament(BaseModel): else: waiting_teams.append(team) - # Set initial stage - if team_reg.group_stage_position is not None: - team.set_stage("Poule") - elif team_reg.bracket_position is not None: - team.set_stage("Tableau") - else: - team.set_stage("Attente") + + # Initialize group stage spots + group_stage_spots = self.group_stage_spots() + bracket_seeds = self.team_count - group_stage_spots - len(wildcard_bracket) + group_stage_team_count = group_stage_spots - len(wildcard_group_stage) + if group_stage_team_count < 0: + group_stage_team_count = 0 + if bracket_seeds < 0: + bracket_seeds = 0 # Sort teams based on tournament rules if self.team_sorting == TeamSortingType.INSCRIPTION_DATE: @@ -407,25 +421,45 @@ class Tournament(BaseModel): complete_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) waiting_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) - # Determine final team list based on tournament settings - if len(complete_teams) <= self.team_count: - all_teams = wildcard_bracket + wildcard_group_stage + complete_teams - if include_waiting_list: - all_teams.extend(waiting_teams) - return all_teams + wildcard_group_stage.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) + wildcard_bracket.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) # Split teams into main bracket and waiting list - qualified_teams = complete_teams[:self.team_count] - excess_teams = complete_teams[self.team_count:] + computed_team_count = self.team_count - len(wildcard_bracket) - len(wildcard_group_stage) + if computed_team_count < 0: + computed_team_count = 0 + qualified_teams = complete_teams[:computed_team_count] + excess_teams = complete_teams[computed_team_count:] + + qualified_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) + excess_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) # Combine all waiting list teams waiting_list = excess_teams + waiting_teams + if self.team_sorting == TeamSortingType.INSCRIPTION_DATE: + waiting_list.sort(key=lambda t: ( + t.registration_date is None, + t.registration_date or datetime.min, + t.initial_weight, + t.team_registration.id + )) + else: + waiting_list.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) # Return final sorted list - if include_waiting_list: - return wildcard_bracket + wildcard_group_stage + qualified_teams + waiting_list - return wildcard_bracket + wildcard_group_stage + qualified_teams + bracket_teams = qualified_teams[:bracket_seeds] + wildcard_bracket + gs_teams = qualified_teams[bracket_seeds:(bracket_seeds+group_stage_team_count)] + wildcard_group_stage + bracket_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) + gs_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id)) + all_teams = bracket_teams + gs_teams + for team in bracket_teams: + team.set_stage("Tableau") + for team in gs_teams: + team.set_stage("Poule") + if include_waiting_list: + all_teams.extend(waiting_list) + return all_teams def match_groups(self, broadcasted, group_stage_id, round_id): diff --git a/tournaments/services/tournament_registration.py b/tournaments/services/tournament_registration.py index f7fbb3a..90492ba 100644 --- a/tournaments/services/tournament_registration.py +++ b/tournaments/services/tournament_registration.py @@ -307,5 +307,6 @@ class TournamentRegistrationService: def _license_already_registered(self, stripped_license): return PlayerRegistration.objects.filter( team_registration__tournament=self.tournament, - licence_id__startswith=stripped_license + licence_id__startswith=stripped_license, + team_registration__walk_out=False ).exists() diff --git a/tournaments/signals.py b/tournaments/signals.py index 7e05b91..306801d 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -12,6 +12,8 @@ from tournaments.services.email_service import TournamentEmailService # Others +from shared.discord import send_discord_log_message, send_discord_failed_calls_message + def generate_unique_code(): characters = string.ascii_lowercase + string.digits while True: @@ -25,19 +27,16 @@ def assign_unique_code(sender, instance, **kwargs): instance.broadcast_code = generate_unique_code() instance.save() -DISCORD_FAILED_CALLS_WEBHOOK_URL = 'https://discord.com/api/webhooks/1248191778134163486/sSoTL6cULCElWr2YFwyllsg7IXxHcCx_YMDJA_cUHtVUU4WOfN-5M7drCJuwNBBfAk9a' -DISCORD_LOGS_WEBHOOK_URL = 'https://discord.com/api/webhooks/1257987637449588736/TtOUwzYgSlQH2d3Ps7SfIKRcFALQVa3hfkC-j9K4_UAcWtsfiw4v8NUPbnX2_ZPOYzuv' - @receiver(post_save, sender=FailedApiCall) def notify_discord_on_create(sender, instance, created, **kwargs): - notify_object_creation_on_discord(created, instance, DISCORD_FAILED_CALLS_WEBHOOK_URL) + notify_object_creation_on_discord(created, instance) @receiver(post_save, sender=Log) def notify_log_creation_on_discord(sender, instance, created, **kwargs): - notify_object_creation_on_discord(created, instance, DISCORD_LOGS_WEBHOOK_URL) + notify_object_creation_on_discord(created, instance) # WARNING: using this method requires the instance to have a discord_string method -def notify_object_creation_on_discord(created, instance, webhook_url): +def notify_object_creation_on_discord(created, instance): if created: default_db_engine = settings.DATABASES['default']['ENGINE'] if default_db_engine != 'django.db.backends.sqlite3': @@ -46,17 +45,22 @@ def notify_object_creation_on_discord(created, instance, webhook_url): message = f'{site_name} > {instance.__class__.__name__} created: {instance.discord_string()}' else: message = "no message. Please configure 'discord_string' on your instance" - send_discord_message(webhook_url, message) - -def send_discord_message(webhook_url, content): - data = { - "content": content - } - requests.post(webhook_url, json=data) - # if response.status_code != 204: - # raise ValueError( - # f'Error sending message to Discord webhook: {response.status_code}, {response.text}' - # ) + + if isinstance(instance, FailedApiCall): + send_discord_failed_calls_message(message) + else: + send_discord_log_message(message) + + +# def send_discord_message(webhook_url, content): +# data = { +# "content": content +# } +# requests.post(webhook_url, json=data) +# # if response.status_code != 204: +# # raise ValueError( +# # f'Error sending message to Discord webhook: {response.status_code}, {response.text}' +# # ) @receiver(pre_delete, sender=TeamRegistration) def unregister_team(sender, instance, **kwargs): diff --git a/tournaments/templates/tournaments/teams.html b/tournaments/templates/tournaments/teams.html index 1d18ac6..d6cb0ba 100644 --- a/tournaments/templates/tournaments/teams.html +++ b/tournaments/templates/tournaments/teams.html @@ -11,31 +11,31 @@ {% load static %} - {% include 'tournaments/navigation_tournament.html' %} +{% include 'tournaments/navigation_tournament.html' %} +