add the ability to register animation

sync_v2
Raz 7 months ago
parent 0470d80379
commit 260692da75
  1. 35
      tournaments/migrations/0117_playerregistration_user_teamregistration_user_and_more.py
  2. 38
      tournaments/models/enums.py
  3. 3
      tournaments/models/player_registration.py
  4. 3
      tournaments/models/team_registration.py
  5. 89
      tournaments/models/tournament.py
  6. 3
      tournaments/models/unregistered_player.py
  7. 3
      tournaments/models/unregistered_team.py
  8. 158
      tournaments/services/email_service.py
  9. 19
      tournaments/services/tournament_registration.py
  10. 25
      tournaments/services/tournament_unregistration.py
  11. 2
      tournaments/templates/tournaments/tournament_info.html
  12. 5
      tournaments/templates/tournaments/tournament_row.html
  13. 120
      tournaments/views.py

@ -0,0 +1,35 @@
# Generated by Django 5.1 on 2025-04-25 07:48
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0116_customuser_disable_ranking_federal_ruling_and_more'),
]
operations = [
migrations.AddField(
model_name='playerregistration',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='player_registrations', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='teamregistration',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team_registrations', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='unregisteredplayer',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unregistered_players', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='unregisteredteam',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unregistered_teams', to=settings.AUTH_USER_MODEL),
),
]

@ -49,6 +49,44 @@ class FederalLevelCategory(models.IntegerChoices):
P2000 = 2000, 'P2000'
CHPT = 1, 'Championnat'
def localized_word(self):
if self == FederalLevelCategory.UNLISTED:
return "animation"
elif self == FederalLevelCategory.CHPT:
return "championnat"
else:
return "tournoi"
def is_feminine_word(self):
if self == FederalLevelCategory.UNLISTED:
return True
else:
return False
def localized_prefix_at(self):
if self == FederalLevelCategory.UNLISTED:
return "à l'"
else:
return "au "
def localized_prefix_of(self):
if self == FederalLevelCategory.UNLISTED:
return "de l'"
else:
return "du "
def localized_prefix_this(self):
if self == FederalLevelCategory.UNLISTED:
return "cette"
else:
return "ce "
def localized_prefix_that(self):
if self == FederalLevelCategory.UNLISTED:
return "l'"
else:
return "le "
@staticmethod
def min_player_rank(level=None, category=None, age_category=None) -> int:
if level == FederalLevelCategory.P25:

@ -1,5 +1,5 @@
from django.db import models
from . import SideStoreModel, TeamRegistration, PlayerSexType, PlayerDataSource, PlayerPaymentType, OnlineRegistrationStatus
from . import SideStoreModel, TeamRegistration, PlayerSexType, PlayerDataSource, PlayerPaymentType, OnlineRegistrationStatus, CustomUser
from .enums import RegistrationStatus
import uuid
from django.utils import timezone
@ -7,6 +7,7 @@ from django.utils import timezone
class PlayerRegistration(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
team_registration = models.ForeignKey(TeamRegistration, on_delete=models.SET_NULL, related_name='player_registrations', null=True)
user = models.ForeignKey(CustomUser, null=True, blank=True, on_delete=models.SET_NULL, related_name='player_registrations')
first_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
licence_id = models.CharField(max_length=50, null=True, blank=True)

@ -1,5 +1,5 @@
from django.db import models
from . import SideStoreModel, Tournament, GroupStage, Match
from . import SideStoreModel, Tournament, GroupStage, Match, CustomUser
import uuid
from django.utils import timezone
from .enums import RegistrationStatus
@ -10,6 +10,7 @@ class TeamRegistration(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
tournament = models.ForeignKey(Tournament, on_delete=models.SET_NULL, related_name='team_registrations', null=True)
group_stage = models.ForeignKey(GroupStage, null=True, blank=True, on_delete=models.SET_NULL, related_name='team_registrations')
user = models.ForeignKey(CustomUser, null=True, blank=True, on_delete=models.SET_NULL, related_name='team_registrations')
registration_date = models.DateTimeField(null=True, blank=True)
call_date = models.DateTimeField(null=True, blank=True)
bracket_position = models.IntegerField(null=True, blank=True)

@ -131,16 +131,12 @@ class Tournament(BaseModel):
def display_name(self):
if self.name:
if self.federal_level_category == FederalLevelCategory.UNLISTED:
return self.name
return self.base_name() + " " + self.name
return self.short_base_name() + " " + self.name
else:
return self.base_name()
def broadcast_display_name(self):
if self.name:
if self.federal_level_category == FederalLevelCategory.UNLISTED:
return self.name
return self.short_base_name() + " " + self.name
else:
return self.base_name()
@ -154,12 +150,17 @@ class Tournament(BaseModel):
def base_name(self):
return f"{self.level()} {self.category()}"
def full_name(self):
if self.name:
return f"{self.level()} {self.name} {self.category()} {self.age()}"
return f"{self.level()} {self.category()} {self.age()}"
def short_base_name(self):
category = self.category()
if len(category) > 0:
return f"{self.level()}{category[0]}"
if len(category) > 0 and self.federal_level_category > 1:
return f"{self.short_level()}{category[0]}"
else:
return self.level()
return self.short_level()
def filter_name(self):
components = [self.formatted_start_date(), self.short_base_name()]
@ -185,6 +186,9 @@ class Tournament(BaseModel):
return formats.date_format(self.local_start_date(), format='l j F Y H:i').capitalize()
def level(self):
return self.get_federal_level_category_display()
def short_level(self):
if self.federal_level_category == 0:
return "Anim."
if self.federal_level_category == 1:
@ -192,6 +196,11 @@ class Tournament(BaseModel):
return self.get_federal_level_category_display()
def category(self):
if self.federal_age_category > 100 and self.federal_age_category < 200:
if self.federal_category == 0:
return "Garçon"
if self.federal_category == 1:
return "Fille"
return self.get_federal_category_display()
def age(self):
@ -1250,24 +1259,25 @@ class Tournament(BaseModel):
# In waiting list with no limit
return current_team_count - self.team_count
def build_tournament_type_array(self):
def build_tournament_type_str(self):
tournament_details = []
if self.federal_level_category > 0:
tournament_details.append(self.level())
if self.category():
tournament_details.append(self.category())
if self.age() and self.federal_age_category != FederalAgeCategory.SENIOR:
tournament_details.append(self.age())
return tournament_details
def build_tournament_type_str(self):
tournament_details = self.build_tournament_type_array()
return " ".join(filter(None, tournament_details))
def build_tournament_details_str(self):
tournament_details = self.build_tournament_type_array()
name_str = self.build_name_details_str()
tournament_details = []
if self.federal_level_category > 0:
tournament_details.append(self.level())
if self.category():
tournament_details.append(self.category())
if self.age() and self.federal_age_category != FederalAgeCategory.SENIOR:
tournament_details.append(self.age())
if len(name_str) > 0:
tournament_details.append(name_str)
@ -1313,13 +1323,13 @@ class Tournament(BaseModel):
# Check age category restrictions
if self.federal_age_category == FederalAgeCategory.A11_12 and user_age > 12:
reasons.append("Ce tournoi est réservé aux -12 ans")
reasons.append("Ce tournoi est réservé aux 12 ans et moins")
if self.federal_age_category == FederalAgeCategory.A13_14 and user_age > 14:
reasons.append("Ce tournoi est réservé aux -14 ans")
reasons.append("Ce tournoi est réservé aux 14 ans et moins")
if self.federal_age_category == FederalAgeCategory.A15_16 and user_age > 16:
reasons.append("Ce tournoi est réservé aux -16 ans")
reasons.append("Ce tournoi est réservé aux 16 ans et moins")
if self.federal_age_category == FederalAgeCategory.A17_18 and user_age > 18:
reasons.append("Ce tournoi est réservé aux -18 ans")
reasons.append("Ce tournoi est réservé aux 18 ans et moins")
if self.federal_age_category == FederalAgeCategory.SENIOR and user_age < 11:
reasons.append("Ce tournoi est réservé aux 11 ans et plus")
if self.federal_age_category == FederalAgeCategory.A45 and user_age < 45:
@ -1728,26 +1738,29 @@ class Tournament(BaseModel):
return "journée"
def get_player_registration_status_by_licence(self, user):
licence_id = user.licence_id
if not licence_id:
user_player = self.get_user_registered(user)
if user_player:
return user_player.get_registration_status()
return None
def get_user_registered(self, user):
if not user.is_authenticated:
return None
validator = LicenseValidator(licence_id)
if validator.validate_license():
stripped_license = validator.stripped_license
# Check if there is a PlayerRegistration for this user in this tournament
PlayerRegistration = apps.get_model('tournaments', 'PlayerRegistration')
user_player = PlayerRegistration.objects.filter(
licence_id__icontains=stripped_license,
# First, try to find a registration directly linked to the user
direct_registration = PlayerRegistration.objects.filter(
team_registration__tournament=self,
user=user,
team_registration__walk_out=False
).first()
if user_player:
return user_player.get_registration_status()
return None
if direct_registration:
return direct_registration
def get_user_registered(self, user):
if not user.is_authenticated or not user.licence_id:
# If no direct registration found and user has no license, return None
if not user.licence_id:
return None
# Validate the license format
@ -1757,10 +1770,8 @@ class Tournament(BaseModel):
# Get the stripped license (without check letter)
stripped_license = validator.stripped_license
PlayerRegistration = apps.get_model('tournaments', 'PlayerRegistration')
# Check if there's a player registration with this license in the tournament
# that hasn't walked out
# Fall back to checking by license ID
return PlayerRegistration.objects.filter(
team_registration__tournament=self,
licence_id__icontains=stripped_license,
@ -1809,6 +1820,14 @@ class Tournament(BaseModel):
else:
return 0
def is_free(self):
if self.entry_fee is not None and self.entry_fee == 0:
return True
elif self.entry_fee is None:
return True
else:
return False
def effective_commission_rate(self):
"""Get the commission rate for this tournament, falling back to the umpire default if not set"""
return 1.00 # Fallback default

@ -1,11 +1,12 @@
from django.db import models
from . import UnregisteredTeam
from . import UnregisteredTeam, CustomUser
from .player_enums import PlayerPaymentType
import uuid
class UnregisteredPlayer(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
unregistered_team = models.ForeignKey(UnregisteredTeam, on_delete=models.SET_NULL, related_name='unregistered_players', null=True, blank=True)
user = models.ForeignKey(CustomUser, null=True, blank=True, on_delete=models.SET_NULL, related_name='unregistered_players')
first_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
licence_id = models.CharField(max_length=50, null=True, blank=True)

@ -1,9 +1,10 @@
from django.db import models
from . import Tournament
from . import Tournament, CustomUser
import uuid
class UnregisteredTeam(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
user = models.ForeignKey(CustomUser, null=True, blank=True, on_delete=models.SET_NULL, related_name='unregistered_teams')
tournament = models.ForeignKey(Tournament, on_delete=models.SET_NULL, related_name='unregistered_teams', null=True, blank=True)
unregistration_date = models.DateTimeField(null=True, blank=True)

@ -1,6 +1,6 @@
from django.core.mail import EmailMessage
from enum import Enum
from ..models.enums import RegistrationStatus
from ..models.enums import RegistrationStatus, FederalLevelCategory
from ..models.tournament import TeamSortingType
from django.utils import timezone
@ -18,7 +18,7 @@ class TeamEmailType(Enum):
UNEXPECTED_OUT_OF_TOURNAMENT = 'unexpected_out_of_tournament'
REQUIRES_TIME_CONFIRMATION = 'requires_time_confirmation'
def email_subject(self, time_to_confirm=None) -> str:
def email_topic(self, category=None, time_to_confirm=None) -> str:
confirmation_types = [
self.REGISTERED,
self.OUT_OF_WAITING_LIST,
@ -26,6 +26,12 @@ class TeamEmailType(Enum):
self.OUT_OF_WALKOUT_IS_IN,
self.REQUIRES_TIME_CONFIRMATION,
]
word = "Tournoi"
grammar = ''
if category is not None:
federal_category = FederalLevelCategory(category)
word = federal_category.localized_word().capitalize()
grammar = 'e' if federal_category.is_feminine_word() else ''
if time_to_confirm and self in confirmation_types:
return "Participation en attente de confirmation"
@ -36,7 +42,7 @@ class TeamEmailType(Enum):
self.UNREGISTERED: "Désistement",
self.OUT_OF_WAITING_LIST: "Participation confirmée",
self.REQUIRES_TIME_CONFIRMATION: "Participation en attente de confirmation",
self.TOURNAMENT_CANCELED: "Tournoi annulé",
self.TOURNAMENT_CANCELED: f"{word} annulé{grammar}",
self.IN_TOURNAMENT_STRUCTURE: "Participation confirmée",
self.OUT_OF_TOURNAMENT_STRUCTURE: "Participation annulée",
self.OUT_OF_WALKOUT_IS_IN: "Participation confirmée",
@ -81,34 +87,39 @@ class TournamentEmailService:
@staticmethod
def _build_registration_email_body(tournament, captain, tournament_details_str, other_player, waiting_list):
inscription_date = captain.team_registration.local_registration_date().strftime("%d/%m/%Y à %H:%M")
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
tournament_prefix_of = federal_level_category.localized_prefix_of()
tournament_prefix_that = federal_level_category.localized_prefix_that()
body_parts = []
body_parts.append("Bonjour,\n")
if waiting_list:
body_parts.append(f"Votre inscription en liste d'attente du tournoi {tournament_details_str} est confirmée.")
body_parts.append(f"Votre inscription en liste d'attente {tournament_prefix_of}{tournament_word} {tournament_details_str} prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} est confirmée.")
else:
body_parts.append(f"Votre inscription au tournoi {tournament_details_str} est confirmée.")
body_parts.append(f"Votre inscription {tournament_prefix_at}{tournament_word} {tournament_details_str} prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} est confirmée.")
if tournament.team_sorting == TeamSortingType.RANK:
cloture_date = tournament.local_registration_federal_limit().strftime("%d/%m/%Y à %H:%M")
cloture_date = tournament.local_registration_federal_limit()
loc = ""
if cloture_date is not None:
loc = f", prévu le {cloture_date}"
loc = f", prévu le {cloture_date.strftime("%d/%m/%Y à %H:%M")}"
body_parts.append(f"Attention, la sélection définitive se fera par poids d'équipe à la clôture des inscriptions{loc}.")
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "informations sur le tournoi"
link_text = f"informations sur {tournament_prefix_that}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.extend([
f"\nDate d'inscription: {inscription_date}",
f"\nÉquipe inscrite: {captain.name()} et {other_player.name()}",
f"\nLe tournoi commencera le {tournament.formatted_start_date()} au club {tournament.event.club.name}",
f"\nVoir les {absolute_url}"
])
# Add payment information if applicable
if tournament.should_request_payment:
if tournament.should_request_payment():
payment_info = TournamentEmailService._build_payment_info(tournament, captain.team_registration)
body_parts.append(payment_info)
@ -123,9 +134,14 @@ class TournamentEmailService:
@staticmethod
def _build_unregistration_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
tournament_prefix_that = federal_level_category.localized_prefix_that()
body_parts = [
"Bonjour,\n\n",
f"Votre inscription au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été annulée"
f"Votre inscription {tournament_prefix_at}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été annulée"
]
if other_player is not None:
@ -134,7 +150,7 @@ class TournamentEmailService:
)
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "informations sur le tournoi"
link_text = f"informations sur {tournament_prefix_that}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.append(
@ -152,13 +168,17 @@ class TournamentEmailService:
@staticmethod
def _build_out_of_waiting_list_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
body_parts = [
"Bonjour,\n\n",
f"Suite au désistement d'une paire, vous êtes maintenant inscrit au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
f"Suite au désistement d'une paire, vous êtes maintenant inscrit {tournament_prefix_at}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
]
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "accéder au tournoi"
link_text = f"accéder {tournament_prefix_at}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
if other_player is not None:
@ -178,13 +198,17 @@ class TournamentEmailService:
@staticmethod
def _build_requires_confirmation_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
body_parts = [
"Bonjour,\n\n",
f"Vous n'avez toujours pas confirmé votre participation au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
f"Vous n'avez toujours pas confirmé votre participation {tournament_prefix_at}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
]
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "accéder au tournoi"
link_text = f"accéder {tournament_prefix_at}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
if other_player is not None:
@ -204,9 +228,13 @@ class TournamentEmailService:
@staticmethod
def _build_tournament_cancellation_email_body(tournament, player, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_that = federal_level_category.localized_prefix_that()
body_parts = [
"Bonjour,\n\n",
f"Le tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été annulé par le juge-arbitre."
f"{tournament_prefix_that.capitalize()}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été annulé par le juge-arbitre."
]
if other_player is not None:
@ -224,13 +252,18 @@ class TournamentEmailService:
@staticmethod
def _build_in_tournament_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
tournament_prefix_of = federal_level_category.localized_prefix_of()
body_parts = [
"Bonjour,\n\n",
f"Suite à une modification de la taille du tournoi, vous pouvez participer au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
f"Suite à une modification de la taille {tournament_prefix_of}{tournament_word}, vous pouvez participer {tournament_prefix_at}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
]
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "accéder au tournoi"
link_text = f"accéder {tournament_prefix_at}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
if other_player is not None:
@ -250,13 +283,19 @@ class TournamentEmailService:
@staticmethod
def _build_out_of_tournament_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
tournament_prefix_of = federal_level_category.localized_prefix_of()
tournament_prefix_that = federal_level_category.localized_prefix_that()
body_parts = [
"Bonjour,\n\n",
f"Suite à une modification de la taille du tournoi, vous avez été placé en liste d'attente. Votre participation au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} n'est plus confirmée."
f"Suite à une modification de la taille {tournament_prefix_of}{tournament_word}, vous avez été placé en liste d'attente. Votre participation {tournament_prefix_at}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} n'est plus confirmée."
]
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "informations sur le tournoi"
link_text = f"informations sur {tournament_prefix_that}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.append(f"\n\nVoir les {absolute_url}")
@ -276,13 +315,17 @@ class TournamentEmailService:
@staticmethod
def _build_walk_out_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
body_parts = [
"Bonjour,\n\n",
f"Le juge-arbitre a annulé votre participation au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
f"Le juge-arbitre a annulé votre participation {tournament_prefix_at}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
]
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "accéder au tournoi"
link_text = f"accéder {tournament_prefix_at}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.append(f"\n\nVoir les {absolute_url}")
@ -303,13 +346,18 @@ class TournamentEmailService:
@staticmethod
def _build_out_of_walkout_is_in_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
tournament_prefix_that = federal_level_category.localized_prefix_that()
body_parts = [
"Bonjour,\n\n",
f"Le juge-arbitre vous a ré-intégré au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
f"Le juge-arbitre vous a ré-intégré {tournament_prefix_at}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}"
]
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "informations sur le tournoi"
link_text = f"informations sur {tournament_prefix_that}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.append(f"\n\nVoir les {absolute_url}")
@ -332,13 +380,18 @@ class TournamentEmailService:
@staticmethod
def _build_unexpected_out_of_tournament_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
tournament_prefix_that = federal_level_category.localized_prefix_that()
body_parts = [
"Bonjour,\n\n",
f"En raison d'une décision du juge-arbitre, vous avez été placé en liste d'attente. Votre participation au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} n'est plus confirmée."
f"En raison d'une décision du juge-arbitre, vous avez été placé en liste d'attente. Votre participation {tournament_prefix_at}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} n'est plus confirmée."
]
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "informations sur le tournoi"
link_text = f"informations sur {tournament_prefix_that}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.append(f"\n\nVoir les {absolute_url}")
@ -358,17 +411,22 @@ class TournamentEmailService:
@staticmethod
def _build_out_of_walkout_waiting_list_email_body(tournament, captain, tournament_details_str, other_player):
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_of = federal_level_category.localized_prefix_of()
tournament_prefix_that = federal_level_category.localized_prefix_that()
body_parts = [
"Bonjour,\n\n",
]
if captain.registration_status == RegistrationStatus.CANCELED:
body_parts.append("Le temps accordé pour confirmer votre inscription s'est écoulé.")
body_parts.append(f"Vous avez été replacé en liste d'attente du tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}")
body_parts.append(f"Vous avez été replacé en liste d'attente {tournament_prefix_of}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}")
else:
body_parts.append(f"Le juge-arbitre vous a placé en liste d'attente du tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}")
body_parts.append(f"Le juge-arbitre vous a placé en liste d'attente {tournament_prefix_of}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}")
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "informations sur le tournoi"
link_text = f"informations sur {tournament_prefix_that}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.append(f"\n\nVoir les {absolute_url}")
@ -424,20 +482,25 @@ class TournamentEmailService:
account_info = "\nVous devez avoir un compte Padel Club."
url_info = f"\n{absolute_url}"
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_at = federal_level_category.localized_prefix_at()
tournament_prefix_this = federal_level_category.localized_prefix_this()
# Base message varies based on whether confirmation is needed
if time_to_confirm is not None:
# Format the deadline time with proper timezone
deadline_str = time_to_confirm.astimezone(tournament.timezone()).strftime("%d/%m/%Y à %H:%M (%Z)")
# Confirmation required message
action_text = f"Pour confirmer votre participation au tournoi, cliquez sur ce lien pour {url_info} ou contactez rapidement le juge-arbitre."
action_text = f"Pour confirmer votre participation {tournament_prefix_at}{tournament_word}, cliquez sur ce lien pour {url_info} ou contactez rapidement le juge-arbitre."
warning_text = f" ATTENTION : Vous avez jusqu'au {deadline_str} pour confirmer votre participation. Passé ce délai, votre place sera automatiquement proposée à l'équipe suivante sur liste d'attente.\n\n"
elif captain.registration_status == RegistrationStatus.PENDING:
action_text = f"Pour confirmer votre participation au tournoi, cliquez sur ce lien pour {url_info} ou contactez rapidement le juge-arbitre."
warning_text = " ATTENTION : Actuellement, il n'y a pas de liste d'attente pour ce tournoi. Dès qu'une liste d'attente se formera, vous recevrez un email avec un délai précis pour confirmer votre participation.\n\n"
action_text = f"Pour confirmer votre participation {tournament_prefix_at}{tournament_word}, cliquez sur ce lien pour {url_info} ou contactez rapidement le juge-arbitre."
warning_text = f" ATTENTION : Actuellement, il n'y a pas de liste d'attente pour {tournament_prefix_this}{tournament_word}. Dès qu'une liste d'attente se formera, vous recevrez un email avec un délai précis pour confirmer votre participation.\n\n"
else:
# Standard message for teams already confirmed
action_text = f"Si vous n'êtes plus disponible pour participer à ce tournoi, cliquez sur ce lien pour {url_info} ou contactez rapidement le juge-arbitre."
action_text = f"Si vous n'êtes plus disponible pour participer à {tournament_prefix_this}{tournament_word}, cliquez sur ce lien pour {url_info} ou contactez rapidement le juge-arbitre."
warning_text = ""
# Construct the complete message
@ -458,7 +521,7 @@ class TournamentEmailService:
if email_body is None:
return
topic = message_type.email_subject(captain.time_to_confirm)
topic = message_type.email_topic(tournament.federal_level_category, captain.time_to_confirm)
email_subject = TournamentEmailService.email_subject(tournament, topic)
TournamentEmailService._send_email(captain.email, email_subject, email_body)
@ -536,6 +599,7 @@ class TournamentEmailService:
print("TournamentEmailService.notify_team 2p", team)
first_player, second_player = players
TournamentEmailService.notify(first_player, second_player, tournament, message_type)
if first_player.email != second_player.email:
TournamentEmailService.notify(second_player, first_player, tournament, message_type)
elif len(players) == 1:
print("TournamentEmailService.notify_team 1p", team)
@ -547,7 +611,10 @@ class TournamentEmailService:
"""
Build payment information section for emails
"""
if not tournament.should_request_payment:
if not tournament.should_request_payment():
return ""
if tournament.is_free():
return ""
# Check payment status
@ -591,6 +658,10 @@ class TournamentEmailService:
if payment_amount is None:
payment_amount = tournament.team_fee()
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_that = federal_level_category.localized_prefix_that()
for player in player_registrations:
if not player.email or not player.registered_online:
continue
@ -600,7 +671,7 @@ class TournamentEmailService:
body_parts = [
"Bonjour,\n\n",
f"Votre paiement pour le tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été reçu avec succès."
f"Votre paiement pour {tournament_prefix_that}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été reçu avec succès."
]
# Add information about the other player if available
@ -620,7 +691,7 @@ class TournamentEmailService:
)
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "informations sur le tournoi"
link_text = f"informations sur {tournament_prefix_that}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.append(f"\n\nVoir les {absolute_url}")
@ -662,16 +733,23 @@ class TournamentEmailService:
if refund_amount is None:
refund_amount = tournament.team_fee()
federal_level_category = FederalLevelCategory(tournament.federal_level_category)
tournament_word = federal_level_category.localized_word()
tournament_prefix_that = federal_level_category.localized_prefix_that()
processed_emails = set()
for player in player_registrations:
if not player.email or not player.registered_online:
continue
if player.email in processed_emails:
continue
processed_emails.add(player.email)
tournament_details_str = tournament.build_tournament_details_str()
other_player = team_registration.get_other_player(player) if len(player_registrations) > 1 else None
body_parts = [
"Bonjour,\n\n",
f"Votre remboursement pour le tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été traité avec succès."
f"Votre remboursement pour {tournament_prefix_that}{tournament_word} {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été traité avec succès."
]
# Add information about the other player if available
@ -691,7 +769,7 @@ class TournamentEmailService:
)
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info"
link_text = "informations sur le tournoi"
link_text = f"informations sur {tournament_prefix_that}{tournament_word}"
absolute_url = f'<a href="{absolute_url}">{link_text}</a>'
body_parts.append(f"\n\nVoir les {absolute_url}")

@ -208,7 +208,6 @@ class RegistrationCartManager:
elif not tournament.license_is_required:
# License not required, check if name is provided
if not first_name or not last_name:
self.first_tournament = True
return False, "Le prénom et le nom sont obligatoires."
else:
# License is required but not provided
@ -335,6 +334,11 @@ class RegistrationCartManager:
return False, f"Vous avez besoin d'au moins {tournament.minimum_player_per_team} joueurs pour vous inscrire."
# Identify captain from user's license
# # Update user phone if provided
if self.request.user.is_authenticated and mobile_number:
self.request.user.phone = mobile_number
self.request.user.save(update_fields=['phone'])
stripped_license = None
if self.request.user.is_authenticated and self.request.user.licence_id:
validator = LicenseValidator(self.request.user.licence_id)
@ -348,6 +352,7 @@ class RegistrationCartManager:
registration_date=timezone.now(),
walk_out=False,
weight=weight,
user=self.request.user
)
for player_data in players: # Compute rank and sex using the original logic
@ -364,17 +369,20 @@ class RegistrationCartManager:
data_source = PlayerDataSource.FRENCH_FEDERATION # Now using the enum value
User = get_user_model()
matching_user = None
if player_licence_id:
matching_user = self.request.user
if player_licence_id and (stripped_license is None or is_captain is False):
try:
# Using icontains for case-insensitive match
matching_user = User.objects.get(licence_id__icontains=player_licence_id)
if matching_user is None:
matching_user = self.request.user
except User.DoesNotExist:
pass
# Create player registration with all the original fields
PlayerRegistration.objects.create(
team_registration=team_registration,
user=matching_user,
captain=is_captain,
source=data_source,
registered_online=True,
@ -395,11 +403,6 @@ class RegistrationCartManager:
registration_status=RegistrationStatus.CONFIRMED if self.session.get('waiting_list_position', 0) < 0 else RegistrationStatus.WAITING
)
# Update user phone if provided
if self.request.user.is_authenticated and mobile_number:
self.request.user.phone = mobile_number
self.request.user.save(update_fields=['phone'])
# Clear the cart
self.clear_cart()
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)

