From c44a876f8e5eaf1cf34f49e10dd9804009898b85 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 29 Mar 2025 10:09:48 +0100 Subject: [PATCH 01/12] fix p500+ stuff --- .../0113_tournament_team_count_limit.py | 18 ++++ tournaments/models/tournament.py | 88 +++++++++++++++---- tournaments/services/email_service.py | 8 ++ tournaments/signals.py | 4 +- .../templates/register_tournament.html | 24 ++++- 5 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 tournaments/migrations/0113_tournament_team_count_limit.py diff --git a/tournaments/migrations/0113_tournament_team_count_limit.py b/tournaments/migrations/0113_tournament_team_count_limit.py new file mode 100644 index 0000000..2b113cb --- /dev/null +++ b/tournaments/migrations/0113_tournament_team_count_limit.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2025-03-29 08:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0112_tournament_disable_ranking_federal_ruling_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='tournament', + name='team_count_limit', + field=models.BooleanField(default=True), + ), + ] diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 24af6d5..42ab95b 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -67,6 +67,7 @@ class Tournament(BaseModel): initial_seed_round = models.IntegerField(default=0) initial_seed_count = models.IntegerField(default=0) enable_online_registration = models.BooleanField(default=False) # Equivalent to Bool = false + team_count_limit = models.BooleanField(default=True) registration_date_limit = models.DateTimeField(null=True, blank=True) # Equivalent to Date? = nil opening_registration_date = models.DateTimeField(null=True, blank=True) # Equivalent to Date? = nil waiting_list_limit = models.IntegerField(null=True, blank=True) # Equivalent to Int? = nil @@ -397,7 +398,7 @@ class Tournament(BaseModel): complete_teams.append(team) else: waiting_teams.append(team) - wildcard_bracket = [] + return complete_teams, wildcard_bracket, wildcard_group_stage, waiting_teams def sort_teams(self, include_waiting_list, complete_teams, wildcard_bracket, wildcard_group_stage, waiting_teams): @@ -1051,19 +1052,22 @@ class Tournament(BaseModel): def options_online_registration(self): options = [] + timezone = self.timezone() # Date d'ouverture if self.opening_registration_date: - date = formats.date_format(timezone.localtime(self.opening_registration_date), format='j F Y H:i') + date = formats.date_format(self.opening_registration_date.astimezone(timezone), format='j F Y H:i') options.append(f"Ouverture des inscriptions le {date}") # Date limite if self.registration_date_limit: - date = formats.date_format(timezone.localtime(self.registration_date_limit), format='j F Y H:i') + date = formats.date_format(self.registration_date_limit.astimezone(timezone), format='j F Y H:i') options.append(f"Clôture des inscriptions le {date}") + options.append(self.get_selection_status_localized) + # Cible d'équipes - if self.team_count: + if self.team_count_limit is True: options.append(f"Maximum {self.team_count} équipes") # Liste d'attente @@ -1122,11 +1126,17 @@ class Tournament(BaseModel): return False return True + def get_selection_status_localized(self): + if self.team_sorting == TeamSortingType.RANK: + return "La sélection se fait par le poids de l'équipe" + else: + return "La sélection se fait par date d'inscription" + def get_online_registration_status(self): if self.supposedly_in_progress(): return OnlineRegistrationStatus.ENDED if self.closed_registration_date is not None: - return OnlineRegistrationStatus.ENDED + return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE if self.end_date is not None: return OnlineRegistrationStatus.ENDED_WITH_RESULTS @@ -1140,9 +1150,12 @@ class Tournament(BaseModel): if self.registration_date_limit is not None: timezoned_datetime = timezone.localtime(self.registration_date_limit) if now > timezoned_datetime: - return OnlineRegistrationStatus.ENDED + return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE + + if self.team_sorting == TeamSortingType.RANK: + return OnlineRegistrationStatus.OPEN - if self.team_count is not None: + if self.team_count_limit is True: # Get all team registrations excluding walk_outs current_team_count = self.team_registrations.exclude(walk_out=True).count() if current_team_count >= self.team_count: @@ -1174,8 +1187,14 @@ class Tournament(BaseModel): return True def get_waiting_list_position(self): + current_time = timezone.now() + local_registration_federal_limit = self.local_registration_federal_limit() + if self.team_sorting == TeamSortingType.RANK and local_registration_federal_limit is not None: + if current_time < local_registration_federal_limit: + return -1 + # If no target team count exists, no one goes to waiting list - if self.team_count is None: + if self.team_count_limit is False: return -1 # Get count of active teams (not walked out) @@ -1257,7 +1276,6 @@ class Tournament(BaseModel): current_year += 1 user_age = current_year - int(birth_year) - print("user_age", user_age) # Check age category restrictions if self.federal_age_category == FederalAgeCategory.A11_12 and user_age > 12: @@ -1290,12 +1308,49 @@ class Tournament(BaseModel): def min_player_rank(self): return FederalLevelCategory.min_player_rank(self.federal_level_category, self.federal_category, self.federal_age_category) - def first_waiting_list_team(self, teams): + def local_registration_federal_limit(self): + timezone = self.timezone() + if self.registration_date_limit is not None: + return self.registration_date_limit.astimezone(timezone) + + if self.closed_registration_date is not None: + return self.closed_registration_date.astimezone(timezone) + + local_start_date = self.local_start_date() + + if local_start_date is None: + return None + + if self.federal_level_category == FederalLevelCategory.P500: + # 7 days before at 23:59 + return (local_start_date - timedelta(days=7)).replace(hour=23, minute=59, second=59) + elif self.federal_level_category in [FederalLevelCategory.P1000, + FederalLevelCategory.P1500, + FederalLevelCategory.P2000]: + # 14 days before at 23:59 + return (local_start_date - timedelta(days=14)).replace(hour=23, minute=59, second=59) + return None + + def waiting_list_teams(self, teams): + current_time = timezone.now() + local_registration_federal_limit = self.local_registration_federal_limit() + if self.team_sorting == TeamSortingType.RANK and local_registration_federal_limit is not None: + if current_time < local_registration_federal_limit: + return None + if len(teams)<=self.team_count: return None waiting_teams = [team for team in teams if team.stage == "Attente"] - if len(waiting_teams) > 0: - return waiting_teams[0].team_registration + return waiting_teams + + def first_waiting_list_team(self, teams): + waiting_list_team = self.waiting_list_teams(teams) + if waiting_list_team is None: + return None + if len(waiting_list_team) > 0: + return waiting_list_team[0].team_registration + else: + return None def broadcasted_prog(self): # Get matches from broadcasted_matches_and_group_stages @@ -1418,19 +1473,16 @@ class Tournament(BaseModel): def umpire_contact(self): if self.umpire_custom_contact is not None: - print(self.umpire_custom_contact) return self.umpire_custom_contact return self.event.creator.full_name() def umpire_mail(self): if self.umpire_custom_mail is not None: - print(self.umpire_custom_mail) return self.umpire_custom_mail return self.event.creator.email def umpire_phone(self): if self.umpire_custom_phone is not None: - print(self.umpire_custom_phone) return self.umpire_custom_phone return self.event.creator.phone @@ -1486,6 +1538,12 @@ class TeamSummon: } class TeamItem: + def __str__(self): + return f"TeamItem({self.team_registration.id}, names={self.names}, stage={self.stage})" + + def __repr__(self): + return self.__str__() + def __init__(self, team_registration): self.names = team_registration.team_names() self.date = team_registration.local_call_date() diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py index 6a3a59d..c017685 100644 --- a/tournaments/services/email_service.py +++ b/tournaments/services/email_service.py @@ -2,6 +2,7 @@ from django.core.mail import EmailMessage from django.utils import timezone from django.urls import reverse from enum import Enum +from ..models.tournament import TeamSortingType class TeamEmailType(Enum): REGISTERED = "registered" @@ -74,6 +75,13 @@ class TournamentEmailService: body_parts.append(f"Votre inscription en liste d'attente du tournoi {tournament_details_str} est confirmée.") else: body_parts.append(f"Votre inscription au tournoi {tournament_details_str} est confirmée.") + if tournament.team_sort == TeamSortingType.RANK: + cloture_date = tournament.local_registration_federal_limit().strftime("%d/%m/%Y à %H:%M") + loc = "" + if cloture_date is not None: + loc = f", prévu le {cloture_date}" + 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" diff --git a/tournaments/signals.py b/tournaments/signals.py index 48859cb..0eacdf9 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -113,10 +113,10 @@ def check_waiting_list(sender, instance, **kwargs): teams_out_to_warn = [] teams_in_to_warn = [] + previous_state_teams = previous_state.teams(True) if previous_state.team_count > instance.team_count: teams_to_remove_count = previous_state.team_count - instance.team_count - previous_state_teams = previous_state.teams(True) sorted_teams = sorted( [team for team in previous_state_teams if team.stage != "Attente" and not (team.wildcard_bracket or team.wildcard_groupstage)], key=lambda t: ( @@ -127,7 +127,7 @@ def check_waiting_list(sender, instance, **kwargs): teams_out_to_warn = sorted_teams[-teams_to_remove_count:] elif previous_state.team_count < instance.team_count: teams_in_to_warn = [ - team for team in previous_state.teams(True)[(instance.team_count - previous_state.team_count):] + team for team in previous_state_teams[(instance.team_count - previous_state.team_count):] if team.stage == "Attente" ] diff --git a/tournaments/templates/register_tournament.html b/tournaments/templates/register_tournament.html index 59f9d6d..c8349d1 100644 --- a/tournaments/templates/register_tournament.html +++ b/tournaments/templates/register_tournament.html @@ -26,6 +26,22 @@ Un email de confirmation a été envoyé à l'adresse associée à votre compte Padel Club ({{ user.email }}). Pensez à vérifier vos spams si vous ne recevez pas l'email. En cas de problème, contactez le juge-arbitre.

