commit
b983b933e7
@ -0,0 +1,17 @@ |
||||
# Generated by Django 5.1 on 2025-02-12 13:36 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('sync', '0001_initial'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterModelOptions( |
||||
name='modellog', |
||||
options={'ordering': ['date']}, |
||||
), |
||||
] |
||||
@ -0,0 +1,34 @@ |
||||
# backends.py |
||||
from django.contrib.auth import get_user_model |
||||
from django.contrib.auth.backends import ModelBackend |
||||
from django.db.models import Q |
||||
import logging |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
class EmailOrUsernameModelBackend(ModelBackend): |
||||
def authenticate(self, request, username=None, password=None, **kwargs): |
||||
UserModel = get_user_model() |
||||
|
||||
print(f"Backend attempting authentication for: {username}") # Debug print |
||||
logger.info(f"Backend attempting authentication for: {username}") |
||||
|
||||
try: |
||||
user = UserModel.objects.get( |
||||
Q(username__iexact=username) | Q(email__iexact=username) |
||||
) |
||||
print(f"User found: {user}") # Debug print |
||||
logger.info(f"User found: {user}") |
||||
|
||||
if user.check_password(password): |
||||
print("Password check successful") # Debug print |
||||
logger.info("Password check successful") |
||||
return user |
||||
print("Password check failed") # Debug print |
||||
logger.warning("Password check failed") |
||||
return None |
||||
|
||||
except UserModel.DoesNotExist: |
||||
print("User not found") # Debug print |
||||
logger.warning("User not found") |
||||
return None |
||||
@ -1,19 +1,257 @@ |
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm |
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordResetForm |
||||
from django import forms |
||||
from .models import CustomUser |
||||
import re # Import the re module for regular expressions |
||||
from .utils.licence_validator import LicenseValidator |
||||
from django.core.mail import send_mail |
||||
from django.template.loader import render_to_string |
||||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode |
||||
from django.contrib.auth.tokens import default_token_generator |
||||
from django.contrib.sites.shortcuts import get_current_site |
||||
from django.utils.encoding import force_bytes |
||||
|
||||
class CustomUserCreationForm(UserCreationForm): |
||||
|
||||
class Meta: |
||||
model = CustomUser |
||||
error_messages = { |
||||
'email': { |
||||
'unique': "Cette adresse email est déjà utilisée.", |
||||
}, |
||||
'username': { |
||||
'unique': "Ce nom d'utilisateur est déjà pris.", |
||||
}, |
||||
'licence_id': { |
||||
'unique': "Cette licence est déjà utilisée par un autre compte.", |
||||
}, |
||||
|
||||
} |
||||
|
||||
fields = UserCreationForm.Meta.fields + ('umpire_code', 'clubs', 'phone', 'first_name', 'last_name', 'licence_id', 'country') |
||||
|
||||
class SimpleCustomUserCreationForm(UserCreationForm): |
||||
|
||||
class Meta: |
||||
model = CustomUser |
||||
fields = UserCreationForm.Meta.fields + ('email', 'phone', 'first_name', 'last_name', 'licence_id', 'country') |
||||
error_messages = { |
||||
'email': { |
||||
'unique': "Cette adresse email est déjà utilisée.", |
||||
}, |
||||
'username': { |
||||
'unique': "Ce nom d'utilisateur est déjà pris.", |
||||
}, |
||||
'licence_id': { |
||||
'unique': "Cette licence est déjà utilisée par un autre compte.", |
||||
}, |
||||
|
||||
} |
||||
labels = { |
||||
'username': 'Nom d’utilisateur', |
||||
'email': 'E-mail', |
||||
'phone': 'Numéro de téléphone', |
||||
'first_name': 'Prénom', |
||||
'last_name': 'Nom de famille', |
||||
'licence_id': 'Numéro de licence', |
||||
'country': 'Pays', |
||||
'password1': 'Mot de passe', |
||||
'password2': 'Confirmer le mot de passe', |
||||
} |
||||
|
||||
|
||||
class CustomUserChangeForm(UserChangeForm): |
||||
|
||||
class Meta: |
||||
model = CustomUser |
||||
error_messages = { |
||||
'email': { |
||||
'unique': "Cette adresse email est déjà utilisée.", |
||||
}, |
||||
'username': { |
||||
'unique': "Ce nom d'utilisateur est déjà pris.", |
||||
}, |
||||
'licence_id': { |
||||
'unique': "Cette licence est déjà utilisée par un autre compte.", |
||||
}, |
||||
|
||||
} |
||||
fields = UserCreationForm.Meta.fields + ('umpire_code', 'clubs', 'phone', 'first_name', 'last_name', 'licence_id', 'country') |
||||
|
||||
class SimpleForm(forms.Form): |
||||
# A single field for user input (e.g., a text field) |
||||
name = forms.CharField(label='Enter your name!', max_length=100) |
||||
|
||||
class TournamentRegistrationForm(forms.Form): |
||||
#first_name = forms.CharField(label='Prénom', max_length=50) |
||||
#last_name = forms.CharField(label='Nom', max_length=50) |
||||
email = forms.EmailField(label='E-mail', widget=forms.EmailInput(attrs={'readonly': 'readonly'})) |
||||
mobile_number = forms.CharField( |
||||
label='Téléphone', |
||||
max_length=15, |
||||
required=True |
||||
) |
||||
|
||||
def clean_mobile_number(self): |
||||
mobile_number = self.cleaned_data.get('mobile_number') |
||||
if mobile_number: |
||||
# Basic regex for mobile numbers, matching common formats |
||||
# Remove spaces from the number first |
||||
mobile_number = mobile_number.replace(' ', '') |
||||
if not re.match(r"^\+?\d{10,15}$", mobile_number): |
||||
raise forms.ValidationError("Entrer un numéro de téléphone valide.") |
||||
return mobile_number |
||||
|
||||
class AddPlayerForm(forms.Form): |
||||
licence_id = forms.CharField(label='Numéro de licence (avec la lettre)', max_length=20, required=False) |
||||
first_name = forms.CharField(label='Prénom', min_length=2, max_length=50, required=False) |
||||
last_name = forms.CharField(label='Nom', min_length=2, max_length=50, required=False) |
||||
first_tournament = False |
||||
user_without_licence = False |
||||
|
||||
def names_is_valid(self): |
||||
first_name = self.cleaned_data.get('first_name') |
||||
last_name = self.cleaned_data.get('last_name') |
||||
return len(first_name) >= 2 and len(last_name) >= 2 |
||||
|
||||
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}") |
||||
|
||||
return licence_id |
||||
|
||||
def clean_last_name(self): |
||||
last_name = self.cleaned_data.get('last_name') |
||||
|
||||
# Convert to uppercase |
||||
last_name = last_name.upper() |
||||
|
||||
# Update the cleaned_data with the modified licence_id |
||||
self.cleaned_data['last_name'] = last_name |
||||
return last_name |
||||
|
||||
def clean_first_name(self): |
||||
first_name = self.cleaned_data.get('first_name') |
||||
|
||||
# Convert to capitalize |
||||
first_name = first_name.capitalize() |
||||
|
||||
# Update the cleaned_data with the modified licence_id |
||||
self.cleaned_data['first_name'] = first_name |
||||
return first_name |
||||
|
||||
class CustomPasswordResetForm(PasswordResetForm): |
||||
def save(self, *args, **kwargs): |
||||
""" |
||||
Override the save method to send a custom email. |
||||
""" |
||||
email = self.cleaned_data["email"] |
||||
users = self.get_users(email) |
||||
|
||||
for user in users: |
||||
# Generate the token for password reset |
||||
token = default_token_generator.make_token(user) |
||||
uid = urlsafe_base64_encode(force_bytes(user.pk)) |
||||
|
||||
# Prepare the context for the email template |
||||
context = { |
||||
"email": user.email, |
||||
"domain": get_current_site(self.request).domain, |
||||
"site_name": "Padel Club", |
||||
"uid": uid, |
||||
"token": token, |
||||
"protocol": "http", # Use 'https' in production |
||||
} |
||||
|
||||
# Render the email content from the template |
||||
subject = "Réinitialisation du mot de passe" |
||||
message = render_to_string("registration/password_reset_email.html", context) |
||||
|
||||
# Send the email |
||||
send_mail(subject, message, None, [user.email]) |
||||
|
||||
class ProfileUpdateForm(forms.ModelForm): |
||||
def __init__(self, *args, **kwargs): |
||||
super().__init__(*args, **kwargs) |
||||
# Remove autofocus from the 'username' field |
||||
self.fields['username'].widget.attrs.pop("autofocus", None) |
||||
|
||||
class Meta: |
||||
model = CustomUser |
||||
fields = ['first_name', 'last_name', 'licence_id', 'username', 'email', 'phone'] |
||||
labels = { |
||||
'username': 'Nom d’utilisateur', |
||||
'email': 'E-mail', |
||||
'phone': 'Numéro de téléphone', |
||||
'first_name': 'Prénom', |
||||
'last_name': 'Nom de famille', |
||||
'licence_id': 'Numéro de licence', |
||||
} |
||||
error_messages = { |
||||
'email': { |
||||
'unique': "Cette adresse email est déjà utilisée.", |
||||
}, |
||||
'username': { |
||||
'unique': "Ce nom d'utilisateur est déjà pris.", |
||||
}, |
||||
'licence_id': { |
||||
'unique': "Cette licence est déjà utilisée par un autre compte.", |
||||
}, |
||||
} |
||||
|
||||
from django.contrib.auth.forms import PasswordChangeForm |
||||
|
||||
class CustomPasswordChangeForm(PasswordChangeForm): |
||||
def __init__(self, *args, **kwargs): |
||||
super().__init__(*args, **kwargs) |
||||
# Remove autofocus from all fields in the PasswordChangeForm |
||||
for field in self.fields.values(): |
||||
field.widget.attrs.pop("autofocus", None) |
||||
|
||||
from django.contrib.auth.forms import AuthenticationForm |
||||
from django.contrib.auth import authenticate # Add this import |
||||
from django import forms |
||||
import logging |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
class EmailOrUsernameAuthenticationForm(AuthenticationForm): |
||||
username = forms.CharField(label='Username or Email') |
||||
|
||||
def clean(self): |
||||
username = self.cleaned_data.get('username') |
||||
password = self.cleaned_data.get('password') |
||||
|
||||
print(f"Login attempt with username/email: {username}") # Debug print |
||||
logger.info(f"Login attempt with username/email: {username}") |
||||
|
||||
if username and password: |
||||
self.user_cache = authenticate( |
||||
self.request, |
||||
username=username, |
||||
password=password |
||||
) |
||||
|
||||
print(f"Authentication result: {self.user_cache}") # Debug print |
||||
logger.info(f"Authentication result: {self.user_cache}") |
||||
|
||||
if self.user_cache is None: |
||||
print("Authentication failed") # Debug print |
||||
logger.warning("Authentication failed") |
||||
raise forms.ValidationError( |
||||
"Identifiant/E-mail ou mot de passe incorrect. Les champs sont sensibles à la casse.", |
||||
code='invalid_login' |
||||
) |
||||
else: |
||||
print(f"Authentication successful for user: {self.user_cache}") # Debug print |
||||
logger.info(f"Authentication successful for user: {self.user_cache}") |
||||
self.confirm_login_allowed(self.user_cache) |
||||
|
||||
return self.cleaned_data |
||||
|
||||
@ -0,0 +1,18 @@ |
||||
# Generated by Django 4.2.11 on 2024-11-15 07:51 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0093_drawlog_draw_type_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='playerregistration', |
||||
name='captain', |
||||
field=models.BooleanField(default=False), |
||||
), |
||||
] |
||||
@ -0,0 +1,14 @@ |
||||
# Generated by Django 4.2.11 on 2024-11-29 11:43 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0094_alter_customuser_bracket_match_format_preference_and_more'), |
||||
('tournaments', '0094_playerregistration_captain'), |
||||
] |
||||
|
||||
operations = [ |
||||
] |
||||
@ -1,41 +0,0 @@ |
||||
# Generated by Django 5.1 on 2024-12-13 14:18 |
||||
|
||||
import django.db.models.deletion |
||||
import django.utils.timezone |
||||
from django.conf import settings |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0095_club_creation_date_club_last_update_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='drawlog', |
||||
name='creation_date', |
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False), |
||||
), |
||||
migrations.AddField( |
||||
model_name='drawlog', |
||||
name='last_update', |
||||
field=models.DateTimeField(default=django.utils.timezone.now), |
||||
), |
||||
migrations.AddField( |
||||
model_name='drawlog', |
||||
name='last_updated_by', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL), |
||||
), |
||||
migrations.AddField( |
||||
model_name='drawlog', |
||||
name='related_user', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL), |
||||
), |
||||
migrations.AddField( |
||||
model_name='drawlog', |
||||
name='store_id', |
||||
field=models.CharField(default='', max_length=100), |
||||
), |
||||
] |
||||
@ -0,0 +1,38 @@ |
||||
# Generated by Django 4.2.11 on 2024-11-29 11:43 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0095_merge_20241129_1243'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='account_is_required', |
||||
field=models.BooleanField(default=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='license_is_required', |
||||
field=models.BooleanField(default=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='maximum_player_per_team', |
||||
field=models.IntegerField(default=2), |
||||
), |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='minimum_player_per_team', |
||||
field=models.IntegerField(default=2), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='playerregistration', |
||||
name='source', |
||||
field=models.IntegerField(blank=True, choices=[(0, 'French Federation'), (1, 'Beach Padel'), (2, 'Online Registration')], null=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,23 @@ |
||||
# Generated by Django 4.2.11 on 2024-11-29 13:42 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0096_tournament_account_is_required_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='display_entry_fee_information', |
||||
field=models.BooleanField(default=False), |
||||
), |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='information', |
||||
field=models.CharField(blank=True, max_length=4000, null=True), |
||||
), |
||||
] |
||||
@ -1,19 +0,0 @@ |
||||
# Generated by Django 5.1 on 2024-12-16 09:02 |
||||
|
||||
import django.db.models.deletion |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0097_special_store_id'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterField( |
||||
model_name='drawlog', |
||||
name='tournament', |
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='draw_logs', to='tournaments.tournament'), |
||||
), |
||||
] |
||||
@ -0,0 +1,38 @@ |
||||
# Generated by Django 4.2.11 on 2024-11-30 09:18 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0097_tournament_display_entry_fee_information_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='enable_online_registration', |
||||
field=models.BooleanField(default=False), |
||||
), |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='opening_registration_date', |
||||
field=models.DateTimeField(blank=True, null=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='registration_date_limit', |
||||
field=models.DateTimeField(blank=True, null=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='target_team_count', |
||||
field=models.IntegerField(blank=True, null=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='waiting_list_limit', |
||||
field=models.IntegerField(blank=True, null=True), |
||||
), |
||||
] |
||||
@ -1,79 +0,0 @@ |
||||
# Generated by Django 5.1 on 2025-01-28 14:54 |
||||
|
||||
import django.db.models.deletion |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0098_alter_drawlog_tournament'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterField( |
||||
model_name='court', |
||||
name='club', |
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='courts', to='tournaments.club'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='dateinterval', |
||||
name='event', |
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='date_intervals', to='tournaments.event'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='drawlog', |
||||
name='tournament', |
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='draw_logs', to='tournaments.tournament'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='groupstage', |
||||
name='tournament', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='group_stages', to='tournaments.tournament'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='match', |
||||
name='group_stage', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='matches', to='tournaments.groupstage'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='match', |
||||
name='round', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='matches', to='tournaments.round'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='playerregistration', |
||||
name='team_registration', |
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='player_registrations', to='tournaments.teamregistration'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='round', |
||||
name='parent', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='tournaments.round'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='round', |
||||
name='tournament', |
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rounds', to='tournaments.tournament'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='teamregistration', |
||||
name='tournament', |
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team_registrations', to='tournaments.tournament'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='teamscore', |
||||
name='match', |
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team_scores', to='tournaments.match'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='teamscore', |
||||
name='team_registration', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team_scores', to='tournaments.teamregistration'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='tournament', |
||||
name='event', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tournaments', to='tournaments.event'), |
||||
), |
||||
] |
||||
@ -0,0 +1,17 @@ |
||||
# Generated by Django 4.2.11 on 2024-11-30 10:08 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0098_tournament_enable_online_registration_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RemoveField( |
||||
model_name='tournament', |
||||
name='display_entry_fee_information', |
||||
), |
||||
] |
||||
@ -0,0 +1,28 @@ |
||||
# Generated by Django 4.2.11 on 2024-12-10 08:14 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0099_remove_tournament_display_entry_fee_information'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='playerregistration', |
||||
name='coach', |
||||
field=models.BooleanField(default=False), |
||||
), |
||||
migrations.AddField( |
||||
model_name='teamregistration', |
||||
name='unregistered', |
||||
field=models.BooleanField(default=False), |
||||
), |
||||
migrations.AddField( |
||||
model_name='teamregistration', |
||||
name='unregistration_date', |
||||
field=models.DateTimeField(blank=True, null=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,38 @@ |
||||
# Generated by Django 4.2.11 on 2024-12-16 08:57 |
||||
|
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
import uuid |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0100_playerregistration_coach_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='UnregisteredTeam', |
||||
fields=[ |
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), |
||||
('unregistration_date', models.DateTimeField(blank=True, null=True)), |
||||
('comment', models.CharField(blank=True, max_length=200, null=True)), |
||||
('source', models.CharField(blank=True, max_length=20, null=True)), |
||||
('source_value', models.CharField(blank=True, max_length=200, null=True)), |
||||
('reason', models.IntegerField(blank=True, null=True)), |
||||
('tournament', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tournaments.tournament')), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='UnregisteredPlayer', |
||||
fields=[ |
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), |
||||
('first_name', models.CharField(blank=True, max_length=50)), |
||||
('last_name', models.CharField(blank=True, max_length=50)), |
||||
('licence_id', models.CharField(blank=True, max_length=50, null=True)), |
||||
('reason', models.IntegerField(blank=True, null=True)), |
||||
('unregistered_team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tournaments.unregisteredteam')), |
||||
], |
||||
), |
||||
] |
||||
@ -0,0 +1,21 @@ |
||||
# Generated by Django 4.2.11 on 2024-12-16 13:10 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0101_unregisteredteam_unregisteredplayer'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RemoveField( |
||||
model_name='teamregistration', |
||||
name='unregistered', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='teamregistration', |
||||
name='unregistration_date', |
||||
), |
||||
] |
||||
@ -0,0 +1,33 @@ |
||||
# Generated by Django 4.2.11 on 2024-12-17 14:14 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0102_remove_teamregistration_unregistered_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RemoveField( |
||||
model_name='unregisteredplayer', |
||||
name='reason', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='unregisteredteam', |
||||
name='comment', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='unregisteredteam', |
||||
name='reason', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='unregisteredteam', |
||||
name='source', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='unregisteredteam', |
||||
name='source_value', |
||||
), |
||||
] |
||||
@ -0,0 +1,17 @@ |
||||
# Generated by Django 4.2.11 on 2024-12-20 13:33 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0103_remove_unregisteredplayer_reason_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RemoveField( |
||||
model_name='tournament', |
||||
name='target_team_count', |
||||
), |
||||
] |
||||
@ -0,0 +1,23 @@ |
||||
# Generated by Django 4.2.11 on 2025-01-15 07:49 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0104_remove_tournament_target_team_count'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='playerregistration', |
||||
name='registered_online', |
||||
field=models.BooleanField(default=False), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='playerregistration', |
||||
name='source', |
||||
field=models.IntegerField(blank=True, choices=[(0, 'French Federation'), (1, 'Beach Padel')], null=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,18 @@ |
||||
# Generated by Django 4.2.11 on 2025-01-15 07:53 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0105_playerregistration_registered_online_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterField( |
||||
model_name='customuser', |
||||
name='licence_id', |
||||
field=models.CharField(blank=True, max_length=10, null=True, unique=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,18 @@ |
||||
# Generated by Django 5.1 on 2025-01-28 07:16 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0106_alter_customuser_licence_id'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='customuser', |
||||
name='origin', |
||||
field=models.IntegerField(blank=True, choices=[(0, 'Admin'), (1, 'Site'), (2, 'App')], default=0, null=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,25 @@ |
||||
from django.db import models |
||||
from . import UnregisteredTeam |
||||
import uuid |
||||
|
||||
class UnregisteredPlayer(models.Model): |
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) |
||||
unregistered_team = models.ForeignKey(UnregisteredTeam, on_delete=models.SET_NULL, related_name='unregistered_players', null=True, blank=True) |
||||
first_name = models.CharField(max_length=50, blank=True) |
||||
last_name = models.CharField(max_length=50, blank=True) |
||||
licence_id = models.CharField(max_length=50, null=True, blank=True) |
||||
|
||||
def __str__(self): |
||||
return self.name() |
||||
|
||||
def name(self): |
||||
return f"{self.first_name} {self.last_name}" |
||||
|
||||
def shortened_name(self): |
||||
name = self.name() |
||||
if len(name) > 20 and self.first_name: |
||||
name = f"{self.first_name[0]}. {self.last_name}" |
||||
if len(name) > 20: |
||||
name_parts = self.last_name.split(" ") |
||||
name = f"{self.first_name[0]}. {name_parts[0]}" |
||||
return name |
||||
@ -0,0 +1,39 @@ |
||||
from django.db import models |
||||
from django.db.models.sql.query import Q |
||||
from . import Tournament |
||||
import uuid |
||||
from django.utils import timezone |
||||
|
||||
class UnregisteredTeam(models.Model): |
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) |
||||
tournament = models.ForeignKey(Tournament, on_delete=models.SET_NULL, related_name='unregistered_teams', null=True, blank=True) |
||||
unregistration_date = models.DateTimeField(null=True, blank=True) |
||||
|
||||
def __str__(self): |
||||
return self.player_names() |
||||
|
||||
def player_names_as_list(self): |
||||
return [pr.name() for pr in self.unregistered_players.all()] |
||||
|
||||
def team_names(self): |
||||
return self.player_names_as_list() |
||||
|
||||
def shortened_team_names(self): |
||||
players = list(self.unregistered_players.all()) |
||||
if len(players) == 1: |
||||
return [players[0].shortened_name(), ''] |
||||
else: |
||||
return [pr.shortened_name() for pr in players] |
||||
|
||||
@property |
||||
def players(self): |
||||
# Fetch related PlayerRegistration objects |
||||
return self.unregistered_players.all().order_by('last_name') |
||||
|
||||
def player_names(self): |
||||
names = self.player_names_as_list() |
||||
str = " - ".join(names) |
||||
if len(str) > 0: |
||||
return str |
||||
else: |
||||
return "no players" |
||||
@ -0,0 +1,79 @@ |
||||
from django.utils import timezone |
||||
from .models import TeamRegistration, PlayerRegistration |
||||
from .models.player_enums import PlayerSexType, PlayerDataSource |
||||
from .models.enums import FederalCategory |
||||
from tournaments.utils.licence_validator import LicenseValidator |
||||
|
||||
class TournamentRegistrationRepository: |
||||
@staticmethod |
||||
def create_team_registration(tournament, registration_date): |
||||
team_registration = TeamRegistration.objects.create( |
||||
tournament=tournament, |
||||
registration_date=registration_date |
||||
) |
||||
return team_registration |
||||
|
||||
@staticmethod |
||||
def create_player_registrations(request, team_registration, players_data, team_form_data): |
||||
stripped_license = None |
||||
if request.user.is_authenticated and request.user.licence_id: |
||||
stripped_license = LicenseValidator(request.user.licence_id).stripped_license |
||||
|
||||
for player_data in players_data: |
||||
is_captain = False |
||||
player_licence_id = player_data['licence_id'] |
||||
if player_licence_id and stripped_license: |
||||
if player_licence_id.startswith(stripped_license): |
||||
is_captain = True |
||||
|
||||
sex, rank, computed_rank = TournamentRegistrationRepository._compute_rank_and_sex( |
||||
team_registration.tournament, |
||||
player_data |
||||
) |
||||
|
||||
data_source = None |
||||
if player_data.get('found_in_french_federation', False) == True: |
||||
data_source = PlayerDataSource.FRENCH_FEDERATION |
||||
|
||||
player_registration = PlayerRegistration.objects.create( |
||||
team_registration=team_registration, |
||||
captain=is_captain, |
||||
source=data_source, |
||||
registered_online=True, |
||||
first_name=player_data.get('first_name'), |
||||
last_name=player_data.get('last_name'), |
||||
points=player_data.get('points'), |
||||
assimilation=player_data.get('assimilation'), |
||||
tournament_played=player_data.get('tournament_count'), |
||||
ligue_name=player_data.get('ligue_name'), |
||||
club_name=player_data.get('club_name'), |
||||
birthdate=player_data.get('birth_year'), |
||||
sex=sex, |
||||
rank=rank, |
||||
computed_rank=computed_rank, |
||||
licence_id=player_data['licence_id'], |
||||
) |
||||
|
||||
if is_captain is True: |
||||
player_registration.email=team_form_data['email'] |
||||
player_registration.phone_number=team_form_data['mobile_number'] |
||||
|
||||
player_registration.save() |
||||
|
||||
team_registration.set_weight() |
||||
team_registration.save() |
||||
|
||||
@staticmethod |
||||
def _compute_rank_and_sex(tournament, player_data): |
||||
is_woman = player_data.get('is_woman', False) |
||||
rank = player_data.get('rank', 0) |
||||
computed_rank = rank |
||||
sex = PlayerSexType.MALE |
||||
|
||||
if is_woman: |
||||
sex = PlayerSexType.FEMALE |
||||
if tournament.federal_category == FederalCategory.MEN: |
||||
computed_rank = str(int(computed_rank) + |
||||
FederalCategory.female_in_male_assimilation_addition(int(rank))) |
||||
|
||||
return sex, rank, computed_rank |
||||
@ -0,0 +1,259 @@ |
||||
from django.core.mail import EmailMessage |
||||
from django.utils import timezone |
||||
from django.urls import reverse |
||||
|
||||
class TournamentEmailService: |
||||
@staticmethod |
||||
def _convert_newlines_to_html(text): |
||||
html_content = text.replace('\n', '<br>') |
||||
return f""" |
||||
<html> |
||||
<body> |
||||
{html_content} |
||||
</body> |
||||
</html> |
||||
""" |
||||
|
||||
@staticmethod |
||||
def email_subject(tournament, topic): |
||||
base_subject = f"[{tournament.build_tournament_type_str()}] [{tournament.formatted_start_date()}] " + topic |
||||
return base_subject |
||||
|
||||
@staticmethod |
||||
def send_registration_confirmation(request, tournament, team_registration, waiting_list_position): |
||||
tournament_details_str = tournament.build_tournament_details_str() |
||||
|
||||
email_subject = TournamentEmailService._build_email_subject( |
||||
tournament, |
||||
tournament_details_str, |
||||
waiting_list_position |
||||
) |
||||
|
||||
email_body = TournamentEmailService._build_email_body( |
||||
request, |
||||
tournament, |
||||
team_registration, |
||||
tournament_details_str, |
||||
waiting_list_position |
||||
) |
||||
|
||||
email = EmailMessage( |
||||
subject=email_subject, |
||||
body=TournamentEmailService._convert_newlines_to_html(email_body), |
||||
to=[request.user.email] |
||||
) |
||||
|
||||
email.content_subtype = "html" |
||||
email.send() |
||||
|
||||
@staticmethod |
||||
def _build_email_subject(tournament, tournament_details_str, waiting_list_position): |
||||
if waiting_list_position >= 0: |
||||
base_subject = "En liste d'attente du tournoi" |
||||
else: |
||||
base_subject = "Confirmation d'inscription au tournoi" |
||||
return TournamentEmailService.email_subject(tournament, base_subject) |
||||
|
||||
@staticmethod |
||||
def _build_email_body(request, tournament, team_registration, tournament_details_str, waiting_list_position): |
||||
inscription_date = team_registration.local_registration_date().strftime("%d/%m/%Y à %H:%M") |
||||
team_members = [player.name() for player in team_registration.player_registrations.all()] |
||||
team_members_str = " et ".join(team_members) |
||||
|
||||
body_parts = [] |
||||
body_parts.append("Bonjour,\n") |
||||
|
||||
if waiting_list_position >= 0: |
||||
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.") |
||||
|
||||
absolute_url = f"{request.build_absolute_uri(f'/tournament/{tournament.id}/')}" |
||||
link_text = "informations sur le tournoi" |
||||
absolute_url = f'<a href="{absolute_url}">{link_text}</a>' |
||||
|
||||
body_parts.extend([ |
||||
f"\nDate d'inscription: {inscription_date}", |
||||
f"\nÉquipe inscrite: {team_members_str}", |
||||
f"\nLe tournoi commencera le {tournament.formatted_start_date()} au club {tournament.event.club.name}", |
||||
f"\nVoir les {absolute_url}", |
||||
"\nPour toute question, veuillez contacter votre juge-arbitre. Si vous n'êtes pas à l'origine de cette inscription, merci de le contacter rapidement.", |
||||
f"\n{tournament.event.creator.full_name()}\n{tournament.event.creator.email}", |
||||
"\nCeci est un e-mail automatique, veuillez ne pas y répondre.", |
||||
"\nCordialement,\n\nPadel Club" |
||||
]) |
||||
|
||||
return "\n".join(body_parts) |
||||
|
||||
@staticmethod |
||||
def send_unregistration_confirmation(captain, tournament, other_player): |
||||
tournament_details_str = tournament.build_tournament_details_str() |
||||
|
||||
email_subject = TournamentEmailService.email_subject(tournament, "Désistement du tournoi") |
||||
email_body = TournamentEmailService._build_unregistration_email_body( |
||||
tournament, |
||||
captain, |
||||
tournament_details_str, |
||||
other_player |
||||
) |
||||
|
||||
email = EmailMessage( |
||||
subject=email_subject, |
||||
body=TournamentEmailService._convert_newlines_to_html(email_body), |
||||
to=[captain.email] |
||||
) |
||||
|
||||
email.content_subtype = "html" |
||||
email.send() |
||||
|
||||
if other_player.email is not None: |
||||
email_body = TournamentEmailService._build_unregistration_email_body( |
||||
tournament, |
||||
other_player, |
||||
tournament_details_str, |
||||
captain |
||||
) |
||||
|
||||
email = EmailMessage( |
||||
subject=email_subject, |
||||
body=TournamentEmailService._convert_newlines_to_html(email_body), |
||||
to=[other_player.email] |
||||
) |
||||
|
||||
email.content_subtype = "html" |
||||
email.send() |
||||
|
||||
@staticmethod |
||||
def send_out_of_waiting_list_confirmation(captain, tournament, other_player): |
||||
tournament_details_str = tournament.build_tournament_details_str() |
||||
email_subject = TournamentEmailService.email_subject(tournament, "Participation au tournoi") |
||||
email_body = TournamentEmailService._build_out_of_waiting_list_email_body( |
||||
tournament, |
||||
captain, |
||||
tournament_details_str, |
||||
other_player |
||||
) |
||||
|
||||
email = EmailMessage( |
||||
subject=email_subject, |
||||
body=TournamentEmailService._convert_newlines_to_html(email_body), |
||||
to=[captain.email] |
||||
) |
||||
|
||||
email.content_subtype = "html" |
||||
email.send() |
||||
|
||||
if other_player.email is not None: |
||||
email_body = TournamentEmailService._build_out_of_waiting_list_email_body( |
||||
tournament, |
||||
other_player, |
||||
tournament_details_str, |
||||
captain |
||||
) |
||||
|
||||
email = EmailMessage( |
||||
subject=email_subject, |
||||
body=TournamentEmailService._convert_newlines_to_html(email_body), |
||||
to=[other_player.email] |
||||
) |
||||
|
||||
email.content_subtype = "html" |
||||
email.send() |
||||
|
||||
@staticmethod |
||||
def _build_unregistration_email_body(tournament, captain, tournament_details_str, other_player): |
||||
body_parts = [ |
||||
"Bonjour,\n\n", |
||||
f"Votre inscription au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été annulée" |
||||
] |
||||
|
||||
if other_player is not None: |
||||
body_parts.append( |
||||
f"\n\nVous étiez inscrit avec {other_player.name()}, n'oubliez pas de prévenir votre partenaire." |
||||
) |
||||
|
||||
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info" |
||||
link_text = "informations sur le tournoi" |
||||
absolute_url = f'<a href="{absolute_url}">{link_text}</a>' |
||||
|
||||
body_parts.append( |
||||
f"\n\nVoir les {absolute_url}", |
||||
) |
||||
|
||||
body_parts.extend([ |
||||
"\n\nPour toute question, veuillez contacter votre juge-arbitre. " |
||||
"Si vous n'êtes pas à l'origine de cette désinscription, merci de le contacter rapidement.", |
||||
f"\n{tournament.event.creator.full_name()}\n{tournament.event.creator.email}", |
||||
"\n\nCeci est un e-mail automatique, veuillez ne pas y répondre." |
||||
]) |
||||
|
||||
return "".join(body_parts) |
||||
|
||||
@staticmethod |
||||
def _build_out_of_waiting_list_email_body(tournament, captain, tournament_details_str, other_player): |
||||
body_parts = [ |
||||
"Bonjour,\n\n", |
||||
f"Suite au désistement d'une paire, vous êtes maintenant inscrit au tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name}" |
||||
] |
||||
|
||||
absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info" |
||||
link_text = "accéder au tournoi" |
||||
absolute_url = f'<a href="{absolute_url}">{link_text}</a>' |
||||
|
||||
if other_player is not None: |
||||
body_parts.append( |
||||
f"\nVoici le partenaire indiqué dans l'inscription : {other_player.name()}, n'oubliez pas de le prévenir." |
||||
) |
||||
|
||||
body_parts.append( |
||||
"\n\nSi vous n'êtes plus disponible pour participer à ce tournoi, cliquez sur ce lien ou contactez rapidement le juge-arbitre." |
||||
f"\n{absolute_url}" |
||||
"\nPour vous désinscrire en ligne vous devez avoir un compte Padel Club." |
||||
) |
||||
|
||||
body_parts.extend([ |
||||
f"\n\n{tournament.event.creator.full_name()}\n{tournament.event.creator.email}", |
||||
"\n\nCeci est un e-mail automatique, veuillez ne pas y répondre." |
||||
]) |
||||
|
||||
return "".join(body_parts) |
||||
|
||||
@staticmethod |
||||
def send_tournament_cancellation_notification(player, tournament, other_player): |
||||
tournament_details_str = tournament.build_tournament_details_str() |
||||
email_subject = TournamentEmailService.email_subject(tournament, "Annulation du tournoi") |
||||
email_body = TournamentEmailService._build_tournament_cancellation_email_body( |
||||
tournament, |
||||
player, |
||||
tournament_details_str, |
||||
other_player |
||||
) |
||||
|
||||
email = EmailMessage( |
||||
subject=email_subject, |
||||
body=TournamentEmailService._convert_newlines_to_html(email_body), |
||||
to=[player.email] |
||||
) |
||||
|
||||
email.content_subtype = "html" |
||||
email.send() |
||||
|
||||
@staticmethod |
||||
def _build_tournament_cancellation_email_body(tournament, player, tournament_details_str, other_player): |
||||
body_parts = [ |
||||
"Bonjour,\n\n", |
||||
f"Le tournoi {tournament_details_str}, prévu le {tournament.formatted_start_date()} au club {tournament.event.club.name} a été annulé par le juge-arbitre." |
||||
] |
||||
|
||||
if other_player is not None: |
||||
body_parts.append( |
||||
f"\nVous étiez inscrit avec {other_player.name()}, n'oubliez pas de prévenir votre partenaire." |
||||
) |
||||
|
||||
body_parts.extend([ |
||||
"\n\nPour toute question, veuillez contacter votre juge-arbitre:", |
||||
f"\n{tournament.event.creator.full_name()}\n{tournament.event.creator.email}", |
||||
"\n\nCeci est un e-mail automatique, veuillez ne pas y répondre." |
||||
]) |
||||
|
||||
return "".join(body_parts) |
||||
@ -0,0 +1,311 @@ |
||||
from django.utils import timezone |
||||
from ..forms import TournamentRegistrationForm, AddPlayerForm |
||||
from ..repositories import TournamentRegistrationRepository |
||||
from .email_service import TournamentEmailService |
||||
from django.contrib import messages |
||||
from ..utils.licence_validator import LicenseValidator |
||||
from ..utils.player_search import get_player_name_from_csv |
||||
from tournaments.models import PlayerRegistration |
||||
|
||||
class TournamentRegistrationService: |
||||
def __init__(self, request, tournament): |
||||
self.request = request |
||||
self.tournament = tournament |
||||
self.context = {} |
||||
self.repository = TournamentRegistrationRepository() |
||||
self.email_service = TournamentEmailService() |
||||
|
||||
def initialize_context(self): |
||||
self.context = { |
||||
'tournament': self.tournament, |
||||
'registration_successful': False, |
||||
'team_form': None, |
||||
'add_player_form': None, |
||||
'current_players': self.request.session.get('team_registration', []), |
||||
} |
||||
return self.context |
||||
|
||||
def handle_post_request(self): |
||||
self.context['team_form'] = TournamentRegistrationForm(self.request.POST) |
||||
self.context['add_player_form'] = AddPlayerForm(self.request.POST) |
||||
|
||||
if 'add_player' in self.request.POST: |
||||
self.handle_add_player() |
||||
elif 'register_team' in self.request.POST: |
||||
self.handle_team_registration() |
||||
|
||||
def handle_add_player(self): |
||||
if not self.context['add_player_form'].is_valid(): |
||||
return |
||||
|
||||
player_data = self.context['add_player_form'].cleaned_data |
||||
licence_id = player_data.get('licence_id', '').upper() |
||||
|
||||
# Validate license |
||||
if not self._validate_license(licence_id): |
||||
return |
||||
|
||||
# Check for duplicate players |
||||
if self._is_duplicate_player(licence_id): |
||||
return |
||||
|
||||
# Check if player is already registered in tournament |
||||
if self._is_already_registered(licence_id): |
||||
return |
||||
|
||||
if self.request.user.licence_id is None and len(self.context['current_players']) == 0: |
||||
# if no licence id for authentificated user and trying to add him as first player of the team, we check his federal data |
||||
self._handle_invalid_names(licence_id, player_data) |
||||
else: |
||||
# Handle player data |
||||
if self.context['add_player_form'].names_is_valid(): |
||||
self._handle_valid_names(player_data) |
||||
else: |
||||
self._handle_invalid_names(licence_id, player_data) |
||||
|
||||
if self.request.user.is_authenticated and self.request.user.licence_id is None: |
||||
self._update_user_license(player_data.get('licence_id')) |
||||
|
||||
def handle_team_registration(self): |
||||
if not self.context['team_form'].is_valid(): |
||||
return |
||||
|
||||
if self.request.user.is_authenticated: |
||||
cleaned_data = self.context['team_form'].cleaned_data |
||||
mobile_number = cleaned_data.get('mobile_number') |
||||
self.request.user.phone = mobile_number |
||||
self.request.user.save() |
||||
|
||||
waiting_list_position = self.tournament.get_waiting_list_position() |
||||
|
||||
team_registration = self.repository.create_team_registration( |
||||
self.tournament, |
||||
timezone.now().replace(microsecond=0) |
||||
) |
||||
|
||||
self.repository.create_player_registrations( |
||||
self.request, |
||||
team_registration, |
||||
self.request.session['team_registration'], |
||||
self.context['team_form'].cleaned_data |
||||
) |
||||
|
||||
self.email_service.send_registration_confirmation( |
||||
self.request, |
||||
self.tournament, |
||||
team_registration, |
||||
waiting_list_position |
||||
) |
||||
|
||||
self.clear_session_data() |
||||
self.context['registration_successful'] = True |
||||
|
||||
def handle_get_request(self): |
||||
self.context['add_player_form'] = AddPlayerForm() |
||||
self.context['team_form'] = self.initialize_team_form() |
||||
self.initialize_session_data() |
||||
|
||||
def add_player_to_session(self, player_data): |
||||
if not self.request.session.get('team_registration'): |
||||
self.request.session['team_registration'] = [] |
||||
|
||||
self.request.session['team_registration'].append(player_data) |
||||
self.context['current_players'] = self.request.session.get('team_registration', []) |
||||
self.context['add_player_form'].first_tournament = False |
||||
self.context['add_player_form'].user_without_licence = False |
||||
self.request.session.modified = True |
||||
|
||||
def clear_session_data(self): |
||||
self.request.session['team_registration'] = [] |
||||
self.request.session.modified = True |
||||
|
||||
def initialize_team_form(self): |
||||
initial_data = {} |
||||
if self.request.user.is_authenticated: |
||||
initial_data = { |
||||
'email': self.request.user.email, |
||||
'mobile_number': self.request.user.phone, |
||||
} |
||||
return TournamentRegistrationForm(initial=initial_data) |
||||
|
||||
def initialize_session_data(self): |
||||
print("initialize_session_data") |
||||
self.request.session['team_registration'] = [] |
||||
if self.request.user.is_authenticated: |
||||
self._add_authenticated_user_to_session() |
||||
|
||||
def _add_authenticated_user_to_session(self): |
||||
if not self.request.user.licence_id: |
||||
self._handle_user_without_license() |
||||
return |
||||
|
||||
player_data = self._get_authenticated_user_data() |
||||
if player_data: |
||||
self.request.session['team_registration'].insert(0, player_data) |
||||
self.context['current_players'] = self.request.session.get('team_registration', []) |
||||
self.request.session.modified = True |
||||
|
||||
def _handle_user_without_license(self): |
||||
player_data = { |
||||
'first_name': self.request.user.first_name, |
||||
'last_name': self.request.user.last_name.upper(), |
||||
} |
||||
self.context['add_player_form'] = AddPlayerForm(initial=player_data) |
||||
self.context['add_player_form'].user_without_licence = True |
||||
self.request.session.modified = True |
||||
|
||||
def _get_authenticated_user_data(self): |
||||
from ..utils.player_search import get_player_name_from_csv |
||||
from ..utils.licence_validator import LicenseValidator |
||||
user = self.request.user |
||||
validator = LicenseValidator(user.licence_id) |
||||
|
||||
player_data = { |
||||
'first_name': user.first_name, |
||||
'last_name': user.last_name.upper(), |
||||
'email': user.email, |
||||
'phone': user.phone, |
||||
'licence_id': validator.computed_licence_id |
||||
} |
||||
|
||||
data, found = get_player_name_from_csv(self.tournament.federal_category, user.licence_id) |
||||
if found and data: |
||||
player_data.update({ |
||||
'rank': data['rank'], |
||||
'points': data.get('points'), |
||||
'assimilation': data.get('assimilation'), |
||||
'tournament_count': data.get('tournament_count'), |
||||
'ligue_name': data.get('ligue_name'), |
||||
'club_name': data.get('club_name'), |
||||
'birth_year': data.get('birth_year'), |
||||
'found_in_french_federation': True, |
||||
}) |
||||
|
||||
return player_data |
||||
|
||||
def _validate_license(self, licence_id): |
||||
validator = LicenseValidator(licence_id) |
||||
|
||||
if validator.validate_license() is False and self.tournament.license_is_required: |
||||
if not licence_id: |
||||
message = ("Le numéro de licence est obligatoire." |
||||
if not self.request.session.get('team_registration', []) |
||||
else "Le numéro de licence de votre partenaire est obligatoire.") |
||||
messages.error(self.request, message) |
||||
else: |
||||
# computed_license_key = validator.computed_license_key |
||||
# messages.error(self.request, f"Le numéro de licence est invalide, la lettre ne correspond pas. {computed_license_key}") |
||||
messages.error(self.request, "Le numéro de licence est invalide, la lettre ne correspond pas.") |
||||
return False |
||||
return True |
||||
|
||||
def _is_duplicate_player(self, licence_id): |
||||
existing_players = [player['licence_id'] for player in self.request.session.get('team_registration', [])] |
||||
if licence_id in existing_players: |
||||
messages.error(self.request, "Ce joueur est déjà dans l'équipe.") |
||||
return True |
||||
return False |
||||
|
||||
def _is_already_registered(self, licence_id): |
||||
validator = LicenseValidator(licence_id) |
||||
if (validator.validate_license() and |
||||
self._license_already_registered(validator.stripped_license) and |
||||
self.tournament.license_is_required): |
||||
messages.error(self.request, "Un joueur avec ce numéro de licence est déjà inscrit dans une équipe.") |
||||
return True |
||||
return False |
||||
|
||||
def _handle_valid_names(self, player_data): |
||||
if player_data.get('rank') is None: |
||||
self._set_default_rank(player_data) |
||||
|
||||
self.add_player_to_session(player_data) |
||||
self.context['add_player_form'] = AddPlayerForm() |
||||
self.context['add_player_form'].first_tournament = False |
||||
|
||||
def _handle_invalid_names(self, licence_id, player_data): |
||||
if not self.context['add_player_form'].first_tournament: |
||||
data, found = get_player_name_from_csv(self.tournament.federal_category, licence_id) |
||||
if found and data: |
||||
self._update_player_data_from_csv(player_data, data) |
||||
player_check = self._player_check(player_data) |
||||
if player_check == True: |
||||
self.add_player_to_session(player_data) |
||||
self.context['add_player_form'] = AddPlayerForm() |
||||
else: |
||||
return |
||||
else: |
||||
self._handle_first_tournament_case(data) |
||||
|
||||
def _set_default_rank(self, player_data): |
||||
if self.request.session.get('last_rank') is None: |
||||
data, found = get_player_name_from_csv(self.tournament.federal_category, None) |
||||
if data: |
||||
self.request.session['last_rank'] = data['rank'] |
||||
self.request.session['is_woman'] = data['is_woman'] |
||||
self.request.session.modified = True |
||||
|
||||
player_data['rank'] = self.request.session.get('last_rank', 0) |
||||
player_data['is_woman'] = self.request.session.get('is_woman', False) |
||||
|
||||
def _update_user_license(self, licence_id): |
||||
if self.request.user.is_authenticated and licence_id: |
||||
self.context['add_player_form'].user_without_licence = False |
||||
validator = LicenseValidator(licence_id) |
||||
self.request.user.licence_id = validator.computed_licence_id |
||||
self.request.user.save() |
||||
self.request.user.refresh_from_db() |
||||
self.request.session.modified = True |
||||
# Reset the form state |
||||
self.context['add_player_form'] = AddPlayerForm() |
||||
self.context['add_player_form'].first_tournament = False |
||||
|
||||
def _update_player_data_from_csv(self, player_data, csv_data): |
||||
player_data.update({ |
||||
'first_name': csv_data['first_name'], |
||||
'last_name': csv_data['last_name'], |
||||
'rank': csv_data['rank'], |
||||
'is_woman': csv_data['is_woman'], |
||||
'points': csv_data.get('points'), |
||||
'assimilation': csv_data.get('assimilation'), |
||||
'tournament_count': csv_data.get('tournament_count'), |
||||
'ligue_name': csv_data.get('ligue_name'), |
||||
'club_name': csv_data.get('club_name'), |
||||
'birth_year': csv_data.get('birth_year'), |
||||
'found_in_french_federation': True, |
||||
'email': None, |
||||
'phone': None, |
||||
}) |
||||
|
||||
def _handle_first_tournament_case(self, data): |
||||
if data: |
||||
self.request.session['last_rank'] = data['rank'] |
||||
self.request.session['is_woman'] = data['is_woman'] |
||||
self.request.session.modified = True |
||||
|
||||
self.context['add_player_form'].first_tournament = True |
||||
|
||||
if not self.context['add_player_form'].names_is_valid(): |
||||
message = ("Pour confirmer votre inscription votre prénom et votre nom sont obligatoires." |
||||
if not self.request.session.get('team_registration', []) |
||||
else "Pour rajouter un partenaire, son prénom et son nom sont obligatoires.") |
||||
messages.error(self.request, message) |
||||
|
||||
def _player_check(self, player_data): |
||||
licence_id = player_data['licence_id'].upper() |
||||
validator = LicenseValidator(licence_id) |
||||
is_license_valid = validator.validate_license() |
||||
|
||||
player_register_check = self.tournament.player_register_check(licence_id) |
||||
if is_license_valid and player_register_check is not None: |
||||
for message in player_register_check: |
||||
messages.error(self.request, message) |
||||
return False |
||||
|
||||
return True |
||||
|
||||
def _license_already_registered(self, stripped_license): |
||||
return PlayerRegistration.objects.filter( |
||||
team_registration__tournament=self.tournament, |
||||
licence_id__startswith=stripped_license |
||||
).exists() |
||||
@ -0,0 +1,72 @@ |
||||
from django.contrib import messages |
||||
from django.utils import timezone |
||||
from ..models import PlayerRegistration, UnregisteredTeam, UnregisteredPlayer |
||||
from ..models.player_enums import PlayerDataSource |
||||
from ..services.email_service import TournamentEmailService |
||||
|
||||
class TournamentUnregistrationService: |
||||
def __init__(self, request, tournament): |
||||
self.request = request |
||||
self.tournament = tournament |
||||
self.player_registration = None |
||||
self.other_player = None |
||||
|
||||
def can_unregister(self): |
||||
if not self.tournament.is_unregistration_possible(): |
||||
messages.error(self.request, "Le désistement n'est plus possible pour ce tournoi. Si vous souhaitez vous désinscrire, veuillez contacter le juge-arbitre.") |
||||
return False |
||||
|
||||
if not self.request.user.licence_id: |
||||
messages.error(self.request, |
||||
"Vous ne pouvez pas vous désinscrire car vous n'avez pas de numéro de licence associé.") |
||||
return False |
||||
|
||||
return True |
||||
|
||||
def process_unregistration(self): |
||||
if not self._find_player_registration(): |
||||
messages.error(self.request, |
||||
"La désincription a échouée. Veuillez contacter le juge-arbitre.") |
||||
return False |
||||
|
||||
self._unregister_team() |
||||
self._delete_registered_team() |
||||
self._cleanup_session() |
||||
return True |
||||
|
||||
def _unregister_team(self): |
||||
# Create unregistered team record |
||||
team_registration = self.player_registration.team_registration |
||||
|
||||
unregistered_team = UnregisteredTeam.objects.create( |
||||
tournament=team_registration.tournament, |
||||
unregistration_date=timezone.now(), |
||||
) |
||||
|
||||
for player in team_registration.player_registrations.all(): |
||||
UnregisteredPlayer.objects.create( |
||||
unregistered_team=unregistered_team, |
||||
first_name=player.first_name, |
||||
last_name=player.last_name, |
||||
licence_id=player.licence_id, |
||||
) |
||||
|
||||
def _find_player_registration(self): |
||||
self.player_registration = PlayerRegistration.objects.filter( |
||||
licence_id__startswith=self.request.user.licence_id, |
||||
team_registration__tournament_id=self.tournament.id, |
||||
).first() |
||||
|
||||
if self.player_registration: |
||||
team_registration = self.player_registration.team_registration |
||||
self.other_player = team_registration.get_other_player(self.player_registration) |
||||
return True |
||||
return False |
||||
|
||||
def _delete_registered_team(self): |
||||
team_registration = self.player_registration.team_registration |
||||
team_registration.delete() |
||||
|
||||
def _cleanup_session(self): |
||||
self.request.session['team_registration'] = [] |
||||
self.request.session.modified = True |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
Can't render this file because it is too large.
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 758 KiB After Width: | Height: | Size: 1.4 MiB |
@ -0,0 +1,79 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
{% block head_title %} Mon Compte {% endblock %} |
||||
{% block first_title %} Mon Compte Padel Club {% endblock %} |
||||
{% block second_title %} {{ user.first_name }} {{ user.last_name }} {% endblock %} |
||||
|
||||
{% block content %} |
||||
|
||||
<nav class="margin10"> |
||||
<a href="{% url 'index' %}" class="orange">Accueil</a> |
||||
<a href="{% url 'clubs' %}" class="orange">Clubs</a> |
||||
{% if user.is_authenticated %} |
||||
<a href="{% url 'my-tournaments' %}" class="orange">Mes tournois</a> |
||||
<a href="{% url 'profile' %}">Mon compte</a> |
||||
<a href="{% url 'logout' %}" class="red">Se déconnecter</a> |
||||
{% else %} |
||||
<a href="{% url 'login' %}">Se connecter</a> |
||||
{% endif %} |
||||
</nav> |
||||
|
||||
{% load static %} |
||||
{% load tz %} |
||||
|
||||
<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> |
||||
{% 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 %} |
||||
<p>{{ error }}</p> |
||||
{% endfor %} |
||||
</div> |
||||
{% endif %} |
||||
<form method="post"> |
||||
{% csrf_token %} |
||||
{{ form.as_p }} |
||||
<button type="submit" class="rounded-button">Sauver les changements</button> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
<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' %}"> |
||||
{% csrf_token %} |
||||
{{ password_change_form.as_p }} |
||||
<button type="submit" class="rounded-button">Modifier le mot de passe</button> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,140 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
|
||||
{% block head_title %}{{ tournament.display_name }} : Informations{% endblock %} |
||||
{% block first_title %}{{ tournament.event.display_name }}{% endblock %} |
||||
{% block second_title %}{{ tournament.display_name }}{% endblock %} |
||||
|
||||
{% block content %} |
||||
{% load static %} |
||||
{% load tz %} |
||||
|
||||
<nav class="margin10"> |
||||
<a href="{% url 'tournament-info' tournament.id %}" class="topmargin5">Informations</a> |
||||
</nav> |
||||
|
||||
<div class="grid-x"> |
||||
|
||||
<div class="cell medium-6 large-6 my-block"> |
||||
<h1 class="club my-block topmargin20">Inscription : {{ tournament.display_name }} {{ tournament.get_federal_age_category_display}}</h1 > |
||||
|
||||
|
||||
<div class="bubble"> |
||||
|
||||
{% if registration_successful %} |
||||
<p>Merci, l'inscription a bien été envoyée au juge-arbitre.</p> |
||||
<p style="text-align: justify;"> |
||||
Un email de confirmation a été envoyé à {{ user.email }} pour confirmer votre inscription. Pensez à vérifier vos spams si vous ne recevez pas l'email. En cas de problème, contactez le juge-arbitre. |
||||
</p> |
||||
{% else %} |
||||
<form method="post"> |
||||
{% csrf_token %} |
||||
|
||||
<!-- Team Registration Form --> |
||||
<div> |
||||
<p> |
||||
<div class="semibold"> |
||||
Informations de contact |
||||
</div> |
||||
</p> |
||||
{{ team_form.as_p }} <!-- Render team registration form fields here --> |
||||
</div> |
||||
|
||||
<!-- Show players added to the team only if there are players added --> |
||||
{% if current_players %} |
||||
<p> |
||||
<div class="semibold"> |
||||
Constitution de votre équipe |
||||
</div> |
||||
</p> |
||||
<ul> |
||||
{% for player in current_players %} |
||||
<li>{{ player.first_name }} {{ player.last_name }}{% if player.licence_id %} ({{ player.licence_id }}){% endif %}</li> |
||||
{% endfor %} |
||||
</ul> |
||||
{% endif %} |
||||
|
||||
<!-- Add Player Form --> |
||||
{% if current_players|length < 2 %} |
||||
<div> |
||||
{% if current_players|length == 1 %} |
||||
<div class="semibold"> |
||||
Inscrivez votre partenaire |
||||
</div> |
||||
{% endif %} |
||||
{% if current_players|length == 0 and add_player_form.user_without_licence and tournament.license_is_required %} |
||||
<div class="semibold"> |
||||
Une licence est obligatoire pour vous inscrire : |
||||
</div> |
||||
{% endif %} |
||||
|
||||
{% if tournament.license_is_required %} |
||||
{{ add_player_form.licence_id.label_tag }} |
||||
{{ add_player_form.licence_id }} |
||||
{% endif %} |
||||
{% if add_player_form.first_tournament or add_player_form.user_without_licence or tournament.license_is_required is False %} |
||||
{% if not add_player_form.user_without_licence and tournament.license_is_required is True %} |
||||
<div class="semibold"> |
||||
Précisez les informations du joueur : |
||||
</div> |
||||
{% endif %} |
||||
{{ add_player_form.first_name.label_tag }} |
||||
{{ add_player_form.first_name }} |
||||
{{ add_player_form.last_name.label_tag }} |
||||
{{ add_player_form.last_name }} |
||||
{% if tournament.license_is_required is False %} |
||||
{{ add_player_form.licence_id.label_tag }} |
||||
{% if tournament.license_is_required is False %}(facultatif){% endif %} |
||||
{{ add_player_form.licence_id }} |
||||
{% endif %} |
||||
{% endif %} |
||||
|
||||
<div class="margin10"> |
||||
{% for message in messages %} |
||||
<div class="alert alert-{{ message.tags }}">{{ message }}</div> |
||||
{% endfor %} |
||||
</div> |
||||
|
||||
<button type="submit" name="add_player" class="rounded-button"> |
||||
{% if add_player_form.user_without_licence %} |
||||
Confirmer |
||||
{% else %} |
||||
{% if current_players|length == 0 %} |
||||
Confirmer |
||||
{% else %} |
||||
Ajouter un partenaire |
||||
{% endif %} |
||||
{% endif %} |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
|
||||
<!-- Show players added to the team only if there are players added --> |
||||
{% if current_players|length >= tournament.minimum_player_per_team %} |
||||
<div class="margin10"> |
||||
</div> |
||||
<div class="semibold margin10"> |
||||
{% if tournament.get_waiting_list_position == 1 %} |
||||
Tournoi complet, {{ tournament.get_waiting_list_position }} équipe en liste d'attente actuellement. |
||||
{% elif tournament.get_waiting_list_position > 1 %} |
||||
Tournoi complet, {{ tournament.get_waiting_list_position }} équipes en liste d'attente actuellement. |
||||
{% elif tournament.get_waiting_list_position == 0 %} |
||||
Tournoi complet, vous seriez la première équipe en liste d'attente. |
||||
{% endif %} |
||||
</div> |
||||
|
||||
<div> |
||||
<button type="submit" name="register_team" class="rounded-button"> |
||||
{% if tournament.get_waiting_list_position < 0 %} |
||||
Confirmer l'inscription |
||||
{% else %} |
||||
Se mettre en liste d'attente |
||||
{% endif %} |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
</form> |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,52 @@ |
||||
<!-- templates/registration/login.html --> |
||||
{% extends 'tournaments/base.html' %} |
||||
{% block head_title %} Connexion {% endblock %} |
||||
{% block first_title %} Padel Club {% endblock %} |
||||
{% block second_title %} Connexion {% endblock %} |
||||
|
||||
{% block content %} |
||||
{% load static %} |
||||
{% load tz %} |
||||
|
||||
<div class="grid-x"> |
||||
<div class="bubble"> |
||||
<div class="cell medium-6 large-6 my-block"> |
||||
{% 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 %} |
||||
<form method="post" action="{% url 'login' %}"> |
||||
{% csrf_token %} |
||||
<input type="hidden" name="next" value="{{ request.GET.next }}"> |
||||
<label for="username">Identifiant ou e-mail </label> |
||||
<input type="text" name="username" id="username" required> |
||||
|
||||
<label for="password">Mot de passe :</label> |
||||
<input type="password" name="password" id="password" required> |
||||
<button type="submit" class="rounded-button">Se connecter</button> |
||||
|
||||
<p> |
||||
<a href="{% url 'password_reset' %}" class="styled-link">Mot de passe oublié ?</a> |
||||
</p> |
||||
|
||||
</form> |
||||
<p>Pas encore de compte ? <a href="{% url 'signup' %}" class="styled-link">Créer le tout de suite !</a></p> |
||||
</div> |
||||
{% for message in messages %} |
||||
<div class="alert alert-{{ message.tags }}">{{ message }}</div> |
||||
{% endfor %} |
||||
</div> |
||||
</div> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,51 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
{% block head_title %} Mes tournois {% endblock %} |
||||
{% block first_title %} Mes tournois Padel Club {% endblock %} |
||||
{% block second_title %} {{ user.first_name }} {{ user.last_name }} {% endblock %} |
||||
|
||||
{% block content %} |
||||
|
||||
{% include 'tournaments/navigation_base.html' %} |
||||
|
||||
{% load static %} |
||||
{% 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"> |
||||
<label class="title">Vos tournois en cours</label> |
||||
{% if running_tournaments %} |
||||
{% for tournament in running_tournaments %} |
||||
{% include 'tournaments/tournament_row.html' %} |
||||
{% endfor %} |
||||
{% else %} |
||||
Aucun tournoi en cours |
||||
{% endif %} |
||||
|
||||
</div> |
||||
</div> |
||||
<div class="cell medium-6 large-6 topblock my-block"> |
||||
<div class="bubble"> |
||||
<label class="title">Vos tournois terminés</label> |
||||
{% if ended_tournaments %} |
||||
{% for tournament in ended_tournaments %} |
||||
{% include 'tournaments/tournament_row.html' %} |
||||
{% endfor %} |
||||
{% else %} |
||||
Aucun tournoi terminé |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,19 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
{% block head_title %} Réinitialisation terminée {% endblock %} |
||||
{% block first_title %} Padel Club {% endblock %} |
||||
{% block second_title %} Réinitialisation terminée {% endblock %} |
||||
|
||||
{% block content %} |
||||
<div class="grid-x"> |
||||
<div class="bubble"> |
||||
<div class="cell medium-6 large-6 my-block"> |
||||
<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> |
||||
<a href="{% url 'login' %}" class="rounded-button">Se connecter</a> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,47 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
{% block head_title %} Nouveau mot de passe {% endblock %} |
||||
{% block first_title %} Padel Club {% endblock %} |
||||
{% block second_title %} Nouveau mot de passe {% endblock %} |
||||
|
||||
{% block content %} |
||||
<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 %} |
||||
<p>{{ error }}</p> |
||||
{% endfor %} |
||||
</div> |
||||
{% endif %} |
||||
<form method="post"> |
||||
{% csrf_token %} |
||||
<label for="new_password1">Nouveau mot de passe :</label> |
||||
<input type="password" name="new_password1" id="new_password1" required> |
||||
|
||||
<label for="new_password2">Confirmer le nouveau mot de passe :</label> |
||||
<input type="password" name="new_password2" id="new_password2" required> |
||||
|
||||
<button type="submit" class="rounded-button">Réinitialiser le mot de passe</button> |
||||
</form> |
||||
<p> |
||||
<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> |
||||
{% endblock %} |
||||
@ -0,0 +1,20 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
{% block head_title %} Demande envoyée {% endblock %} |
||||
{% block first_title %} Padel Club {% endblock %} |
||||
{% block second_title %} Demande envoyée {% endblock %} |
||||
|
||||
{% block content %} |
||||
<div class="grid-x"> |
||||
<div class="bubble"> |
||||
<div class="cell medium-6 large-6 my-block"> |
||||
<p> |
||||
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> |
||||
<a href="{% url 'login' %}" class="styled-link">Retour à la connexion</a> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,11 @@ |
||||
{% autoescape off %} |
||||
Bonjour, |
||||
|
||||
Vous avez demandé une réinitialisation de votre mot de passe. Veuillez cliquer sur le lien suivant pour en choisir un nouveau : |
||||
|
||||
http://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} |
||||
|
||||
Si vous n'avez pas fait cette demande, vous pouvez ignorer ce message. |
||||
|
||||
A tout de suite sur Padel Club ! |
||||
{% endautoescape %} |
||||
@ -0,0 +1,43 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
{% block head_title %} Réinitialisation du mot de passe {% endblock %} |
||||
{% block first_title %} Padel Club {% endblock %} |
||||
{% block second_title %} Réinitialisation du mot de passe {% endblock %} |
||||
|
||||
{% block content %} |
||||
<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 %} |
||||
<p>{{ error }}</p> |
||||
{% endfor %} |
||||
</div> |
||||
{% endif %} |
||||
<form method="post" action="{% url 'password_reset' %}"> |
||||
{% csrf_token %} |
||||
<label for="email">Adresse e-mail :</label> |
||||
<input type="email" name="email" id="email" required> |
||||
<button type="submit" class="rounded-button">Envoyer le lien de réinitialisation</button> |
||||
</form> |
||||
<p> |
||||
<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> |
||||
{% endblock %} |
||||
@ -0,0 +1,46 @@ |
||||
<!-- templates/registration/signup.html --> |
||||
{% extends 'tournaments/base.html' %} |
||||
{% block head_title %} Création de compte {% endblock %} |
||||
{% block first_title %} Padel Club {% endblock %} |
||||
{% block second_title %} Création de compte {% endblock %} |
||||
|
||||
{% block content %} |
||||
{% load static %} |
||||
{% load tz %} |
||||
|
||||
<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 %} |
||||
<p>{{ error }}</p> |
||||
{% endfor %} |
||||
</div> |
||||
{% endif %} |
||||
|
||||
<form method="post"> |
||||
{% csrf_token %} |
||||
{{ form.as_p }} |
||||
<button type="submit" class="rounded-button">Créer votre compte</button> |
||||
</form> |
||||
</div> |
||||
|
||||
{% for message in messages %} |
||||
<div class="alert alert-{{ message.tags }}">{{ message }}</div> |
||||
{% endfor %} |
||||
</div> |
||||
</div> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,38 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
|
||||
{% block head_title %} Création de compte {% endblock %} |
||||
{% block first_title %} Padel Club {% endblock %} |
||||
{% block second_title %} Création de compte {% endblock %} |
||||
|
||||
{% block content %} |
||||
{% load static %} |
||||
{% load tz %} |
||||
|
||||
<div class="grid-x"> |
||||
<div class="bubble"> |
||||
<label class="title">Bienvenue ! Votre compte a été créé avec succès</label> |
||||
<div class="cell medium-6 large-6 my-block"> |
||||
<p>Un e-mail de confirmation a été envoyé à :<br> |
||||
<strong>{{ user_email }}</strong></p> |
||||
|
||||
<p>Pour finaliser votre inscription, merci de :</p> |
||||
<ol> |
||||
<li>Vérifier votre boîte de réception (et vos spams si nécessaire)</li> |
||||
<li>Cliquer sur le lien de confirmation dans l'e-mail</li> |
||||
</ol> |
||||
</div> |
||||
<div class="button-container"> |
||||
<a href="{{ next_url }}" class="rounded-button">Continuer</a> |
||||
</div> |
||||
<div class="support-link"> |
||||
<p>Un problème ? <a href="mailto:support@padelclub.app" class="styled-link">Contactez-nous</a></p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<style> |
||||
.button-container { |
||||
margin-bottom: 20px; |
||||
} |
||||
</style> |
||||
{% endblock %} |
||||
@ -1,13 +1,12 @@ |
||||
<html> |
||||
<body> |
||||
{% autoescape off %} |
||||
Bienvenue {{ user.username }} ! |
||||
|
||||
Veuillez cliquer sur le lien suivant pour activer votre compte: |
||||
padelclub://{{ domain }}{% url 'activate' uidb64=uid token=token %} |
||||
<br><br>Veuillez cliquer sur le lien suivant pour activer votre compte :<br> |
||||
<a href="https://{{ domain }}{% url 'activate' uidb64=uid token=token %}">Activer mon compte</a> |
||||
|
||||
Si le lien ne marche pas, cliquer ici : |
||||
http://{{ domain }}{% url 'activate' uidb64=uid token=token %} |
||||
|
||||
Une fois votre compte activé, connectez-vous dans l'app. |
||||
|
||||
A tout de suite sur Padel Club ! |
||||
<br><br>À tout de suite sur <a href="https://padelclub.app">Padel Club</a> ! |
||||
{% endautoescape %} |
||||
</body> |
||||
</html> |
||||
|
||||
@ -0,0 +1,100 @@ |
||||
<!DOCTYPE html> |
||||
{% load static %} |
||||
{% load qr_code %} |
||||
<html> |
||||
<head> |
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" /> |
||||
<link rel="stylesheet" href="{% static 'tournaments/css/foundation.min.css' %}" /> |
||||
<link rel="stylesheet" href="{% static 'tournaments/css/basics.css' %}" /> |
||||
<link rel="stylesheet" href="{% static 'tournaments/css/style.css' %}" /> |
||||
<link rel="stylesheet" href="{% static 'tournaments/css/broadcast.css' %}" /> |
||||
|
||||
<link rel="icon" type="image/png" href="{% static 'tournaments/images/favicon.png' %}" /> |
||||
|
||||
<title>Matchs</title> |
||||
|
||||
<script src="{% static 'tournaments/js/alpine.min.js' %}"></script> |
||||
<!-- Matomo --> |
||||
<script> |
||||
var _paq = window._paq = window._paq || []; |
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */ |
||||
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]); |
||||
_paq.push(["setDoNotTrack", true]); |
||||
_paq.push(['trackPageView']); |
||||
_paq.push(['enableLinkTracking']); |
||||
(function() { |
||||
var u="//matomo.padelclub.app/"; |
||||
_paq.push(['setTrackerUrl', u+'matomo.php']); |
||||
_paq.push(['setSiteId', '1']); |
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; |
||||
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); |
||||
})(); |
||||
</script> |
||||
<!-- End Matomo Code --> |
||||
|
||||
</head> |
||||
|
||||
<body x-data="{ |
||||
paginatedMatches: null, |
||||
active: 1, |
||||
retrieveMatches() { |
||||
fetch('/tournament/{{ tournament.id }}/prog/json/') |
||||
.then(res => res.json()) |
||||
.then((data) => { |
||||
this.paginatedMatches = this.paginate(data, 8) |
||||
}) |
||||
}, |
||||
paginate(array, pageSize) { |
||||
let paginatedArray = []; |
||||
for (let i = 0; i < array.length; i += pageSize) { |
||||
paginatedArray.push(array.slice(i, i + pageSize)); |
||||
} |
||||
return paginatedArray; |
||||
}, |
||||
loop() { |
||||
this.retrieveMatches() |
||||
setInterval(() => { |
||||
this.retrieveMatches() |
||||
this.active = this.active === this.paginatedMatches.length ? 1 : this.active+1 |
||||
}, 15000) |
||||
} |
||||
|
||||
}" x-init="loop()"> |
||||
|
||||
|
||||
<header> |
||||
<div id="header"> |
||||
<div class="left-content bubble"> |
||||
<img src="{% static 'tournaments/images/PadelClub_logo_512.png' %}" alt="logo" class="logo"> |
||||
<div class="left-margin"> |
||||
<h1 class="club">{{ tournament.broadcast_event_display_name }}</h1> |
||||
<h1 class="event">Programmation {{ tournament.broadcast_display_name }}</h1> |
||||
</div> |
||||
</div> |
||||
<div class="right-content">{% qr_from_text qr_code_url options=qr_code_options %}</div> |
||||
</div> |
||||
</header> |
||||
|
||||
<div class="wrapper"> |
||||
<main> |
||||
|
||||
<div class="grid-x padding-bottom"> |
||||
|
||||
<template x-for="i in paginatedMatches.length" > |
||||
<template x-for="match in paginatedMatches[i-1]" > |
||||
|
||||
<div class="cell medium-6 large-3 my-block" x-show="active === i"> |
||||
|
||||
<template x-if="!match.empty"> |
||||
{% include 'tournaments/broadcast/broadcasted_match.html' %} |
||||
</template> |
||||
</div> |
||||
</template> |
||||
</template> |
||||
|
||||
</div> |
||||
|
||||
</main> |
||||
</div> |
||||
</body> |
||||
</html> |
||||
@ -1,5 +1,12 @@ |
||||
|
||||
<nav class="margin10"> |
||||
<a href="{% url 'index' %}">Accueil</a> |
||||
<a href="{% url 'clubs' %}">Clubs</a> |
||||
<a href="{% url 'index' %}" class="orange">Accueil</a> |
||||
<a href="{% url 'clubs' %}" class="orange">Clubs</a> |
||||
{% if user.is_authenticated %} |
||||
<a href="{% url 'my-tournaments' %}" class="orange">Mes tournois</a> |
||||
<a href="{% url 'profile' %}">Mon compte</a> |
||||
{% else %} |
||||
<a href="{% url 'login' %}">Se connecter</a> |
||||
{% endif %} |
||||
<a href="{% url 'download' %}" class="download-button">Ajouter vos tournois</a> |
||||
</nav> |
||||
|
||||
@ -1,25 +1,30 @@ |
||||
<nav class="margin10"> |
||||
|
||||
<a href="{% url 'tournament-info' tournament.id %}" class="topmargin5">Informations</a> |
||||
<a href="{% url 'tournament-info' tournament.id %}" class="topmargin5 orange">Informations</a> |
||||
|
||||
{% if tournament.display_matches or tournament.display_group_stages %} |
||||
<a href="{% url 'tournament' tournament.id %}" class="topmargin5">Matches</a> |
||||
<a href="{% url 'tournament' tournament.id %}" class="topmargin5 orange">Matches</a> |
||||
{% endif %} |
||||
|
||||
{% if tournament.display_group_stages %} |
||||
<a href="{% url 'group-stages' tournament.id %}" class="topmargin5">Poules</a> |
||||
<a href="{% url 'group-stages' tournament.id %}" class="topmargin5 orange">Poules</a> |
||||
{% endif %} |
||||
|
||||
{% if tournament.display_summons %} |
||||
<a href="{% url 'tournament-summons' tournament.id %}" class="topmargin5">Convocations</a> |
||||
<a href="{% url 'tournament-summons' tournament.id %}" class="topmargin5 orange">Convocations</a> |
||||
{% endif %} |
||||
|
||||
{% if tournament.display_teams %} |
||||
<a href="{% url 'tournament-teams' tournament.id %}" class="topmargin5">Équipes</a> |
||||
<a href="{% url 'tournament-teams' tournament.id %}" class="topmargin5 orange">Équipes</a> |
||||
{% endif %} |
||||
|
||||
{% if tournament.display_rankings %} |
||||
<a href="{% url 'tournament-rankings' tournament.id %}" class="topmargin5">Classement</a> |
||||
<a href="{% url 'tournament-rankings' tournament.id %}" class="topmargin5 orange">Classement</a> |
||||
{% endif %} |
||||
|
||||
{% if user.is_authenticated %} |
||||
<a href="{% url 'profile' %}" class="topmargin5">Mon compte</a> |
||||
{% else %} |
||||
<a href="{% url 'login' %}" class="topmargin5">Se connecter</a> |
||||
{% endif %} |
||||
</nav> |
||||
|
||||
@ -0,0 +1,66 @@ |
||||
<div class="cell medium-12 large-3 my-block"> |
||||
<div class="bubble"> |
||||
<label class="matchtitle">{{ player.name }}</label> |
||||
|
||||
<div> |
||||
<div class="match-result bottom-border" style="padding-right: 10px;"> |
||||
<div class="player"> |
||||
<div class="single-line"> |
||||
<strong>{{ player.clean_club_name }}</strong> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="match-result bottom-border"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Classement</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ player.format_ordinal }}</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="match-result bottom-border"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Age</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers"> |
||||
{{ player.calculate_age|default:"?" }} ans |
||||
</span> |
||||
</div> |
||||
</div> |
||||
|
||||
|
||||
{% if player.points %} |
||||
<div class="match-result bottom-border"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Points</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ player.points|default:"0" }} pts</span> |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
|
||||
{% if player.tournament_played %} |
||||
<div class="match-result"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Tournois joués</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ player.tournament_played|default:"0" }}</span> |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
@ -0,0 +1,54 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
|
||||
{% block head_title %}Équipes du {{ tournament.display_name }}{% endblock %} |
||||
{% block first_title %}{{ tournament.event.display_name }}{% endblock %} |
||||
{% block second_title %}{{ tournament.display_name }}{% endblock %} |
||||
|
||||
{% block content %} |
||||
<div class="grid-x grid-margin-x"> |
||||
<style> |
||||
.bubble { |
||||
height: 100%; |
||||
} |
||||
</style> |
||||
<div class="cell medium-12"> |
||||
<h1 class="club my-block topmargin20">{{ team.formatted_team_names }}</h1> |
||||
<div class="grid-x"> |
||||
{% for player in team.player_registrations.all %} |
||||
{% include 'tournaments/player_row.html' with player=player %} |
||||
{% endfor %} |
||||
{% include 'tournaments/team_stats.html' %} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="grid-x grid-margin-x"> |
||||
{% with upcoming_matches=team.get_upcoming_matches %} |
||||
{% if upcoming_matches %} |
||||
<!-- Upcoming Matches --> |
||||
<div class="cell medium-12"> |
||||
<h1 class="club my-block topmargin20">Prochains matchs</h1> |
||||
<div class="grid-x"> |
||||
{% for match in upcoming_matches %} |
||||
{% include 'tournaments/match_cell.html' %} |
||||
{% endfor %} |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
{% endwith %} |
||||
|
||||
{% with completed_matches=team.get_completed_matches %} |
||||
{% if completed_matches %} |
||||
<!-- Completed Matches --> |
||||
<div class="cell medium-12"> |
||||
<h1 class="club my-block topmargin20">Matchs terminés</h1> |
||||
<div class="grid-x"> |
||||
{% for match in completed_matches %} |
||||
{% include 'tournaments/match_cell.html' %} |
||||
{% endfor %} |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
{% endwith %} |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,84 @@ |
||||
<div class="cell medium-12 large-3 my-block"> |
||||
<div class="dark_bubble"> |
||||
<label class="matchtitle winner">Statistiques du tournoi</label> |
||||
|
||||
<div> |
||||
{% with stats=team.get_statistics %} |
||||
|
||||
<div class="match-result bottom-border"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Poids de la paire</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ stats.weight }}</span> |
||||
</div> |
||||
</div> |
||||
|
||||
{% if stats.final_ranking %} |
||||
<div class="match-result bottom-border"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Classement final</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ stats.final_ranking }}</span> |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
|
||||
{% if stats.points_earned %} |
||||
<div class="match-result bottom-border"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Points gagnés</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ stats.points_earned }} pts</span> |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
|
||||
{% if stats.initial_stage %} |
||||
<div class="match-result bottom-border"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Départ</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ stats.initial_stage }}</span> |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
|
||||
<div class="match-result bottom-border"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Matchs joués</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ stats.matches_played }}</span> |
||||
</div> |
||||
</div> |
||||
|
||||
{% if stats.victory_ratio %} |
||||
<div class="match-result"> |
||||
<div class="player"> |
||||
<div class="semibold"> |
||||
<strong>Ratio victoires</strong> |
||||
</div> |
||||
</div> |
||||
<div class="scores"> |
||||
<span class="score ws numbers">{{ stats.victory_ratio }}</span> |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
{% endwith %} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
@ -0,0 +1,48 @@ |
||||
import re |
||||
|
||||
class LicenseValidator: |
||||
def __init__(self, license_id: str): |
||||
self.license_id = license_id.upper() # Ensure uppercase for consistency |
||||
|
||||
@property |
||||
def stripped_license(self) -> str: |
||||
# Remove leading zero if present and match only the numeric part |
||||
license_without_leading_zero = self.license_id.lstrip("0") |
||||
match = re.match(r"^[0-9]{6,8}", license_without_leading_zero) |
||||
if match: |
||||
return match.group(0) |
||||
return 'licence invalide' |
||||
|
||||
@property |
||||
def computed_licence_id(self) -> str: |
||||
return f"{self.stripped_license}{self.computed_license_key}" |
||||
|
||||
@property |
||||
def computed_license_key(self) -> str: |
||||
stripped = self.stripped_license |
||||
if stripped and stripped.isdigit(): |
||||
int_value = int(stripped) |
||||
value = (int_value - 1) % 23 |
||||
char_code = ord('A') + value |
||||
|
||||
# Adjust for letters to skip: I, O, Q |
||||
if char_code >= ord('I'): |
||||
char_code += 1 |
||||
if char_code >= ord('O'): |
||||
char_code += 1 |
||||
if char_code >= ord('Q'): |
||||
char_code += 1 |
||||
|
||||
return chr(char_code) |
||||
return 'aucune clé de licence' |
||||
|
||||
def validate_license(self) -> bool: |
||||
if not self.license_id or len(self.license_id) < 7: |
||||
return False # Invalid length for a license ID |
||||
|
||||
# Separate the numeric part and the letter |
||||
numeric_part = self.license_id[:-1] |
||||
given_letter = self.license_id[-1] |
||||
|
||||
# Verify that the last character matches the computed license key |
||||
return self.computed_license_key == given_letter |
||||
@ -0,0 +1,115 @@ |
||||
import csv |
||||
import os |
||||
import re |
||||
from datetime import datetime |
||||
from django.conf import settings |
||||
|
||||
from tournaments.models.enums import FederalCategory |
||||
|
||||
def clean_licence_id(licence_id): |
||||
# This regex matches the trailing letters (non-digits) and removes them |
||||
cleaned_licence_id = re.sub(r'\D+$', '', str(licence_id)) # \D+ matches non-digits at the end |
||||
return cleaned_licence_id |
||||
|
||||
def get_player_name_from_csv(category, licence_id, base_folder=None): |
||||
""" |
||||
Search for a player's first name, last name, and rank in the most recent rankings file. |
||||
|
||||
:param licence_id: The licence ID to search for. |
||||
:param base_folder: Base folder containing the rankings folder. |
||||
:return: A tuple (first_name, last_name, rank) or (None, None, None) if not found. |
||||
""" |
||||
if base_folder is None: |
||||
base_folder = settings.STATIC_ROOT |
||||
|
||||
folder_path = os.path.join(base_folder, "rankings") |
||||
|
||||
if licence_id: |
||||
cleaned_licence_id = clean_licence_id(licence_id) |
||||
else: |
||||
cleaned_licence_id = None |
||||
|
||||
def extract_date(file_name): |
||||
""" |
||||
Extract the date (MM-YYYY) from the file name and return it as a datetime object. |
||||
""" |
||||
match = re.search(r"(\d{2}-\d{4})", file_name) |
||||
if match: |
||||
return datetime.strptime(match.group(1), "%m-%Y") |
||||
return None |
||||
|
||||
def find_most_recent_file(file_type): |
||||
""" |
||||
Find the most recent file for the given type (-MESSIEURS- or -DAMES-). |
||||
""" |
||||
files = [ |
||||
f for f in os.listdir(folder_path) |
||||
if file_type in f and re.search(r"\d{2}-\d{4}", f) |
||||
] |
||||
files_with_dates = [(f, extract_date(f) or datetime.min) for f in files] |
||||
if not files_with_dates: |
||||
return None |
||||
# Sort by date in descending order |
||||
files_with_dates.sort(key=lambda x: x[1], reverse=True) |
||||
return os.path.join(folder_path, files_with_dates[0][0]) |
||||
|
||||
def search_file(file_path, is_woman): |
||||
if not file_path or not os.path.exists(file_path): |
||||
print("no file found") |
||||
return None, False |
||||
|
||||
last_rank = None |
||||
last_rank_count = 0 |
||||
with open(file_path, newline='', encoding='utf-8') as file: |
||||
reader = csv.reader(file, delimiter=';') |
||||
rows = list(reader) # Read all rows at once to process later |
||||
|
||||
|
||||
if cleaned_licence_id: |
||||
for row in rows: |
||||
if len(row) >= 15: # Ensure row has enough columns |
||||
current_licence_id = row[5] |
||||
if current_licence_id == str(cleaned_licence_id): |
||||
data = { |
||||
"first_name": row[3], # 4th column: first name |
||||
"last_name": row[2], # 3rd column: last name |
||||
"rank": row[1], |
||||
"points": row[6], |
||||
"assimilation": row[7], |
||||
"tournament_count": row[8], |
||||
"ligue_name": row[9], |
||||
"club_name": row[11], |
||||
"birth_year": row[14], |
||||
"is_woman": is_woman, |
||||
} |
||||
return data, True |
||||
|
||||
# Determine the rank for an unranked player |
||||
if rows: |
||||
# Find the last rank in the file |
||||
last_row = rows[-1] |
||||
if len(last_row) >= 2: |
||||
last_rank = last_row[1] |
||||
|
||||
# Count how many times the last rank appears |
||||
last_rank_count = sum(1 for row in rows if len(row) >= 2 and row[1] == last_rank) |
||||
|
||||
if last_rank is not None: |
||||
unranked_rank = int(last_rank) + last_rank_count + 1 |
||||
data = { |
||||
"rank": str(unranked_rank), |
||||
"is_woman": is_woman |
||||
} |
||||
return data, False |
||||
|
||||
return None, False |
||||
|
||||
|
||||
dames_file = find_most_recent_file("CLASSEMENT-PADEL-DAMES-") |
||||
result, found = search_file(dames_file, True) |
||||
if found or category is FederalCategory.WOMEN: |
||||
return result, found |
||||
|
||||
messieurs_file = find_most_recent_file("CLASSEMENT-PADEL-MESSIEURS-") |
||||
result, found = search_file(messieurs_file, False) |
||||
return result, found |
||||
Loading…
Reference in new issue