Laurent 7 months ago
commit f1cfabcbe6
  1. 6
      shop/static/shop/css/shop.css
  2. 2
      shop/templates/shop/product_item.html
  3. 14
      tournaments/custom_views.py
  4. 49
      tournaments/forms.py
  5. 18
      tournaments/migrations/0113_tournament_team_count_limit.py
  6. 51
      tournaments/models/enums.py
  7. 51
      tournaments/models/player_registration.py
  8. 10
      tournaments/models/team_registration.py
  9. 236
      tournaments/models/tournament.py
  10. 8
      tournaments/services/email_service.py
  11. 13
      tournaments/services/tournament_registration.py
  12. 19
      tournaments/signals.py
  13. 153
      tournaments/static/tournaments/css/style.css
  14. 8
      tournaments/static/tournaments/css/tournament_bracket.css
  15. 51
      tournaments/templates/profile.html
  16. 47
      tournaments/templates/register_tournament.html
  17. 4
      tournaments/templates/registration/login.html
  18. 33
      tournaments/templates/registration/my_tournaments.html
  19. 4
      tournaments/templates/registration/password_reset_complete.html
  20. 18
      tournaments/templates/registration/password_reset_confirm.html
  21. 4
      tournaments/templates/registration/password_reset_done.html
  22. 17
      tournaments/templates/registration/password_reset_form.html
  23. 21
      tournaments/templates/registration/signup.html
  24. 9
      tournaments/templates/tournaments/tournament_info.html
  25. 59
      tournaments/templates/tournaments/tournament_row.html
  26. 43
      tournaments/templates/tournaments/tournaments.html
  27. 4
      tournaments/templates/tournaments/tournaments_list.html
  28. 0
      tournaments/templatetags/__init__.py
  29. 7
      tournaments/templatetags/tournament_tags.py
  30. 12
      tournaments/urls.py
  31. 53
      tournaments/views.py

@ -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;

@ -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';

@ -10,12 +10,20 @@ 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:
# 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
@ -24,5 +32,11 @@ class CustomLoginView(auth_views.LoginView):
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)

@ -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']

@ -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),
),
]

@ -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'

@ -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

@ -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):

@ -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'
@ -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
@ -230,36 +231,12 @@ class Tournament(BaseModel):
else:
return None
def tournament_status_display(self):
if self.is_canceled() is True:
return "Annulé"
def get_tournament_status(self):
return self.get_online_registration_status().status_localized()
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
registration_status = None
if self.enable_online_registration == True:
registration_status = self.get_online_registration_status().status_localized()
if teams is not None and len(teams) > 0:
word = "inscription"
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}"
else:
if registration_status is not None:
return f"{registration_status}"
return None
def get_tournament_status_team_count(self):
active_teams_count = self.team_registrations.filter(walk_out=False).count()
return min(active_teams_count, self.team_count)
def name_and_event(self):
event_name = None
@ -331,9 +308,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
@ -397,7 +374,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 +1028,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,13 +1102,23 @@ 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.is_canceled():
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:
return OnlineRegistrationStatus.ENDED
if self.end_date is not None:
return OnlineRegistrationStatus.ENDED_WITH_RESULTS
return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE
now = timezone.now()
@ -1140,9 +1130,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_count is not None:
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:
@ -1153,6 +1146,21 @@ class Tournament(BaseModel):
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():
@ -1174,8 +1182,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 +1271,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 +1303,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,7 +1468,6 @@ 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
if self.event and self.event.creator:
return self.event.creator.full_name()
@ -1427,17 +1476,96 @@ class Tournament(BaseModel):
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
@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
@ -1489,6 +1617,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()

@ -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_sorting == 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"

@ -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()

@ -113,21 +113,16 @@ def check_waiting_list(sender, instance, **kwargs):
teams_out_to_warn = []
teams_in_to_warn = []
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: (
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(True)[(instance.team_count - previous_state.team_count):]
team for team in teams_that_will_be_in
if team.stage == "Attente"
]