{% else %} + + {% if team_form.errors %} +
+ {% if team_form.non_field_errors %} + {% for error in team_form.non_field_errors %} +

{{ error }}

+ {% endfor %} + {% endif %} + + {% for field in team_form %} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} + {% endfor %} +
+ {% endif %}
{% csrf_token %} @@ -109,15 +125,15 @@ {% endif %}
- {% if form.errors %} + {% if add_player_form.errors %}
- {% if form.non_field_errors %} - {% for error in form.non_field_errors %} + {% if add_player_form.non_field_errors %} + {% for error in add_player_form.non_field_errors %}

{{ error }}

{% endfor %} {% endif %} - {% for field in form %} + {% for field in add_player_form %} {% for error in field.errors %}

{{ error }}

{% endfor %} From 15f5aae34fca9cce76447c8d4188aba7eab576f6 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 29 Mar 2025 14:22:07 +0100 Subject: [PATCH 02/12] fix signal tournament team reg --- tournaments/signals.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tournaments/signals.py b/tournaments/signals.py index 0eacdf9..d51286c 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -113,21 +113,16 @@ def check_waiting_list(sender, instance, **kwargs): teams_out_to_warn = [] teams_in_to_warn = [] - previous_state_teams = previous_state.teams(True) - if previous_state.team_count > instance.team_count: - teams_to_remove_count = previous_state.team_count - instance.team_count - sorted_teams = sorted( - [team for team in previous_state_teams if team.stage != "Attente" and not (team.wildcard_bracket or team.wildcard_groupstage)], - key=lambda t: ( - t.registration_date is None, t.registration_date or datetime.min, t.initial_weight, t.team_registration.id - ) if previous_state.team_sorting == TeamSortingType.INSCRIPTION_DATE else - (t.initial_weight, t.team_registration.id) - ) - teams_out_to_warn = sorted_teams[-teams_to_remove_count:] + teams_that_will_be_out = instance.teams(True)[instance.team_count:] + teams_out_to_warn = [ + team for team in teams_that_will_be_out + if team.stage != "Attente" + ] elif previous_state.team_count < instance.team_count: + teams_that_will_be_in = previous_state.teams(True)[previous_state.team_count:instance.team_count] teams_in_to_warn = [ - team for team in previous_state_teams[(instance.team_count - previous_state.team_count):] + team for team in teams_that_will_be_in if team.stage == "Attente" ] From d9a696d82ee37e96c228eb6894a6175e2a6e152b Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 29 Mar 2025 18:45:44 +0100 Subject: [PATCH 03/12] fix crash --- tournaments/forms.py | 49 +++++++++++++++++++++------ tournaments/services/email_service.py | 2 +- tournaments/templates/profile.html | 11 ------ 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/tournaments/forms.py b/tournaments/forms.py index cc4d314..872514b 100644 --- a/tournaments/forms.py +++ b/tournaments/forms.py @@ -12,6 +12,11 @@ from django.utils.encoding import force_bytes class CustomUserCreationForm(UserCreationForm): usable_password = None + def clean_licence_id(self): + licence_id = self.cleaned_data.get('licence_id') + if licence_id: + return licence_id.replace(' ', '').strip().upper() + return licence_id class Meta: model = CustomUser @@ -33,6 +38,22 @@ class CustomUserCreationForm(UserCreationForm): class SimpleCustomUserCreationForm(UserCreationForm): usable_password = None + def clean_licence_id(self): + licence_id = self.cleaned_data.get('licence_id') + if licence_id: + return licence_id.replace(' ', '').strip().upper() + return licence_id + + def clean_phone(self): + phone = self.cleaned_data.get('phone') + if phone: + # Remove all spaces + phone = phone.replace(' ', '') + # Basic regex for phone numbers, matching common formats + if not re.match(r"^\+?\d{10,15}$", phone): + raise forms.ValidationError("Entrer un numéro de téléphone valide.") + return phone + class Meta: model = CustomUser fields = UserCreationForm.Meta.fields + ('email', 'phone', 'first_name', 'last_name', 'licence_id', 'country') @@ -125,16 +146,8 @@ class AddPlayerForm(forms.Form): def clean_licence_id(self): licence_id = self.cleaned_data.get('licence_id') - - # Convert to uppercase - licence_id = licence_id.upper() - - # Update the cleaned_data with the modified licence_id - self.cleaned_data['licence_id'] = licence_id - - # Optionally, print the cleaned license ID for debugging - print(f"Cleaned Licence ID (inside clean_licence_id): {licence_id}") - + if licence_id: + licence_id = licence_id.replace(' ', '').strip().upper() return licence_id def clean_last_name(self): @@ -193,6 +206,22 @@ class ProfileUpdateForm(forms.ModelForm): # Remove autofocus from the 'username' field self.fields['username'].widget.attrs.pop("autofocus", None) + def clean_licence_id(self): + licence_id = self.cleaned_data.get('licence_id') + if licence_id: + return licence_id.replace(' ', '').upper() + return licence_id + + def clean_phone(self): + phone = self.cleaned_data.get('phone') + if phone: + # Remove all spaces + phone = phone.replace(' ', '') + # Basic regex for phone numbers, matching common formats + if not re.match(r"^\+?\d{10,15}$", phone): + raise forms.ValidationError("Entrer un numéro de téléphone valide.") + return phone + class Meta: model = CustomUser fields = ['first_name', 'last_name', 'licence_id', 'username', 'email', 'phone'] diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py index 33fe93f..f8f904c 100644 --- a/tournaments/services/email_service.py +++ b/tournaments/services/email_service.py @@ -75,7 +75,7 @@ class TournamentEmailService: body_parts.append(f"Votre inscription en liste d'attente du tournoi {tournament_details_str} est confirmée.") else: body_parts.append(f"Votre inscription au tournoi {tournament_details_str} est confirmée.") - if tournament.team_sort == TeamSortingType.RANK: + if tournament.team_sorting == TeamSortingType.RANK: cloture_date = tournament.local_registration_federal_limit().strftime("%d/%m/%Y à %H:%M") loc = "" if cloture_date is not None: diff --git a/tournaments/templates/profile.html b/tournaments/templates/profile.html index 89fd5f4..9216b92 100644 --- a/tournaments/templates/profile.html +++ b/tournaments/templates/profile.html @@ -23,17 +23,6 @@
- {% if form.errors %} -
- {% for field, errors in form.errors.items %} - {% for error in errors %} -

