diff --git a/requirements.txt b/requirements.txt index 9a429a3..cd5b9c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ pandas==2.2.2 xlrd==2.0.1 openpyxl==3.1.5 django-filter==24.3 +cryptography==41.0.7 diff --git a/tournaments/admin.py b/tournaments/admin.py index 70ef07c..6656fa3 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -5,7 +5,7 @@ from django.utils import timezone from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer from .forms import CustomUserCreationForm, CustomUserChangeForm -from .filters import TeamScoreTournamentListFilter, MatchTournamentListFilter, SimpleTournamentListFilter, MatchTypeListFilter, SimpleIndexListFilter +from .filters import TeamScoreTournamentListFilter, MatchTournamentListFilter, SimpleTournamentListFilter, MatchTypeListFilter, SimpleIndexListFilter, StartDateRangeFilter from sync.admin import SyncedObjectAdmin @@ -46,12 +46,12 @@ class EventAdmin(SyncedObjectAdmin): class TournamentAdmin(SyncedObjectAdmin): list_display = ['display_name', 'event', 'is_private', 'start_date', 'payment', 'creator'] - list_filter = ['is_deleted', 'event__creator'] + list_filter = [StartDateRangeFilter, 'is_deleted', 'event__creator'] ordering = ['-start_date'] search_fields = ['id'] class TeamRegistrationAdmin(SyncedObjectAdmin): - list_display = ['player_names', 'group_stage_position', 'name', 'tournament'] + list_display = ['player_names', 'group_stage_position', 'name', 'tournament', 'registration_date'] list_filter = [SimpleTournamentListFilter] search_fields = ['id'] @@ -68,8 +68,8 @@ class RoundAdmin(SyncedObjectAdmin): class PlayerRegistrationAdmin(SyncedObjectAdmin): list_display = ['first_name', 'last_name', 'licence_id', 'rank'] - search_fields = ('id', 'first_name', 'last_name') - list_filter = [TeamScoreTournamentListFilter] + search_fields = ('id', 'first_name', 'last_name', 'licence_id__icontains') + list_filter = ['registered_online', TeamScoreTournamentListFilter] ordering = ['last_name', 'first_name'] class MatchAdmin(SyncedObjectAdmin): @@ -109,6 +109,7 @@ class LogAdmin(admin.ModelAdmin): class DeviceTokenAdmin(admin.ModelAdmin): list_display = ['user', 'value'] + list_filter = ['user'] class DrawLogAdmin(SyncedObjectAdmin): list_display = ['tournament', 'draw_date', 'draw_seed', 'draw_match_index', 'draw_team_position'] diff --git a/tournaments/filters.py b/tournaments/filters.py index 167915f..1b841e6 100644 --- a/tournaments/filters.py +++ b/tournaments/filters.py @@ -2,6 +2,8 @@ from django.contrib import admin from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall from django.db.models import Q from django.utils.translation import gettext_lazy as _ +from django.utils import timezone +from datetime import timedelta import uuid from enum import Enum @@ -101,3 +103,36 @@ class SimpleIndexListFilter(admin.SimpleListFilter): return queryset.filter(index=self.value()) else: return queryset + +class StartDateRangeFilter(admin.SimpleListFilter): + title = 'tournament time range' # displayed in the admin UI + parameter_name = 'date_range' # URL parameter + + def lookups(self, request, model_admin): + return ( + ('upcoming', 'Next 30 days'), + ('recent', 'Last 30 days'), + ('current', 'Current (±3 days)'), + ) + + def queryset(self, request, queryset): + if not self.value(): + return queryset + + today = timezone.now().date() + + if self.value() == 'upcoming': + return queryset.filter( + start_date__gte=today, + start_date__lte=today + timedelta(days=30) + ) + elif self.value() == 'recent': + return queryset.filter( + start_date__gte=today - timedelta(days=30), + start_date__lte=today + ) + elif self.value() == 'current': + return queryset.filter( + start_date__gte=today - timedelta(days=3), + start_date__lte=today + timedelta(days=3) + ) diff --git a/tournaments/models/club.py b/tournaments/models/club.py index 0c71209..75cc2a5 100644 --- a/tournaments/models/club.py +++ b/tournaments/models/club.py @@ -34,7 +34,7 @@ class Club(BaseModel): return self.name def events_count(self): - return len(self.events.all()) + return self.events.count() def court_name(self, index): for court in self.courts.all(): diff --git a/tournaments/models/custom_user.py b/tournaments/models/custom_user.py index 8369a12..3137948 100644 --- a/tournaments/models/custom_user.py +++ b/tournaments/models/custom_user.py @@ -1,4 +1,5 @@ from django.db import models +from django.apps import apps from django.contrib.auth.models import AbstractUser from django.utils.timezone import now @@ -59,7 +60,7 @@ class CustomUser(AbstractUser): return f"{self.username} : {self.first_name} {self.last_name} | {self.email} | {self.phone}" def event_count(self): - return len(self.events.all()) + return self.events.count() def full_name(self): return f"{self.first_name} {self.last_name}" @@ -68,4 +69,12 @@ class CustomUser(AbstractUser): latest_event = self.events.order_by('-creation_date').first() if latest_event and latest_event.club: return latest_event.club.name + if self.licence_id: + PlayerRegistration = apps.get_model('tournaments', 'PlayerRegistration') + player_registration = PlayerRegistration.objects.filter(licence_id=self.licence_id).order_by('team_registration__registration_date').first() + try: + return player_registration.team_registration.tournament.event.club.name + except AttributeError: + return None + return None diff --git a/tournaments/models/enums.py b/tournaments/models/enums.py index 77d69a4..0afb918 100644 --- a/tournaments/models/enums.py +++ b/tournaments/models/enums.py @@ -96,6 +96,26 @@ class FederalMatchCategory(models.IntegerChoices): SINGLE_SET_OF_FOUR_GAMES = 13, 'Single set of four games' SINGLE_SET_OF_FOUR_GAMES_DECISIVE_POINT = 14, 'Single set of four games with decisive point' + @property + def format_label_short(self): + format_mapping = { + self.TWO_SETS: "A1", + self.TWO_SETS_SUPER_TIE: "B1", + self.TWO_SETS_FOUR_GAME: "C1", + self.NINE_GAMES: "D1", + self.SUPER_TIE: "E", + self.TWO_SETS_OF_SUPER_TIE: "G", + self.MEGA_TIE: "F", + self.SINGLE_SET: "H1", + self.SINGLE_SET_DECISIVE_POINT: "H2", + self.TWO_SETS_DECISIVE_POINT: "A2", + self.TWO_SETS_DECISIVE_POINT_SUPER_TIE: "B2", + self.TWO_SETS_FOUR_GAME_DECISIVE_POINT: "C2", + self.NINE_GAMES_DECISIVE_POINT: "D2", + self.SINGLE_SET_OF_FOUR_GAMES: "I1" + } + return format_mapping.get(self, "") + def last_set_is_tie_break(value): if value == FederalMatchCategory.TWO_SETS_FOUR_GAME or value == FederalMatchCategory.TWO_SETS_FOUR_GAME_DECISIVE_POINT or value == FederalMatchCategory.TWO_SETS_SUPER_TIE or value == FederalMatchCategory.SUPER_TIE or value == FederalMatchCategory.MEGA_TIE or value == FederalMatchCategory.TWO_SETS_DECISIVE_POINT_SUPER_TIE: return True diff --git a/tournaments/models/group_stage.py b/tournaments/models/group_stage.py index 6945c10..6829404 100644 --- a/tournaments/models/group_stage.py +++ b/tournaments/models/group_stage.py @@ -156,7 +156,7 @@ class GroupStage(SideStoreModel): return False def is_completed(self): - return not self.matches.filter(end_date__isnull=True).exists() + return not self.matches.filter(end_date__isnull=True).exists() and self.matches.count() > 0 class LiveGroupStage: def __init__(self, title, step, index): diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 82999b9..1346b42 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -97,7 +97,7 @@ class Match(SideStoreModel): previous_index = self.round.index + 1 # Check if the next index is within the bounds of the rounds array - if previous_index < len(self.round.tournament.rounds.all()): + if previous_index < self.round.tournament.rounds.count(): return self.round.tournament.rounds.filter(index=previous_index, parent = None).first() # Return None or an appropriate value if the index is out of bounds @@ -149,6 +149,9 @@ class Match(SideStoreModel): team = Team(None, image, names, scores, weight, is_winner, walk_out) 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()) @@ -160,7 +163,7 @@ class Match(SideStoreModel): previous_top_match = self.precedent_match(True) previous_bottom_match = self.precedent_match(False) if len(team_scores) == 0: - if (self.round and len(self.round.tournament.rounds.all()) == self.round.index -1): + if (self.round and self.round.tournament.rounds.count() == self.round.index -1): return teams if (self.group_stage): return teams @@ -292,7 +295,7 @@ class Match(SideStoreModel): def should_appear(self): if self.disabled is True: return False - return len(self.team_scores.all()) > 0 + return self.team_scores.count() > 0 # elif self.group_stage is None: # if len(self.team_scores.all()) == 2: @@ -330,7 +333,9 @@ class Match(SideStoreModel): group_stage_name = self.group_stage.display_name() ended = self.end_date is not None - livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name) + live_format = "Format " + FederalMatchCategory(self.format).format_label_short + + livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name, live_format) for team in self.live_teams(): livematch.add_team(team) @@ -381,7 +386,7 @@ class Team: } class LiveMatch: - def __init__(self, title, date, time_indication, court, started, ended, group_stage_name): + def __init__(self, title, date, time_indication, court, started, ended, group_stage_name, format): self.title = title self.date = date self.teams = [] @@ -391,6 +396,7 @@ class LiveMatch: self.ended = ended self.has_walk_out = False self.group_stage_name = group_stage_name + self.format = format def add_team(self, team): self.teams.append(team) @@ -408,6 +414,7 @@ class LiveMatch: "ended": self.ended, "has_walk_out": self.has_walk_out, "group_stage_name": self.group_stage_name, + "format": self.format } def show_time_indication(self): diff --git a/tournaments/models/team_registration.py b/tournaments/models/team_registration.py index bdd9e22..8b04ae0 100644 --- a/tournaments/models/team_registration.py +++ b/tournaments/models/team_registration.py @@ -123,7 +123,7 @@ class TeamRegistration(SideStoreModel): self.save() # Save the updated weight if necessary def is_valid_for_summon(self): - return len(self.player_registrations.all()) > 0 + return self.player_registrations.count() > 0 or self.name is not None def initial_weight(self): if self.locked_weight is None: @@ -186,10 +186,32 @@ class TeamRegistration(SideStoreModel): 'points_earned': self.get_points_earned(), 'initial_stage': self.get_initial_stage(), 'matches_played': self.count_matches_played(), - 'victory_ratio': self.calculate_victory_ratio() + 'victory_ratio': self.calculate_victory_ratio(), + 'team_rank': self.team_rank_label(), + 'total_teams': self.total_teams(), } return stats + def team_rank(self): + teams = self.tournament.teams(False) # Get list of TeamItem objects + try: + # Find the TeamItem that corresponds to this TeamRegistration + team_index = next(i for i, team in enumerate(teams) + if team.team_registration.id == self.id) + return team_index + 1 + except (StopIteration, ValueError): + return None + + def team_rank_label(self): + team_rank = self.team_rank() + if team_rank is None: + return "--" + return f"{team_rank}" + + def total_teams(self): + teams = self.tournament.teams(False) + return f"{len(teams)}" + def get_initial_stage(self): matches = self.get_matches().order_by('start_date') first_match = matches.first() @@ -204,10 +226,22 @@ class TeamRegistration(SideStoreModel): def get_final_ranking(self): if self.final_ranking: if self.final_ranking == 1: - return "1er" - return f"{self.final_ranking}e" + return "1er" + self.ranking_delta() + return f"{self.final_ranking}ème" + self.ranking_delta() return None + def ranking_delta(self): + team_rank = self.team_rank() + if team_rank is None or self.final_ranking is None: + return "" + + sign = "-" + if team_rank > self.final_ranking: + sign = "+" + if team_rank == self.final_ranking: + sign = "" + return f" ({sign}"+f"{abs(self.final_ranking - team_rank)})" + def get_points_earned(self): return self.points_earned @@ -234,6 +268,6 @@ class TeamRegistration(SideStoreModel): total_matches = matches.count() if total_matches > 0: wins = matches.filter(winning_team_id=self.id).count() - ratio = (wins / total_matches) * 100 + # ratio = (wins / total_matches) * 100 return f"{wins}/{total_matches}" return None diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 22fc955..37c0a0d 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -229,7 +229,7 @@ class Tournament(BaseModel): return "Annulé" teams = self.teams(True) - if self.supposedly_in_progress() or self.end_date is not None: + if self.supposedly_in_progress() or self.end_date is not None or self.should_be_over(): teams = [t for t in teams if t.stage != "Attente"] if teams is not None and len(teams) > 0: word = "équipe" @@ -337,126 +337,94 @@ class Tournament(BaseModel): print("else", index, self.team_count) return -1 - 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}") + def teams(self, include_waiting_list): + """ + Get sorted list of teams for the tournament. - for team_registration in self.team_registrations.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.out_of_tournament() is False: - team = TeamItem(team_registration) - # 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.team_registration.id)) - 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}") + Args: + include_waiting_list (bool): Whether to include teams in waiting list - if self.team_sorting == TeamSortingType.INSCRIPTION_DATE: - complete_teams.sort(key=lambda s: (s.registration_date is None, s.registration_date or datetime.min, s.initial_weight, s.team_registration.id)) - else: - complete_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id)) - - selected_teams = complete_teams[:self.team_count] - selected_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id)) + Returns: + list: List of TeamItem objects sorted according to tournament rules + """ + # Initialize team categories + complete_teams = [] + wildcard_bracket = [] + wildcard_group_stage = [] + waiting_teams = [] - if seeds_count > 0: - bracket_teams = selected_teams[:seeds_count] + wildcard_bracket - else: - bracket_teams = [] - # print(f"Bracket teams: {len(bracket_teams)}") + # Get registration cutoff date + closed_date = self.closed_registration_date + + # Process each team registration + for team_reg in self.team_registrations.all(): + if team_reg.out_of_tournament(): + continue + + # Create team item + team = TeamItem(team_reg) + + # Determine if registration is valid based on date + is_valid = ( + closed_date is None or + team_reg.registration_date is None or + (team_reg.registration_date and team_reg.registration_date <= closed_date) + ) + + # Categorize team + if team_reg.wild_card_bracket: + wildcard_bracket.append(team) + elif team_reg.wild_card_group_stage: + wildcard_group_stage.append(team) + elif is_valid: + complete_teams.append(team) + else: + waiting_teams.append(team) - 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.registration_date is None, s.registration_date or datetime.min, s.initial_weight, s.team_registration.id)) + # 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: - waiting_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id)) - else: - waiting_teams = [] - # print(f"Final waiting teams: {len(waiting_teams)}") + team.set_stage("Attente") - bracket_teams.sort(key=lambda s: (s.weight, s.team_registration.id)) - group_stage_teams.sort(key=lambda s: (s.weight, s.team_registration.id)) + # Sort teams based on tournament rules + if self.team_sorting == TeamSortingType.INSCRIPTION_DATE: + complete_teams.sort(key=lambda t: ( + t.registration_date is None, + t.registration_date or datetime.min, + t.initial_weight, + t.team_registration.id + )) + waiting_teams.sort(key=lambda t: ( + t.registration_date is None, + t.registration_date or datetime.min, + t.initial_weight, + t.team_registration.id + )) + else: + 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)) - for team in bracket_teams: - if team.stage == "Attente": - team.set_stage("Tableau") + # 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 - for team in group_stage_teams: - if team.stage == "Attente": - team.set_stage("Poule") + # Split teams into main bracket and waiting list + qualified_teams = complete_teams[:self.team_count] + excess_teams = complete_teams[self.team_count:] - for team in waiting_teams: - team.set_stage("Attente") + # Combine all waiting list teams + waiting_list = excess_teams + waiting_teams - 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 + # 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 def match_groups(self, broadcasted, group_stage_id, round_id): @@ -660,7 +628,7 @@ class Tournament(BaseModel): matches = [] group_stages = [] - if len(self.group_stages.all()) > 0 and self.no_bracket_match_has_started(): + if self.group_stages.count() > 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() @@ -750,24 +718,24 @@ class Tournament(BaseModel): # all started matches have ended, possibly last_finished_match = self.last_finished_match() - round = last_finished_match.round - if round is None: # when the last finished match is in the group stage - round = self.rounds.filter(parent__isnull=True).order_by('-index').first() - - if round: - # print(f'last_finished_match = {last_finished_match.name}') - round_root_index = round.root_round().index - # print(f'round_index = {round_root_index}') - if round_root_index == 0: - return round - else: - round = self.round_set.filter(parent=None,index=round_root_index-1).first() - if round: + if last_finished_match: + round = last_finished_match.round + if round is None: # when the last finished match is in the group stage + round = self.rounds.filter(parent__isnull=True).order_by('-index').first() + + if round: + # print(f'last_finished_match = {last_finished_match.name}') + round_root_index = round.root_round().index + # print(f'round_index = {round_root_index}') + if round_root_index == 0: return round else: - return None - else: - return None + round = self.rounds.filter(parent=None,index=round_root_index-1).first() + if round: + return round + else: + return None + return None def last_started_match(self): matches = [m for m in self.all_matches(False) if m.start_date] @@ -843,7 +811,7 @@ class Tournament(BaseModel): return False def has_team_registrations(self): - return len(self.team_registrations.all()) > 0 + return self.team_registrations.count() > 0 def display_summons(self): if self.end_date is not None: @@ -938,6 +906,35 @@ class Tournament(BaseModel): return start <= now <= end + def should_be_over(self): + if self.end_date is not None: + return True + + timezoned_datetime = timezone.localtime(self.start_date) + end = timezoned_datetime + timedelta(days=self.day_duration + 1) + now = timezone.now() + return now >= end and self.is_build_and_not_empty() and self.nearly_over() + + def nearly_over(self): + # First check group stages if they exist + if self.group_stages.count() > 0: + # Check if all group stages are completed + for group_stage in self.group_stages.all(): + if group_stage.is_completed(): + return True + + # If no group stages, check semi-finals + if self.rounds.count() > 0: + # Get round with index 1 (semi-finals) and no parent + semifinals = self.rounds.filter(index=1, parent=None).first() + if semifinals: + # Check if any match in semi-finals has started + for match in semifinals.matches.all(): + if match.start_date is not None and match.is_ready(): + return True + return False + + return False def display_points_earned(self): return self.federal_level_category != FederalLevelCategory.UNLISTED and self.hide_points_earned is False @@ -946,7 +943,7 @@ class Tournament(BaseModel): return self.hide_teams_weight def is_build_and_not_empty(self): - return (len(self.group_stages.all()) > 0 or len(self.rounds.all()) > 0) and len(self.team_registrations.all()) >= 4 + return self.group_stages.count() > 0 or self.rounds.count() > 0 and self.team_registrations.count() >= 4 def day_duration_formatted(self): return plural_format("jour", self.day_duration) @@ -1028,7 +1025,7 @@ class Tournament(BaseModel): # Check target team count and waiting list limit if self.team_count is not None: - current_team_count = len([tr for tr in self.team_registrations.all() if tr.out_of_tournament() is False]) + current_team_count = self.team_registrations.exclude(walk_out=True).count() if current_team_count >= self.team_count: if self.waiting_list_limit is not None: waiting_list_count = current_team_count - self.team_count @@ -1058,7 +1055,7 @@ class Tournament(BaseModel): if self.team_count is not None: # Get all team registrations excluding walk_outs - current_team_count = len([tr for tr in self.team_registrations.all() if tr.out_of_tournament() is False]) + current_team_count = self.team_registrations.exclude(walk_out=True).count() if current_team_count >= self.team_count: if self.waiting_list_limit is not None: waiting_list_count = current_team_count - self.team_count @@ -1093,7 +1090,7 @@ class Tournament(BaseModel): return -1 # Get count of active teams (not walked out) - current_team_count = len([tr for tr in self.team_registrations.all() if tr.out_of_tournament() is False]) + current_team_count = self.team_registrations.exclude(walk_out=True).count() # If current count is less than target count, next team is not in waiting list if current_team_count < self.team_count: diff --git a/tournaments/signals.py b/tournaments/signals.py index 87e9a4b..7efc071 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -52,11 +52,11 @@ def send_discord_message(webhook_url, content): data = { "content": content } - response = 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}' - ) + 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/static/misc/required-version.txt b/tournaments/static/misc/required-version.txt new file mode 100644 index 0000000..49d5957 --- /dev/null +++ b/tournaments/static/misc/required-version.txt @@ -0,0 +1 @@ +0.1 diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index a3690f8..eb54c16 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -246,6 +246,7 @@ tr { font-family: "Anybody-ExtraBold"; font-size: 1.2em; color: #1b223a; + line-height: 24px; /* Match the height of flex-row */ } .title { @@ -816,9 +817,9 @@ h-margin { } .download-button { margin-right: 6px; - color: #1a223a; + color: #fff7ed; padding: 8px 12px; - background-color: white; + background-color: #1a223a; border-radius: 12px; text-decoration: none; font-size: 12px; @@ -826,7 +827,7 @@ h-margin { } .download-button:hover { - color: orange; + color: #f39200; } .match-result a { @@ -856,3 +857,12 @@ h-margin { .group-stage-link:hover { color: #f39200; /* Or whatever hover color you prefer */ } + +.tournament-info a { + color: #f39200; + text-decoration: underline; + font-weight: bold; +} +.tournament-info a:hover { + color: #f39200; +} diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_match.html b/tournaments/templates/tournaments/broadcast/broadcasted_match.html index ef51dd8..9e3d0cc 100644 --- a/tournaments/templates/tournaments/broadcast/broadcasted_match.html +++ b/tournaments/templates/tournaments/broadcast/broadcasted_match.html @@ -2,9 +2,13 @@
- +