@ -18,10 +18,10 @@ class TournamentUnregistrationService:
messages.error(self.request, "Le désistement n'est plus possible pour ce tournoi. Si vous souhaitez vous désinscrire, veuillez contacter le juge-arbitre.")
return False
if not self.request.user.licence_id:
messages.error(self.request,
"Vous ne pouvez pas vous désinscrire car vous n'avez pas de numéro de licence associé.")
return False
# if not self.request.user.licence_id:
# messages.error(self.request,
# "Vous ne pouvez pas vous désinscrire car vous n'avez pas de numéro de licence associé.")
# return False
return True
@ -80,12 +80,14 @@ class TournamentUnregistrationService:
unregistered_team = UnregisteredTeam.objects.create(
tournament=team_registration.tournament,
user=team_registration.user,
unregistration_date=timezone.now(),
)
for player in team_registration.players_sorted_by_rank:
UnregisteredPlayer.objects.create(
unregistered_team=unregistered_team,
user=player.user,
first_name=player.first_name,
last_name=player.last_name,
licence_id=player.licence_id,
@ -95,18 +97,9 @@ class TournamentUnregistrationService:
)
def _find_player_registration(self):
if not self.request.user.licence_id:
return False
validator = LicenseValidator(self.request.user.licence_id)
is_license_valid = validator.validate_license()
if not is_license_valid:
return False
self.player_registration = PlayerRegistration.objects.filter(
licence_id__icontains=validator.stripped_license,
team_registration__tournament_id=self.tournament.id,
).first()
# First check if we can find the player registration directly by user
if self.request.user.is_authenticated:
self.player_registration = self.tournament.get_user_registered(self.request.user)
if self.player_registration:
self.team_registration = self.player_registration.team_registration