{{ error }}

- {% endfor %} - {% endfor %} -
- {% endif %} - - {% if form.non_field_errors %}
{% for error in form.non_field_errors %} From 9c456a9357af27a558d802f910d76f015505716f Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 29 Mar 2025 20:18:56 +0100 Subject: [PATCH 04/12] fix lot of stuff around forms and password reset --- tournaments/custom_views.py | 20 +++++-- .../services/tournament_registration.py | 13 +++++ tournaments/templates/profile.html | 52 +++++++++--------- .../templates/register_tournament.html | 51 ++++++++---------- tournaments/templates/registration/login.html | 8 +-- .../registration/password_reset_complete.html | 4 +- .../registration/password_reset_confirm.html | 18 +------ .../registration/password_reset_done.html | 4 +- .../registration/password_reset_form.html | 17 +----- .../templates/registration/signup.html | 53 ++++++++++--------- .../tournaments/tournament_info.html | 9 ---- tournaments/urls.py | 12 ++--- tournaments/views.py | 53 +++++++++++++++++++ 13 files changed, 178 insertions(+), 136 deletions(-) diff --git a/tournaments/custom_views.py b/tournaments/custom_views.py index 01d54bb..4e3d2f1 100644 --- a/tournaments/custom_views.py +++ b/tournaments/custom_views.py @@ -10,19 +10,33 @@ class CustomLoginView(auth_views.LoginView): def get_success_url(self): # First check the 'next' parameter which has higher priority next_url = self.request.POST.get('next') or self.request.GET.get('next') + + # Check if the next URL is a password reset page and avoid that redirect if next_url and next_url.strip(): + # Avoid redirecting to password reset pages after login + if 'reset' in next_url or 'password_reset' in next_url: + # Redirect to profile or index instead + return reverse('profile') return next_url # Then check if we have a stored referrer URL referrer = self.request.session.get('login_referrer') if referrer: - # Clear the stored referrer to prevent reuse - del self.request.session['login_referrer'] - return referrer + # Avoid redirecting to password reset pages from stored referrer + if 'reset' not in referrer and 'password_reset' not in referrer: + # Clear the stored referrer to prevent reuse + del self.request.session['login_referrer'] + return referrer # Fall back to default return reverse('index') def get(self, request, *args, **kwargs): + # Clear any potential password reset session data + keys_to_clear = [key for key in request.session.keys() + if 'reset' in key or 'password' in key] + for key in keys_to_clear: + del request.session[key] + messages.get_messages(request).used = True return super().get(request, *args, **kwargs) diff --git a/tournaments/services/tournament_registration.py b/tournaments/services/tournament_registration.py index 0233edb..d42ae9e 100644 --- a/tournaments/services/tournament_registration.py +++ b/tournaments/services/tournament_registration.py @@ -48,6 +48,13 @@ class TournamentRegistrationService: if not self.context['add_player_form'].is_valid(): return + # Clear existing messages if the form is valid + from django.contrib.messages import get_messages + storage = get_messages(self.request) + # Iterate through the storage to clear it + for _ in storage: + pass + player_data = self.context['add_player_form'].cleaned_data licence_id = player_data.get('licence_id', '').upper() @@ -112,6 +119,12 @@ class TournamentRegistrationService: self.context['registration_successful'] = True def handle_get_request(self): + from django.contrib.messages import get_messages + storage = get_messages(self.request) + # Iterate through the storage to clear it + for _ in storage: + pass + self.context['add_player_form'] = AddPlayerForm() self.context['team_form'] = self.initialize_team_form() self.initialize_session_data() diff --git a/tournaments/templates/profile.html b/tournaments/templates/profile.html index 9216b92..2e6a1a7 100644 --- a/tournaments/templates/profile.html +++ b/tournaments/templates/profile.html @@ -20,16 +20,34 @@ {% load static %} {% load tz %} +{% if form.errors or password_change_form.errors %} +
+
+
+ {% for field in form %} + {% if field.errors %} + {% for error in field.errors %} +
{{ field.label }} : {{ error }}
+ {% endfor %} + {% endif %} + {% endfor %} +
+
+ {% for field in password_change_form %} + {% if field.errors %} + {% for error in field.errors %} +
{{ field.label }} : {{ error }}
+ {% endfor %} + {% endif %} + {% endfor %} +
+
+
+{% endif %} +
- {% if form.non_field_errors %} -
- {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} {% csrf_token %} {{ form.as_p }} @@ -40,25 +58,7 @@
- {% if password_change_form.errors %} -
- {% for field, errors in password_change_form.errors.items %} - {% for error in errors %} -

{{ error }}

