sync
Laurent 9 months ago
commit bfbd7caaa3
  1. 14
      api/utils.py
  2. 15
      api/views.py
  3. 19
      shared/discord.py
  4. 70
      tournaments/models/tournament.py
  5. 3
      tournaments/services/tournament_registration.py
  6. 38
      tournaments/signals.py
  7. 26
      tournaments/templates/tournaments/teams.html
  8. 5
      tournaments/views.py

@ -3,3 +3,17 @@ import re
def is_valid_email(email): def is_valid_email(email):
email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
return re.match(email_regex, email) is not None 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

@ -24,10 +24,12 @@ from django.utils.decorators import method_decorator
from collections import defaultdict from collections import defaultdict
from .permissions import IsClubOwner 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 sync.models import Device
from shared.discord import send_discord_log_message
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class CustomAuthToken(APIView): class CustomAuthToken(APIView):
permission_classes = [] permission_classes = []
@ -135,6 +137,17 @@ class TournamentViewSet(SoftDeleteViewSet):
return [] return []
return self.queryset.filter(event__creator=self.request.user) 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): class PurchaseViewSet(SoftDeleteViewSet):
queryset = Purchase.objects.all() queryset = Purchase.objects.all()
serializer_class = PurchaseSerializer serializer_class = PurchaseSerializer

@ -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)}")

@ -337,6 +337,10 @@ class Tournament(BaseModel):
print("else", index, self.team_count) print("else", index, self.team_count)
return -1 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): def teams(self, include_waiting_list):
""" """
Get sorted list of teams for the tournament. 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) (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 # Categorize team
if team_reg.wild_card_bracket: if team_reg.wild_card_bracket:
wildcard_bracket.append(team) wildcard_bracket.append(team)
@ -381,13 +393,15 @@ class Tournament(BaseModel):
else: else:
waiting_teams.append(team) waiting_teams.append(team)
# Set initial stage
if team_reg.group_stage_position is not None: # Initialize group stage spots
team.set_stage("Poule") group_stage_spots = self.group_stage_spots()
elif team_reg.bracket_position is not None: bracket_seeds = self.team_count - group_stage_spots - len(wildcard_bracket)
team.set_stage("Tableau") group_stage_team_count = group_stage_spots - len(wildcard_group_stage)
else: if group_stage_team_count < 0:
team.set_stage("Attente") group_stage_team_count = 0
if bracket_seeds < 0:
bracket_seeds = 0
# Sort teams based on tournament rules # Sort teams based on tournament rules
if self.team_sorting == TeamSortingType.INSCRIPTION_DATE: 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)) 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)) waiting_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
# Determine final team list based on tournament settings wildcard_group_stage.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
if len(complete_teams) <= self.team_count: wildcard_bracket.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
all_teams = wildcard_bracket + wildcard_group_stage + complete_teams
if include_waiting_list:
all_teams.extend(waiting_teams)
return all_teams
# Split teams into main bracket and waiting list # Split teams into main bracket and waiting list
qualified_teams = complete_teams[:self.team_count] computed_team_count = self.team_count - len(wildcard_bracket) - len(wildcard_group_stage)
excess_teams = complete_teams[self.team_count:] 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 # Combine all waiting list teams
waiting_list = excess_teams + waiting_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 # Return final sorted list
if include_waiting_list: bracket_teams = qualified_teams[:bracket_seeds] + wildcard_bracket
return wildcard_bracket + wildcard_group_stage + qualified_teams + waiting_list gs_teams = qualified_teams[bracket_seeds:(bracket_seeds+group_stage_team_count)] + wildcard_group_stage
return wildcard_bracket + wildcard_group_stage + qualified_teams 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): def match_groups(self, broadcasted, group_stage_id, round_id):

@ -307,5 +307,6 @@ class TournamentRegistrationService:
def _license_already_registered(self, stripped_license): def _license_already_registered(self, stripped_license):
return PlayerRegistration.objects.filter( return PlayerRegistration.objects.filter(
team_registration__tournament=self.tournament, team_registration__tournament=self.tournament,
licence_id__startswith=stripped_license licence_id__startswith=stripped_license,
team_registration__walk_out=False
).exists() ).exists()