@ -148,7 +148,7 @@
</div>
{% endif %}
<div class="cell medium-12 large-6 padding10">
<h1 class="club padding10">{{ tournament.display_name }} {{ tournament.get_federal_age_category_display}}</h1>
<h1 class="club padding10">{{ tournament.full_name }}</h1>
<div class="bubble">
<div class="semibold">{{ tournament.local_start_date_formatted }}</div>
<div>{{ tournament.day_duration_formatted }}</div>

@ -8,7 +8,7 @@
<div class="small">{{ tournament.month }}</div>
</div>
<div class="table-row-element tournament-type">
<div class="very-large">{{ tournament.level }}</div>
<div class="very-large">{{ tournament.short_level }}</div>
{% if tournament.category %}
<div class="small">{{ tournament.category }}</div>
{% endif %}
@ -21,6 +21,9 @@
{% if tournament.event.name %}
<div class="small event-name">{{ tournament.event.name }}</div>
{% endif %}
{% if tournament.name %}
<div class="small event-name">{{ tournament.name }}</div>
{% endif %}
<div class="small">{{ tournament.localized_day_duration }}</div>
</div>
<div class="table-row-element tournament-status center">

@ -172,6 +172,7 @@ def tournament_info(request, tournament_id):
team_registration = None
is_captain = False
player_register_check = None
if request.method == 'POST':
storage = messages.get_messages(request)
for _ in storage:
pass
@ -179,21 +180,11 @@ def tournament_info(request, tournament_id):
if len(storage._loaded_messages) == 1:
del storage._loaded_messages[0]
if request.user.is_authenticated:
# Assuming user's licence_id is stored in the user profile (e.g., request.user.licence_id)
user_licence_id = request.user.licence_id
player_register_check = tournament.player_register_check(user_licence_id)
if user_licence_id is not None and player_register_check is None:
validator = LicenseValidator(user_licence_id)
stripped_license = validator.stripped_license
# Check if there is a PlayerRegistration for this user in this tournament
registered_user = PlayerRegistration.objects.filter(
licence_id__icontains=stripped_license,
team_registration__tournament=tournament,
team_registration__walk_out=False,
).first()
registered_user = tournament.get_user_registered(request.user)
# If the user is registered, retrieve their team registration
if registered_user:
is_captain = registered_user.captain
@ -774,63 +765,21 @@ def custom_password_change(request):
@login_required
def my_tournaments(request):
user = request.user
user_licence_id = request.user.licence_id
# If no licence_id, return empty lists
if user_licence_id is None:
return render(request, 'registration/my_tournaments.html', {
'upcoming_tournaments': [],
'running_tournaments': [],
'ended_tournaments': [],
'user_name': user.username
})
# Get all tournaments for the user based on their licence
validator = LicenseValidator(user_licence_id)
stripped_license = validator.stripped_license
def filter_user_tournaments(tournaments):
return [t for t in tournaments if t.team_registrations.filter(
player_registrations__licence_id__icontains=stripped_license,
walk_out=False
).exists()]
# Get filtered tournaments using the helper function
upcoming_tournaments = filter_user_tournaments(future_tournaments(None))
running_tournaments = filter_user_tournaments(live_tournaments(None))
ended_tournaments = filter_user_tournaments([t for t in finished_tournaments(None) if not t.is_canceled()])
upcoming_tournaments = get_user_tournaments(request.user, future_tournaments(None))
running_tournaments = get_user_tournaments(request.user, live_tournaments(None))
ended_tournaments = get_user_tournaments(request.user, [t for t in finished_tournaments(None) if not t.is_canceled()])
return render(request, 'registration/my_tournaments.html', {
'upcoming_tournaments': upcoming_tournaments,
'running_tournaments': running_tournaments,
'ended_tournaments': ended_tournaments[:12],
'user_name': user.username
'user_name': request.user.username
})
@login_required
def all_my_ended_tournaments(request):
user_licence_id = request.user.licence_id
# If no licence_id, return empty lists
if user_licence_id is None:
return render(request, '/tournaments.html', {
'tournaments': [],
'title': "Palmarès",
})
# Get all tournaments for the user based on their licence
validator = LicenseValidator(user_licence_id)
stripped_license = validator.stripped_license
def filter_user_tournaments(tournaments):
return [t for t in tournaments if t.team_registrations.filter(
player_registrations__licence_id__icontains=stripped_license,
walk_out=False
).exists()]
ended_tournaments = filter_user_tournaments(finished_tournaments(None))
ended_tournaments = get_user_tournaments(request.user, finished_tournaments(None))
return render(request,
"tournaments/tournaments_list.html",
{
@ -1072,23 +1021,11 @@ def custom_logout(request):
def confirm_tournament_registration(request, tournament_id):
if request.method == 'POST':
tournament = get_object_or_404(Tournament, pk=tournament_id)
# Find the team registration for this user
user_licence_id = request.user.licence_id
if user_licence_id is not None:
validator = LicenseValidator(user_licence_id)
stripped_license = validator.stripped_license
# Check if there is a PlayerRegistration for this user in this tournament
team_registrations = TeamRegistration.objects.filter(
tournament=tournament,
player_registrations__licence_id__icontains=stripped_license,
)
if not team_registrations.exists():
team_registration = tournament.get_user_team_registration(request.user)
if team_registration is None:
messages.error(request, "Aucune inscription trouvée pour ce tournoi.")
return redirect('tournament-info', tournament_id=tournament_id)
team_registration = team_registrations.first()
if team_registration.is_confirmation_expired():
team_registration.check_confirmation_deadline()
messages.error(request, "Le délai de confirmation est expiré. Vous ne pouvez plus confirmer votre participation.")
@ -1563,6 +1500,45 @@ def xls_to_csv(request):
else:
return HttpResponse("No file was uploaded", status=400)
def get_user_tournaments(user, tournaments):
user_tournaments = []
if user.is_authenticated is False:
return user_tournaments
user_licence_id = user.licence_id
stripped_license = None
# If no licence_id, return empty lists
if user_licence_id is not None:
# Get all tournaments for the user based on their licence
validator = LicenseValidator(user_licence_id)
stripped_license = validator.stripped_license
for t in tournaments:
# First check for direct user relationship
direct_registration_exists = t.team_registrations.filter(
player_registrations__user=user,
walk_out=False
).exists()
# If direct relationship exists, add the tournament
if direct_registration_exists:
user_tournaments.append(t)
continue
# Otherwise, check by license
license_registration_exists = None
if stripped_license:
license_registration_exists = t.team_registrations.filter(
player_registrations__licence_id__icontains=stripped_license,
walk_out=False
).exists()
if license_registration_exists:
user_tournaments.append(t)
return user_tournaments
class UserListExportView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
users = CustomUser.objects.order_by('date_joined')

Loading…
Cancel
Save