- {% endfor %} - {% endfor %} -
- {% endif %} - - {% if password_change_form.non_field_errors %} -
- {% for error in password_change_form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} - - + {% csrf_token %} {{ password_change_form.as_p }} diff --git a/tournaments/templates/register_tournament.html b/tournaments/templates/register_tournament.html index c8349d1..95a1212 100644 --- a/tournaments/templates/register_tournament.html +++ b/tournaments/templates/register_tournament.html @@ -28,20 +28,27 @@ {% else %} {% if team_form.errors %} -
- {% if team_form.non_field_errors %} - {% for error in team_form.non_field_errors %} -

{{ error }}

- {% endfor %} - {% endif %} - +
{% for field in team_form %} - {% for error in field.errors %} -

{{ error }}

- {% endfor %} + {% if field.errors %} + {% for error in field.errors %} +
{{ field.label }} : {{ error }}
+ {% endfor %} + {% endif %} + {% endfor %} +
+ {% endif %} + + {% if messages %} +
+ {% for message in messages %} +
+ {{ message }} +
{% endfor %}
{% endif %} + {% csrf_token %} @@ -52,6 +59,12 @@ Informations de contact

+ {% if team_form.non_field_errors %} + {% for error in team_form.non_field_errors %} +

{{ error }}

+ {% endfor %} + {% endif %} + {{ team_form.as_p }}
@@ -124,24 +137,6 @@ {% endif %} {% endif %} -
- {% if add_player_form.errors %} -
- {% if add_player_form.non_field_errors %} - {% for error in add_player_form.non_field_errors %} -

{{ error }}

- {% endfor %} - {% endif %} - - {% for field in add_player_form %} - {% for error in field.errors %} -

{{ error }}

- {% endfor %} - {% endfor %} -
- {% endif %} -
- -

+

- {% for message in messages %} -
{{ message }}
- {% endfor %}
{% endblock %} diff --git a/tournaments/templates/registration/password_reset_done.html b/tournaments/templates/registration/password_reset_done.html index bfc7035..28f2b9f 100644 --- a/tournaments/templates/registration/password_reset_done.html +++ b/tournaments/templates/registration/password_reset_done.html @@ -11,9 +11,9 @@ Un e-mail contenant un lien pour réinitialiser votre mot de passe a été envoyé à votre adresse. Veuillez vérifier votre boîte de réception.

-

+

diff --git a/tournaments/templates/registration/password_reset_form.html b/tournaments/templates/registration/password_reset_form.html index 40e8858..cd0e3d7 100644 --- a/tournaments/templates/registration/password_reset_form.html +++ b/tournaments/templates/registration/password_reset_form.html @@ -7,16 +7,6 @@
- {% if form.errors %} -
- {% for field, errors in form.errors.items %} - {% for error in errors %} -

{{ error }}

- {% endfor %} - {% endfor %} -
- {% endif %} - {% if form.non_field_errors %}
@@ -31,13 +21,10 @@ -

+

- {% for message in messages %} -
{{ message }}
- {% endfor %}
{% endblock %} diff --git a/tournaments/templates/registration/signup.html b/tournaments/templates/registration/signup.html index d541d72..26b3b02 100644 --- a/tournaments/templates/registration/signup.html +++ b/tournaments/templates/registration/signup.html @@ -8,35 +8,40 @@ {% load static %} {% load tz %} -
-
-
- {% if form.errors %} -
- {% for field, errors in form.errors.items %} - {% for error in errors %} -

{{ error }}

+
+ {% if form.errors %} +
+
+
+ {% for field in form %} + {% if field.errors %} + {% for error in field.errors %} +
{{ field.label }} : {{ error }}
+ {% endfor %} + {% endif %} {% endfor %} - {% endfor %} +
- {% endif %} +
+ {% endif %} - - {% if form.non_field_errors %} -
- {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} +
+
+ {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} -
- {% csrf_token %} - {{ form.as_p }} - -
+
+ {% csrf_token %} + {{ form.as_p }} + +
+
-
{% endblock %} diff --git a/tournaments/templates/tournaments/tournament_info.html b/tournaments/templates/tournaments/tournament_info.html index 9c93a55..822bcee 100644 --- a/tournaments/templates/tournaments/tournament_info.html +++ b/tournaments/templates/tournaments/tournament_info.html @@ -47,12 +47,6 @@ {% if tournament.is_unregistration_possible %}

-

- {% for message in messages %} -
{{ message }}
- {% endfor %} -
- @@ -164,9 +158,6 @@

{% endif %} {% else %} - {% for message in messages %} -
{{ message }}
- {% endfor %}