@ -12,6 +12,8 @@ from tournaments.services.email_service import TournamentEmailService
# Others # Others
from shared.discord import send_discord_log_message, send_discord_failed_calls_message
def generate_unique_code(): def generate_unique_code():
characters = string.ascii_lowercase + string.digits characters = string.ascii_lowercase + string.digits
while True: while True:
@ -25,19 +27,16 @@ def assign_unique_code(sender, instance, **kwargs):
instance.broadcast_code = generate_unique_code() instance.broadcast_code = generate_unique_code()
instance.save() 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) @receiver(post_save, sender=FailedApiCall)
def notify_discord_on_create(sender, instance, created, **kwargs): 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) @receiver(post_save, sender=Log)
def notify_log_creation_on_discord(sender, instance, created, **kwargs): 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 # 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: if created:
default_db_engine = settings.DATABASES['default']['ENGINE'] default_db_engine = settings.DATABASES['default']['ENGINE']
if default_db_engine != 'django.db.backends.sqlite3': 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()}' message = f'{site_name} > {instance.__class__.__name__} created: {instance.discord_string()}'
else: else:
message = "no message. Please configure 'discord_string' on your instance" message = "no message. Please configure 'discord_string' on your instance"
send_discord_message(webhook_url, message)
if isinstance(instance, FailedApiCall):
def send_discord_message(webhook_url, content): send_discord_failed_calls_message(message)
data = { else:
"content": content send_discord_log_message(message)
}
requests.post(webhook_url, json=data)
# if response.status_code != 204: # def send_discord_message(webhook_url, content):
# raise ValueError( # data = {
# f'Error sending message to Discord webhook: {response.status_code}, {response.text}' # "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) @receiver(pre_delete, sender=TeamRegistration)
def unregister_team(sender, instance, **kwargs): def unregister_team(sender, instance, **kwargs):

@ -12,30 +12,30 @@
{% load static %} {% load static %}
{% include 'tournaments/navigation_tournament.html' %} {% include 'tournaments/navigation_tournament.html' %}
{% if teams %}
<div class="grid-x padding-bottom"> <div class="grid-x padding-bottom">
<div class="cell medium-6 large-6 my-block"> <div class="cell medium-6 large-6 my-block">
{% if selected_teams|length > 0 %}
<label class="title topmargin20">Équipes sélectionnées : {{ selected_teams|length }}</label>
<div class="bubble"> <div class="bubble">
{% if tournament.registration_count_display %} {% for team in selected_teams %}
<label class="title">{{ tournament.registration_count_display }}</label> {% include 'tournaments/team_row.html' %}
{% endfor %}
</div>
{% endif %} {% endif %}
{% for team in teams %} {% if waiting_teams|length > 0 %}
<label class="title topmargin20">Équipes en attente : {{ waiting_teams|length }}</label>
<div class="bubble">
{% for team in waiting_teams %}
{% include 'tournaments/team_row.html' %} {% include 'tournaments/team_row.html' %}
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
{% endif %}
{% endblock %} {% endblock %}
{% endif %} {% endif %}

@ -270,10 +270,13 @@ def tournament_teams(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id) tournament = get_object_or_404(Tournament, pk=tournament_id)
teams = tournament.teams(True) teams = tournament.teams(True)
selected_teams = [team for team in teams if team.stage != 'Attente']
waiting_teams = [team for team in teams if team.stage == 'Attente']
return render(request, 'tournaments/teams.html', { return render(request, 'tournaments/teams.html', {
'tournament': tournament, 'tournament': tournament,
'teams': teams, 'selected_teams': selected_teams,
'waiting_teams': waiting_teams,
}) })
def tournament_summons(request, tournament_id): def tournament_summons(request, tournament_id):

Loading…
Cancel
Save