@ -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,134 @@ 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;
}
.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 {
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 +994,7 @@ h-margin {
.match-result a:hover {
background-color: #fae7ce;
color: #707070;
color: #505050;
}
.group-stage-link {

@ -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 */

@ -20,27 +20,34 @@
{% load static %}
{% load tz %}
{% if form.errors or password_change_form.errors %}
<div class="cell medium-6 large-6 topblock my-block">
<div class="bubble">
<label class="title">Mes informations</label>
{% if form.errors %}
<div class="alert">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<p>{{ error }}</p>
<div>
{% for field in form %}
{% if field.errors %}
{% for error in field.errors %}
<div class="alert">{{ field.label }} : {{ error }}</div>
{% endfor %}
{% endif %}
{% endfor %}
</div>
<div>
{% for field in password_change_form %}
{% if field.errors %}
{% for error in field.errors %}
<div class="alert">{{ field.label }} : {{ error }}</div>
{% endfor %}
{% endif %}
<!-- Add non-field errors (if any) -->
{% if form.non_field_errors %}
<div class="alert">
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<div class="cell medium-6 large-6 topblock my-block">
<div class="bubble">
<label class="title">Mes informations</label>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
@ -51,25 +58,7 @@
<div class="cell medium-6 large-6 topblock my-block">
<div class="bubble">
<label class="title">Mot de passe</label>
{% if password_change_form.errors %}
<div class="alert">
{% for field, errors in password_change_form.errors.items %}
{% for error in errors %}
<p>{{ error }}</p>
{% endfor %}
{% endfor %}
</div>
{% endif %}
{% if password_change_form.non_field_errors %}
<div class="alert">
{% for error in password_change_form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="post" action="{% url 'password_change' %}">
<form method="post" action="{% url 'custom_password_change' %}">
{% csrf_token %}
{{ password_change_form.as_p }}
<button type="submit" class="rounded-button">Modifier le mot de passe</button>

@ -26,6 +26,29 @@
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.
</p>
{% else %}
{% if team_form.errors %}
<div>
{% for field in team_form %}
{% if field.errors %}
{% for error in field.errors %}
<div class="alert">{{ field.label }} : {{ error }}</div>
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% endif %}
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
@ -36,6 +59,12 @@
Informations de contact
</div>
</p>
{% if team_form.non_field_errors %}
{% for error in team_form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
{% endif %}
{{ team_form.as_p }} <!-- Render team registration form fields here -->
</div>
@ -108,24 +137,6 @@
{% endif %}
{% endif %}
<div class="margin10">
{% if form.errors %}
<div class="alert alert-error">
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
{% endif %}
{% for field in form %}
{% for error in field.errors %}
<p>{{ error }}</p>
{% endfor %}
{% endfor %}
</div>
{% endif %}
</div>
<button type="submit" name="add_player" class="rounded-button">
{% if add_player_form.user_without_licence %}
Confirmer

@ -11,7 +11,7 @@
<div class="grid-x">
<div class="bubble">
<div class="cell medium-6 large-6 my-block">
{% if form.errors %}
{% if form.non_field_errors %}
<div class="alert alert-error">
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
@ -28,7 +28,9 @@
{% endif %}
<form method="post" action="{% url 'custom-login' %}">
{% csrf_token %}
{% if request.GET.next and 'reset' not in request.GET.next and 'password_reset' not in request.GET.next %}
<input type="hidden" name="next" value="{{ request.GET.next }}">
{% endif %}
<label for="username">Identifiant ou e-mail </label>
<input type="text" name="username" id="username" required>

@ -11,20 +11,8 @@
{% load tz %}
<div class="grid-x">
<div class="cell medium-6 large-6 topblock my-block">
<div class="bubble">
<label class="title">Vos tournois à venir</label>
{% if upcoming_tournaments %}
{% for tournament in upcoming_tournaments %}
{% include 'tournaments/tournament_row.html' %}
{% endfor %}
{% else %}
Aucun tournoi à venir
{% endif %}
</div>
</div>
<div class="cell medium-6 large-6 topblock my-block">
<div class="bubble">
<div class="cell medium-10 large-5 topblock my-block">
<div>
<label class="title">Vos tournois en cours</label>
{% if running_tournaments %}
{% for tournament in running_tournaments %}
@ -35,9 +23,22 @@
{% endif %}
</div>
<div>
<label class="title">Vos tournois à venir</label>
{% if upcoming_tournaments %}
{% for tournament in upcoming_tournaments %}
{% include 'tournaments/tournament_row.html' %}
{% endfor %}
{% else %}
Aucun tournoi à venir
{% endif %}
</div>
</div>
<div class="cell medium-6 large-6 topblock my-block">
<div class="bubble">
<div class="cell large-offset-1 medium-10 large-5 topblock my-block">
<div>
<label class="title">Vos tournois terminés</label>
{% if ended_tournaments %}
{% for tournament in ended_tournaments %}

@ -10,9 +10,9 @@
<p>
Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter avec votre nouveau mot de passe.
</p>
<p>
<div class="topmargin20">
<a href="{% url 'login' %}" class="rounded-button">Se connecter</a>
</p>
</div>
</div>
</div>
</div>

@ -7,17 +7,6 @@
<div class="grid-x">
<div class="bubble">
<div class="cell medium-6 large-6 my-block">
{% if form.errors %}
<div class="alert">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<p>{{ error }}</p>
{% endfor %}
{% endfor %}
</div>
{% endif %}
<!-- Add non-field errors (if any) -->
{% if form.non_field_errors %}
<div class="alert">
{% for error in form.non_field_errors %}
@ -35,13 +24,10 @@
<button type="submit" class="rounded-button">Réinitialiser le mot de passe</button>
</form>
<p>
<div class="topmargin20">
<a href="{% url 'login' %}" class="styled-link">Retour à la connexion</a>
</p>
</div>
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

@ -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.
</p>
<p>
<div class="topmargin20">
<a href="{% url 'login' %}" class="styled-link">Retour à la connexion</a>
</p>
</div>
</div>
</div>
</div>

@ -7,16 +7,6 @@
<div class="grid-x">
<div class="bubble">
<div class="cell medium-6 large-6 my-block">
{% if form.errors %}
<div class="alert">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<p>{{ error }}</p>
{% endfor %}
{% endfor %}
</div>
{% endif %}
<!-- Add non-field errors (if any) -->
{% if form.non_field_errors %}
<div class="alert">
@ -31,13 +21,10 @@
<input type="email" name="email" id="email" required>
<button type="submit" class="rounded-button">Envoyer le lien de réinitialisation</button>
</form>
<p>
<div class="topmargin20">
<a href="{% url 'login' %}" class="styled-link">Retour à la connexion</a>
</p>
</div>
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

@ -8,20 +8,25 @@
{% load static %}
{% load tz %}
<div class="grid-x">
<div class="bubble">
<div class="cell medium-6 large-6 my-block">
<div class="grid">
{% if form.errors %}
<div class="alert">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<p>{{ error }}</p>
<div class="cell medium-6 large-6 topblock my-block">
<div class="bubble">
<div>
{% for field in form %}
{% if field.errors %}
{% for error in field.errors %}
<div class="alert">{{ field.label }} : {{ error }}</div>
{% endfor %}
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Add non-field errors (if any) -->
<div class="cell medium-6 large-6 my-block">
<div class="bubble">
{% if form.non_field_errors %}
<div class="alert">
{% for error in form.non_field_errors %}

@ -47,12 +47,6 @@
{% if tournament.is_unregistration_possible %}
<p>
<div class="margin10">
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
<a href="{% url 'unregister_tournament' tournament.id %}"
class="rounded-button destructive-button"
onclick="return confirm('Êtes-vous sûr de vouloir vous désinscrire ?');">
@ -164,9 +158,6 @@
</p>
{% endif %}
{% else %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
<p>
<div>
<a href="{% url 'login' %}?next={{ request.path }}" class="styled-link">Connectez-vous !</a>

@ -1,43 +1,56 @@
{% load tournament_tags %}
{% with status=tournament.get_online_registration_status %}
<a href="{% url 'tournament' tournament.id %}">
<div class="table-row-4-colums-tournament vertical-padding">
<div class="tight table-cell">
<div class="large">{{ tournament.level }}</div>
<div class="table-row-5-colums-tournament vertical-padding">
<div class="table-row-element tournament-date center">
<div class="small">{{ tournament.week_day }}</div>
<div class="very-large">{{ tournament.day }}</div>
<div class="small">{{ tournament.month }}</div>
</div>
<div class="table-row-element tournament-type">
<div class="very-large">{{ tournament.level }}</div>
{% if tournament.category %}
<div class="small">{{ tournament.category }}</div>
{% endif %}
{% if tournament.age %}
<div class="small">{{ tournament.age }}</div>
{% endif %}
</div>
<div class="table-cell-responsive-large table-cell-large horizontal-padding semibold">
<div><span>{{ tournament.event.club.name }}</span></div>
{% if tournament.name_and_event %}
<div>
<span>{{ tournament.name_and_event }}</span>
<div class="table-row-element tournament-name">
<div class="very-large club-name">{{ tournament.event.club.name }}</div>
<div class="small">{{ tournament.localized_day_duration }}</div>
</div>
<div class="table-row-element tournament-status center">
<div class="small">Équipes</div>
<div class="very-large">{{ tournament.get_tournament_status_team_count }}</div>
{% if user.is_authenticated %}
{% with user_registration_status=tournament|get_player_status:user %}
{% if user_registration_status %}
{% if user_registration_status.display_box %}
<div class="box small {{ user_registration_status.box_class }}">
{{ user_registration_status.short_label }}
</div>
{% endif %}
{% else %}
{% if status.display_box %}
<div class="box small {{ status.box_class }}">
{{ status.short_label }}
</div>
<div class="table-cell-responsive-short table-cell-large horizontal-padding semibold">
<div><span>{{ tournament.event.club.computedShortTitle }}</span></div>
{% if tournament.event.name %}
<div><span>{{ tournament.event.name }}</span></div>
{% endif %}
{% if tournament.name %}
<div><span>{{ tournament.name }}</span></div>
{% endif %}
</div>
{% if tournament.tournament_status_display %}
<div class="table-cell-responsive-large right horizontal-padding">
{{ tournament.tournament_status_display|linebreaksbr }}
{% endwith %}
{% else %}
{% if status.display_box %}
<div class="box small {{ status.box_class }}">
{{ status.short_label }}
</div>
{% endif %}
<div class="table-cell">
<div class="mybox center">{{ tournament.formatted_start_date }}</div>
{% if tournament.tournament_status_display %}
<div class="table-cell-responsive-short small center">{{ tournament.tournament_status_display|linebreaksbr }}</div>
{% endif %}
</div>
</div>
</a>
{% if not forloop.last %}
<hr/>
{% endif %}
{% endwith %}

@ -15,47 +15,55 @@
<div class="grid-x">
{% if live or future %}
<div class="cell medium-6 large-6 topblock my-block">
<div class="cell medium-10 large-5 topblock my-block">
<div class="bubble">
<div>
{% if live %}
<div class="table-row-5-colums-tournament header">
<label class="title">En cours</label>
</div>
{% for tournament in live %}
{% include 'tournaments/tournament_row.html' %}
{% endfor %}
<div class="table-row-5-colums-tournament footer">
{% if live|length >= 10 %}
<div class="right">
<div class="small">
{% if club %}
<a href="{% url 'tournaments' %}?filter=1&club={{ club.id }}">Voir tous...</a>
<a href="{% url 'tournaments' %}?filter=1&club={{ club.id }}">voir tous les tournois en cours</a>
{% else %}
<a href="{% url 'tournaments' %}?filter=1">Voir tous...</a>
<a href="{% url 'tournaments' %}?filter=1">voir tous les tournois en cours</a>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
{% if future %}
<div class="table-row-5-colums-tournament header">
<label class="title">À venir</label>
</div>
{% for tournament in future %}
{% include 'tournaments/tournament_row.html' %}
{% endfor %}
<div class="table-row-5-colums-tournament footer">
{% if future|length >= 10 %}
<div class="right">
<div class="small">
{% if club %}
<a href="{% url 'tournaments' %}?filter=0&club={{ club.id }}">Voir tous...</a>
<a href="{% url 'tournaments' %}?filter=0&club={{ club.id }}">voir tous les tournois à venir</a>
{% else %}
<a href="{% url 'tournaments' %}?filter=0">Voir tous...</a>
<a href="{% url 'tournaments' %}?filter=0">voir tous les tournois à venir</a>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
@ -65,25 +73,26 @@
{% endif %}
{% if ended %}
<div class="cell medium-6 large-6 topblock my-block">
<div class="bubble">
<div class="cell large-offset-1 medium-10 large-5 topblock my-block">
<div>
<div class="table-row-5-colums-tournament header">
<label class="title">Terminés</label>
</div>
{% for tournament in ended %}
{% include 'tournaments/tournament_row.html' %}
{% endfor %}
<div class="table-row-5-colums-tournament footer">
{% if ended|length >= 10 %}
<div class="right">
<div class="small">
{% if club %}
<a href="{% url 'tournaments' %}?filter=2&club={{ club.id }}">Voir tous...</a>
<a href="{% url 'tournaments' %}?filter=2&club={{ club.id }}">voir tous les tournois terminés</a>
{% else %}
<a href="{% url 'tournaments' %}?filter=2">Voir tous...</a>
<a href="{% url 'tournaments' %}?filter=2">voir tous les tournois terminés</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}

@ -11,9 +11,9 @@
<div class="grid-x">
{% if tournaments %}
<div class="cell medium-6 large-6 topblock my-block">
<div class="cell medium-10 large-5 topblock my-block">
<div class="bubble">
<div>
{% for tournament in tournaments %}
{% include 'tournaments/tournament_row.html' %}

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

@ -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/<str:tournament_id>/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'),

@ -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

Loading…
Cancel
Save