Connectez-vous ! diff --git a/tournaments/urls.py b/tournaments/urls.py index f544567..e0a49bb 100644 --- a/tournaments/urls.py +++ b/tournaments/urls.py @@ -55,13 +55,7 @@ urlpatterns = [ path('utils/xls-to-csv/', views.xls_to_csv, name='xls-to-csv'), path('mail-test/', views.simple_form_view, name='mail-test'), path('login/', CustomLoginView.as_view(), name='custom-login'), - path('password_change/', - auth_views.PasswordChangeView.as_view( - success_url='/profile/', # Redirect back to profile after success - form_class=CustomPasswordChangeForm - ), - name='password_change' - ), + path('custom_password_change/', views.custom_password_change, name='custom_password_change'), path('logout/', views.custom_logout, name='custom_logout'), path('signup/', views.signup, name='signup'), # URL pattern for signup # path('profile/', views.profile, name='profile'), # URL pattern for signup @@ -70,7 +64,9 @@ urlpatterns = [ path('tournaments//unregister/', views.unregister_tournament, name='unregister_tournament'), path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'), path('password_reset_done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'), - path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'), + path('reset/done/', + views.CustomPasswordResetCompleteView.as_view(), + name='password_reset_complete'), path('profile/', views.ProfileUpdateView.as_view(), name='profile'), path('admin/tournament-import/', views.tournament_import_view, name='tournament_import'), path('admin/status/', views.status_page, name='status_page'), diff --git a/tournaments/views.py b/tournaments/views.py index a3dfa5f..14990c6 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -2,6 +2,11 @@ import os import csv +from django.contrib.auth import update_session_auth_hash +from django.contrib.auth.views import PasswordResetCompleteView +from django.shortcuts import redirect +from django.contrib.auth import login +from django.contrib.auth import get_user_model from django.shortcuts import render, get_object_or_404 from django.http import JsonResponse, HttpResponse from django.utils.encoding import force_str @@ -818,6 +823,54 @@ class CustomPasswordResetConfirmView(PasswordResetConfirmView): except (TypeError, ValueError, User.DoesNotExist): raise Http404("User not found") +class CustomPasswordResetCompleteView(PasswordResetCompleteView): + template_name = 'registration/password_reset_complete.html' + + def get(self, request, *args, **kwargs): + # Get the user from the session + username = request.session.get('reset_username') + + if username: + try: + # Get the user + User = get_user_model() + user = User.objects.get(username=username) + + # Log the user in + login(request, user, backend='django.contrib.auth.backends.ModelBackend') + + # Clean up the session + if 'reset_username' in request.session: + del request.session['reset_username'] + + # Redirect to the profile page + return redirect('profile') + except User.DoesNotExist: + pass + + # If no username in session or user not found, proceed with normal view + return super().get(request, *args, **kwargs) + +@login_required +def custom_password_change(request): + if request.method == 'POST': + form = CustomPasswordChangeForm(user=request.user, data=request.POST) + if form.is_valid(): + user = form.save() + update_session_auth_hash(request, user) # Important to keep user logged in + messages.success(request, 'Votre mot de passe a été mis à jour avec succès!') + return redirect('profile') + else: + # Form is invalid, show errors + profile_form = ProfileUpdateForm(instance=request.user) + return render(request, 'profile.html', { + 'form': profile_form, + 'password_change_form': form + }) + + # If not POST, redirect to profile page + return redirect('profile') + @login_required def my_tournaments(request): user = request.user From 285430d6901ce40e7aefc7fdc62083e52907d1a5 Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 1 Apr 2025 10:38:19 +0200 Subject: [PATCH 05/12] new style list of tournaments --- shop/static/shop/css/shop.css | 6 +- shop/templates/shop/product_item.html | 2 +- tournaments/models/enums.py | 51 ++++- tournaments/models/player_registration.py | 51 ++++- tournaments/models/team_registration.py | 10 +- tournaments/models/tournament.py | 206 +++++++++++++----- tournaments/static/tournaments/css/style.css | 145 ++++++++++-- .../tournaments/css/tournament_bracket.css | 8 +- .../registration/my_tournaments.html | 12 +- .../templates/tournaments/tournament_row.html | 89 ++++---- .../templates/tournaments/tournaments.html | 81 +++---- .../tournaments/tournaments_list.html | 4 +- tournaments/templatetags/__init__.py | 0 tournaments/templatetags/tournament_tags.py | 7 + 14 files changed, 504 insertions(+), 168 deletions(-) create mode 100644 tournaments/templatetags/__init__.py create mode 100644 tournaments/templatetags/tournament_tags.py diff --git a/shop/static/shop/css/shop.css b/shop/static/shop/css/shop.css index 105aaf3..824a705 100644 --- a/shop/static/shop/css/shop.css +++ b/shop/static/shop/css/shop.css @@ -95,7 +95,7 @@ .add-to-cart-button, .checkout-button { background-color: #90ee90; - color: #707070; + color: #505050; border: none; border-radius: 12px; font-size: 12px; @@ -120,7 +120,7 @@ } .coupon-section { - color: #707070; + color: #505050; font-size: 12px; font-weight: 600; text-decoration: none; @@ -129,7 +129,7 @@ .confirm-nav-button { background-color: #90ee90; - color: #707070; + color: #505050; font-size: 12px; font-weight: 600; text-decoration: none; diff --git a/shop/templates/shop/product_item.html b/shop/templates/shop/product_item.html index 4cdb4bb..bb7c275 100644 --- a/shop/templates/shop/product_item.html +++ b/shop/templates/shop/product_item.html @@ -185,7 +185,7 @@ function addToCartAjax(productId) { notification.style.right = '20px'; notification.style.padding = '20px'; notification.style.backgroundColor = '#90ee90'; - notification.style.color = '#707070'; + notification.style.color = '#505050'; notification.style.borderRadius = '12px'; notification.style.zIndex = '9999'; notification.style.opacity = '0'; diff --git a/tournaments/models/enums.py b/tournaments/models/enums.py index d2e5187..8a2f5e4 100644 --- a/tournaments/models/enums.py +++ b/tournaments/models/enums.py @@ -146,6 +146,7 @@ class OnlineRegistrationStatus(models.IntegerChoices): WAITING_LIST_FULL = 6, 'Waiting List Full' IN_PROGRESS = 7, 'In Progress' ENDED_WITH_RESULTS = 8, 'Ended with Results' + CANCELED = 9, 'Canceled' def status_localized(self) -> str: status_map = { @@ -156,10 +157,58 @@ class OnlineRegistrationStatus(models.IntegerChoices): OnlineRegistrationStatus.WAITING_LIST_POSSIBLE: "Liste d'attente ouverte", OnlineRegistrationStatus.WAITING_LIST_FULL: "Liste d'attente complète", OnlineRegistrationStatus.IN_PROGRESS: "Tournoi en cours", - OnlineRegistrationStatus.ENDED_WITH_RESULTS: "Tournoi terminé" + OnlineRegistrationStatus.ENDED_WITH_RESULTS: "Tournoi terminé", + OnlineRegistrationStatus.CANCELED: "Tournoi annulé" } return status_map.get(self, "") + def short_label(self) -> str: + """Returns a short, concise label for the status box""" + label_map = { + OnlineRegistrationStatus.OPEN: "ouvert", + OnlineRegistrationStatus.NOT_ENABLED: "désactivé", + OnlineRegistrationStatus.NOT_STARTED: "à venir", + OnlineRegistrationStatus.ENDED: "clôturé", + OnlineRegistrationStatus.WAITING_LIST_POSSIBLE: "ouvert", + OnlineRegistrationStatus.WAITING_LIST_FULL: "complet", + OnlineRegistrationStatus.IN_PROGRESS: "en cours", + OnlineRegistrationStatus.ENDED_WITH_RESULTS: "résultats", + OnlineRegistrationStatus.CANCELED: "annulé" + } + return label_map.get(self, "") + + def box_class(self) -> str: + """Returns the CSS class for the status box""" + class_map = { + OnlineRegistrationStatus.OPEN: "light-green", + OnlineRegistrationStatus.NOT_ENABLED: "gray", + OnlineRegistrationStatus.NOT_STARTED: "light-green", + OnlineRegistrationStatus.ENDED: "gray", + OnlineRegistrationStatus.WAITING_LIST_POSSIBLE: "light-orange", + OnlineRegistrationStatus.WAITING_LIST_FULL: "light-red", + OnlineRegistrationStatus.IN_PROGRESS: "blue", + OnlineRegistrationStatus.ENDED_WITH_RESULTS: "dark-gray", + OnlineRegistrationStatus.CANCELED: "light-red", + } + return class_map.get(self, "gray") + + def display_box(self) -> bool: + """ + Determines whether this status should display a status box + Returns True if the status should be displayed, False otherwise + """ + # List the statuses that should display a box + display_statuses = [ + OnlineRegistrationStatus.OPEN, + OnlineRegistrationStatus.NOT_STARTED, + OnlineRegistrationStatus.WAITING_LIST_POSSIBLE, + OnlineRegistrationStatus.WAITING_LIST_FULL, + OnlineRegistrationStatus.CANCELED, + # You can add or remove statuses as needed + ] + + return self in display_statuses + class UserOrigin(models.IntegerChoices): ADMIN = 0, 'Admin' SITE = 1, 'Site' diff --git a/tournaments/models/player_registration.py b/tournaments/models/player_registration.py index e23be85..9d00faa 100644 --- a/tournaments/models/player_registration.py +++ b/tournaments/models/player_registration.py @@ -1,5 +1,5 @@ from django.db import models -from . import SideStoreModel, TeamRegistration, PlayerSexType, PlayerDataSource, PlayerPaymentType, FederalCategory +from . import SideStoreModel, TeamRegistration, PlayerSexType, PlayerDataSource, PlayerPaymentType, OnlineRegistrationStatus import uuid from django.utils import timezone @@ -92,3 +92,52 @@ class PlayerRegistration(SideStoreModel): return "1ère" return "1er" return f"{self.rank}ème" + + def get_registration_status(self): + """ + Returns a status object with information about the player's registration status. + This object contains display_box, box_class, and short_label properties + used in the tournament row template. + Returns None if no relevant status can be determined. + """ + # If no team registration exists, return None + if not self.team_registration: + return None + + status = { + 'display_box': True, + 'box_class': 'gray', + 'short_label': 'inscrit' + } + + tournament = self.team_registration.tournament + team = self.team_registration + + # Tournament is ended with results + if tournament.get_online_registration_status() is OnlineRegistrationStatus.ENDED_WITH_RESULTS: + if team.get_final_ranking_component(): + status['box_class'] = 'light-green' + status['short_label'] = team.get_final_ranking_component() + return status + + # Team has walked out + if team.walk_out: + status['box_class'] = 'light-red' + status['short_label'] = 'forfait' + return status + + # Tournament is in progress + if tournament.supposedly_in_progress(): + status['box_class'] = 'light-green' + status['short_label'] = 'en lice' + return status + + # Tournament hasn't started yet + if team.is_in_waiting_list() >= 0: + status['box_class'] = 'light-yellow' + status['short_label'] = "en attente" + else: + status['box_class'] = 'light-green' + status['short_label'] = 'inscrit' + + return status diff --git a/tournaments/models/team_registration.py b/tournaments/models/team_registration.py index 5d468d1..ebb2e08 100644 --- a/tournaments/models/team_registration.py +++ b/tournaments/models/team_registration.py @@ -240,10 +240,16 @@ class TeamRegistration(SideStoreModel): def get_final_ranking(self): + get_final_ranking_component = self.get_final_ranking_component() + if get_final_ranking_component: + return get_final_ranking_component + self.ranking_delta() + return None + + def get_final_ranking_component(self): if self.final_ranking: if self.final_ranking == 1: - return "1er" + self.ranking_delta() - return f"{self.final_ranking}ème" + self.ranking_delta() + return "1er" + return f"{self.final_ranking}ème" return None def ranking_delta(self): diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index c1a4e15..b117f62 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1,4 +1,3 @@ -from time import daylight from zoneinfo import ZoneInfo from django.db import models from typing import TYPE_CHECKING @@ -8,12 +7,13 @@ if TYPE_CHECKING: from . import BaseModel, Event, TournamentPayment, FederalMatchCategory, FederalCategory, FederalLevelCategory, FederalAgeCategory, OnlineRegistrationStatus import uuid from django.utils import timezone, formats -from datetime import datetime, timedelta -from zoneinfo import ZoneInfo +from datetime import datetime, timedelta, time from tournaments.utils.player_search import get_player_name_from_csv from shared.cryptography import encryption_util from ..utils.extensions import plural_format +from django.utils.formats import date_format +from ..utils.licence_validator import LicenseValidator class TeamSortingType(models.IntegerChoices): RANK = 1, 'Rank' @@ -231,37 +231,29 @@ class Tournament(BaseModel): else: return None - def tournament_status_display(self): - if self.is_canceled() is True: - return "Annulé" - - teams = self.teams(True) - 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" - if len(teams) > 1: - word = word + "s" - return f"{len(teams)} {word}" - else: - return None + def get_tournament_status(self): + return self.get_online_registration_status().status_localized() - registration_status = None - if self.enable_online_registration == True: - registration_status = self.get_online_registration_status().status_localized() + def tournament_status_display(self): + teams = self.teams(True) + 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 = "inscription" + word = "équipe" if len(teams) > 1: word = word + "s" - if registration_status is not None: - return f"{registration_status}\n{len(teams)} {word}" - else: - return f"{len(teams)} {word}" + return f"{len(teams)} {word}" else: - if registration_status is not None: - return f"{registration_status}" return None + if teams is not None and len(teams) > 0: + word = "inscription" + if len(teams) > 1: + word = word + "s" + return f"{len(teams)} {word}" + else: + return None + def name_and_event(self): event_name = None if self.event: @@ -332,9 +324,9 @@ class Tournament(BaseModel): index = i # Check if team_count exists - if self.team_count: + if self.team_count_limit == True: # Team is not in list - if index < self.team_count: + if index < 0: print("Team is not in list", index, self.team_count) return -1 # Return position in waiting list relative to target count @@ -1133,39 +1125,56 @@ class Tournament(BaseModel): return "La sélection se fait par date d'inscription" def get_online_registration_status(self): - if self.supposedly_in_progress(): - return OnlineRegistrationStatus.ENDED - if self.closed_registration_date is not None: - return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE - if self.end_date is not None: - return OnlineRegistrationStatus.ENDED_WITH_RESULTS + if self.is_canceled(): + return OnlineRegistrationStatus.CANCELED + if self.end_date is not None: + return OnlineRegistrationStatus.ENDED_WITH_RESULTS + if self.supposedly_in_progress(): + return OnlineRegistrationStatus.ENDED + if self.closed_registration_date is not None: + return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE - now = timezone.now() + now = timezone.now() + + if self.opening_registration_date is not None: + timezoned_datetime = timezone.localtime(self.opening_registration_date) + if now < timezoned_datetime: + return OnlineRegistrationStatus.NOT_STARTED - if self.opening_registration_date is not None: - timezoned_datetime = timezone.localtime(self.opening_registration_date) - if now < timezoned_datetime: - return OnlineRegistrationStatus.NOT_STARTED - - if self.registration_date_limit is not None: - timezoned_datetime = timezone.localtime(self.registration_date_limit) - if now > timezoned_datetime: - return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE - - if self.team_sorting == TeamSortingType.RANK: - return OnlineRegistrationStatus.OPEN - - if self.team_count_limit is True: - # Get all team registrations excluding walk_outs - 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 - if waiting_list_count >= self.waiting_list_limit: - return OnlineRegistrationStatus.WAITING_LIST_FULL - return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE + if self.registration_date_limit is not None: + timezoned_datetime = timezone.localtime(self.registration_date_limit) + if now > timezoned_datetime: + return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE + + if self.team_sorting == TeamSortingType.RANK: return OnlineRegistrationStatus.OPEN + if self.team_count_limit is True: + # Get all team registrations excluding walk_outs + 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 + if waiting_list_count >= self.waiting_list_limit: + return OnlineRegistrationStatus.WAITING_LIST_FULL + return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE + return OnlineRegistrationStatus.OPEN + + def get_registration_status_short_label(self): + """Returns a short label for the registration status""" + status = self.get_online_registration_status() + return status.short_label() + + def get_registration_status_class(self): + """Returns the CSS class for the registration status box""" + status = self.get_online_registration_status() + return status.box_class() + + def should_display_status_box(self): + """Returns whether the registration status box should be displayed""" + status = self.get_online_registration_status() + return status.display_box() + def is_unregistration_possible(self): # Check if tournament has started if self.supposedly_in_progress(): @@ -1490,6 +1499,87 @@ class Tournament(BaseModel): return self.event.creator.phone + + @property + def week_day(self): + """Return the weekday name (e.g., 'Monday')""" + date = self.local_start_date() + return date_format(date, format='D') + '.' # 'l' gives full weekday name + + @property + def day(self): + """Return the day of the month""" + date = self.local_start_date() + return date.day + + @property + def month(self): + """ + Return the month name in lowercase: + - If full month name is 4 letters or fewer, return as is + - If more than 4 letters, return first 4 letters with a dot + """ + date = self.local_start_date() + # Get full month name and convert to lowercase + full_month = date_format(date, format='F').lower() + + # Check if the month name is 5 letters or fewer + if len(full_month) <= 5: + return full_month + else: + # Truncate to 5 letters and add a dot + return f"{full_month[:5]}." + + @property + def year(self): + """Return the year""" + date = self.local_start_date() + return date.year + + @property + def localized_day_duration(self): + """ + Return localized day duration in French: + - If multiple days: '2 jours', '3 jours', etc. + - If 1 day and starts after 18:00: 'soirée' + - If 1 day and starts before 18:00: 'journée' + """ + # Assuming day_duration is a property or field that returns the number of days + days = self.day_duration + + if days > 1: + return f"{days} jours" + else: + # For single day events, check the starting hour + start_time = self.local_start_date().time() + evening_threshold = time(18, 0) # 18:00 (6 PM) + + if start_time >= evening_threshold: + return "soirée" + else: + return "journée" + + def get_player_registration_status_by_licence(self, user): + from . import PlayerRegistration + licence_id = user.licence_id + if not licence_id: + return None + + validator = LicenseValidator(licence_id) + stripped_license = validator.stripped_license + + # Check if there is a PlayerRegistration for this user in this tournament + user_player = PlayerRegistration.objects.filter( + licence_id__icontains=stripped_license, + team_registration__tournament=self, + ).first() + + if user_player: + return user_player.get_registration_status() + else: + return None + + class MatchGroup: def __init__(self, name, matches, formatted_schedule, round_id=None): self.name = name diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index 82b4f40..9409d48 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -35,7 +35,7 @@ body { } label { - color: #707070; + color: #505050; font-size: 1.1em; } @@ -55,7 +55,7 @@ footer { } a { - color: #707070; + color: #505050; } a:hover { @@ -73,7 +73,7 @@ nav { } nav a { - color: #707070; + color: #505050; padding: 8px 12px; background-color: #fae7ce; border-radius: 12px; @@ -161,7 +161,7 @@ tr { .rounded-button { background-color: #fae7ce; /* Green background */ - color: #707070; /* White text */ + color: #505050; /* White text */ padding: 15px 32px; /* Some padding */ font-size: 1em; font-weight: 800; @@ -193,7 +193,7 @@ tr { } .mybox { - color: #707070; + color: #505050; padding: 8px 12px; background-color: #fae7ce; border-radius: 12px; @@ -260,6 +260,11 @@ tr { font-size: 1.2em; } +.very-large { + font-family: "Montserrat-SemiBold"; + font-size: 1.4em; +} + @media screen and (max-width: 40em) { .large { font-size: 0.9em; @@ -278,7 +283,7 @@ tr { .info { font-family: "Montserrat-SemiBold"; font-size: 0.9em; - color: #707070; + color: #505050; } .small { @@ -286,7 +291,7 @@ tr { } .minor-info { - color: #707070; + color: #505050; font-size: 0.85em; } @@ -362,7 +367,7 @@ tr { .separator { height: 1px; - background-color: #707070; + background-color: #505050; margin: 5px 0px; } @@ -608,12 +613,126 @@ h-margin { padding: 5px 0px; } -.table-row-4-colums-tournament { +.table-row-5-colums-tournament { display: grid; - grid-template-columns: auto 1fr auto auto; + grid-template-columns: 75px 90px 1fr 120px; align-items: center; - /* Vertically center the content within each column */ - padding: 5px 0px; + gap: 4px; +} + +.very-large.club-name { + font-size: 1.2em; +} + +.table-row-5-colums-tournament.header { + grid-template-columns: 1fr auto; /* Override to just 2 columns for header */ + justify-content: space-between; +} + +@media screen and (max-width: 800px) { + /* Adjust breakpoint as needed */ + .table-row-5-colums-tournament { + grid-template-columns: 60px 70px 1fr 80px; + gap: 2px; + } + + .small { + font-size: 1em; + } + + .very-large { + font-size: 1.4em; + } + + .very-large.club-name { + font-size: 1.2em; + } +} + +@media screen and (max-width: 400px) { + /* Adjust breakpoint as needed */ + .table-row-5-colums-tournament { + grid-template-columns: 55px 65px 1fr 75px; + gap: 2px; + } + + .small { + font-size: 0.9em; + } + + .very-large { + font-size: 1.3em; + } + + .very-large.club-name { + font-size: 1em; + } +} + +.light-green { + background-color: #90ee90 !important; +} + +.light-yellow { + background-color: #fed300 !important; +} + +.light-orange { + color: white !important; + background-color: #f39200 !important; +} + +.light-red { + background-color: #e84039 !important; + color: white !important; +} + +.table-row-element { + width: 100%; + line-height: 1.2; + padding: 8px 8px; + align-items: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; /* Prevents text from wrapping to a new line */ + max-width: 100%; /* Ensures children don't overflow */ +} + +.table-row-element.tournament-date { + grid-column: 1; + color: #505050; + background-color: #fae7ce; + border-radius: 12px; +} + +.table-row-element.tournament-type { + grid-column: 2; +} + +.table-row-element.tournament-name { + grid-column: 3; + align-self: center; /* Align in grid cell vertically */ + margin: auto 0; /* Alternative vertical centering */ +} + +.very-large.club-name { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; /* Limit to 2 lines */ + -webkit-box-orient: vertical; + white-space: normal; + /* Keep any existing styling for .large */ +} + +.table-row-element.tournament-status { + grid-column: 4; +} + +.box { + color: #505050; + border-radius: 12px; + padding: 4px; } .table-row-6-colums-club-tournament { @@ -867,7 +986,7 @@ h-margin { .match-result a:hover { background-color: #fae7ce; - color: #707070; + color: #505050; } .group-stage-link { diff --git a/tournaments/static/tournaments/css/tournament_bracket.css b/tournaments/static/tournaments/css/tournament_bracket.css index ed1a1a4..5593b51 100644 --- a/tournaments/static/tournaments/css/tournament_bracket.css +++ b/tournaments/static/tournaments/css/tournament_bracket.css @@ -58,7 +58,7 @@ } .round-name { - color: #707070; + color: #505050; font-size: 1.5em; padding: 8px 12px; white-space: nowrap; /* Prevent text wrapping */ @@ -67,7 +67,7 @@ .round-format { font-size: 0.9em; - color: #707070; + color: #505050; margin-top: -5px; /* Reduced from -10px to bring it closer */ white-space: nowrap; /* Prevent text wrapping */ display: block; /* Ensure proper centering */ @@ -199,7 +199,7 @@ .broadcast-mode .round-name, .broadcast-mode .round-format { padding: 0px; - color: #707070; + color: #505050; } .broadcast-mode .round-title { @@ -215,7 +215,7 @@ .outgoing-line, .outgoing-line-upward, .outgoing-line-downward { - background-color: #707070 !important; /* Bright yellow - change to your preferred color */ + background-color: #505050 !important; /* Bright yellow - change to your preferred color */ } /* Broadcast mode styling for all lines */ diff --git a/tournaments/templates/registration/my_tournaments.html b/tournaments/templates/registration/my_tournaments.html index 555b8e2..631fcf1 100644 --- a/tournaments/templates/registration/my_tournaments.html +++ b/tournaments/templates/registration/my_tournaments.html @@ -11,8 +11,8 @@ {% load tz %}
-
-
+
+
{% if upcoming_tournaments %} {% for tournament in upcoming_tournaments %} @@ -23,8 +23,8 @@ {% endif %}
-
-
+
+
{% if running_tournaments %} {% for tournament in running_tournaments %} @@ -36,8 +36,8 @@
-
-
+
+
{% if ended_tournaments %} {% for tournament in ended_tournaments %} diff --git a/tournaments/templates/tournaments/tournament_row.html b/tournaments/templates/tournaments/tournament_row.html index 199c25f..e9eba85 100644 --- a/tournaments/templates/tournaments/tournament_row.html +++ b/tournaments/templates/tournaments/tournament_row.html @@ -1,43 +1,56 @@ - -
-
-
{{ tournament.level }}
-
{{ tournament.category }}
- {% if tournament.age %} -
{{ tournament.age }}
+{% load tournament_tags %} +{% with status=tournament.get_online_registration_status %} +
+ + + {% if not forloop.last %} +
+ {% endif %} +{% endwith %} diff --git a/tournaments/templates/tournaments/tournaments.html b/tournaments/templates/tournaments/tournaments.html index d55463b..96240ab 100644 --- a/tournaments/templates/tournaments/tournaments.html +++ b/tournaments/templates/tournaments/tournaments.html @@ -15,48 +15,51 @@
{% if live or future %} -
+
-
+
{% if live %} - +
+ + {% if live|length >= 10 %} +
+ {% if club %} + tout voir + {% else %} + tout voir + {% endif %} +
+ {% endif %} +
+ {% for tournament in live %} {% include 'tournaments/tournament_row.html' %} {% endfor %} - {% if live|length >= 10 %} -
- {% if club %} - Voir tous... - {% else %} - Voir tous... - {% endif %} -
- {% endif %} - {% endif %} {% if future %} - +
+ + {% if future|length >= 10 %} +
+ {% if club %} + tout voir + {% else %} + tout voir + {% endif %} +
+ {% endif %} +
{% for tournament in future %} {% include 'tournaments/tournament_row.html' %} {% endfor %} - {% if future|length >= 10 %} -
- {% if club %} - Voir tous... - {% else %} - Voir tous... - {% endif %} -
- {% endif %} - {% endif %}
@@ -65,25 +68,25 @@ {% endif %} {% if ended %} -
-
- - - +
+
+ +
+ + {% if ended|length >= 10 %} +
+ {% if club %} + tout voir + {% else %} + tout voir + {% endif %} +
+ {% endif %} +
{% for tournament in ended %} {% include 'tournaments/tournament_row.html' %} {% endfor %} - {% if ended|length >= 10 %} -
- {% if club %} - Voir tous... - {% else %} - Voir tous... - {% endif %} -
- {% endif %} -
{% endif %} diff --git a/tournaments/templates/tournaments/tournaments_list.html b/tournaments/templates/tournaments/tournaments_list.html index 1e0d7d5..8258f5c 100644 --- a/tournaments/templates/tournaments/tournaments_list.html +++ b/tournaments/templates/tournaments/tournaments_list.html @@ -11,9 +11,9 @@
{% if tournaments %} -
+
-
+
{% for tournament in tournaments %} {% include 'tournaments/tournament_row.html' %} diff --git a/tournaments/templatetags/__init__.py b/tournaments/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tournaments/templatetags/tournament_tags.py b/tournaments/templatetags/tournament_tags.py new file mode 100644 index 0000000..072fd07 --- /dev/null +++ b/tournaments/templatetags/tournament_tags.py @@ -0,0 +1,7 @@ +from django import template + +register = template.Library() + +@register.filter +def get_player_status(tournament, user): + return tournament.get_player_registration_status_by_licence(user) From 5d5aba938b48d778a45f50f5210aef8ce57d6445 Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 1 Apr 2025 10:42:52 +0200 Subject: [PATCH 06/12] new style list of tournaments --- tournaments/models/tournament.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index b117f62..f787a80 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1129,6 +1129,8 @@ class Tournament(BaseModel): return OnlineRegistrationStatus.CANCELED if self.end_date is not None: return OnlineRegistrationStatus.ENDED_WITH_RESULTS + if self.enable_online_registration is False: + return OnlineRegistrationStatus.NOT_ENABLED if self.supposedly_in_progress(): return OnlineRegistrationStatus.ENDED if self.closed_registration_date is not None: From 42cf4abbd5c5d8dd7c33a58a590c8f1ac469031d Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 1 Apr 2025 10:53:02 +0200 Subject: [PATCH 07/12] new style list of tournaments --- tournaments/static/tournaments/css/style.css | 8 +++ .../templates/tournaments/tournaments.html | 54 ++++++++++--------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index 9409d48..93e177f 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -629,6 +629,14 @@ h-margin { justify-content: space-between; } +.table-row-5-colums-tournament.footer { + grid-template-columns: 1fr; /* Override to just 2 columns for header */ + text-align: center; /* Center the text content */ + width: 100%; + color: gray; + text-decoration: underline !important; /* Ensures the link is underlined */ +} + @media screen and (max-width: 800px) { /* Adjust breakpoint as needed */ .table-row-5-colums-tournament { diff --git a/tournaments/templates/tournaments/tournaments.html b/tournaments/templates/tournaments/tournaments.html index 96240ab..5748381 100644 --- a/tournaments/templates/tournaments/tournaments.html +++ b/tournaments/templates/tournaments/tournaments.html @@ -23,43 +23,48 @@
+
+ + + {% for tournament in live %} + {% include 'tournaments/tournament_row.html' %} + {% endfor %} + + {% endif %} From 6a257062df01240d7bc02557fdfe1d79705d9fd7 Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 1 Apr 2025 10:54:14 +0200 Subject: [PATCH 08/12] new style list of tournaments --- tournaments/templates/tournaments/tournaments.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tournaments/templates/tournaments/tournaments.html b/tournaments/templates/tournaments/tournaments.html index 5748381..95e6722 100644 --- a/tournaments/templates/tournaments/tournaments.html +++ b/tournaments/templates/tournaments/tournaments.html @@ -54,7 +54,7 @@ {% include 'tournaments/tournament_row.html' %} {% endfor %}