initial work in progress

online_registration
Raz 12 months ago
parent 286746e3cd
commit 3066f184f6
  1. 4
      padelclub_backend/settings.py
  2. 88
      tournaments/forms.py
  3. 7
      tournaments/models/tournament.py
  4. 80
      tournaments/templates/register_tournament.html
  5. 30
      tournaments/templates/registration/login.html
  6. 30
      tournaments/templates/registration/profile.html
  7. 24
      tournaments/templates/registration/signup.html
  8. 4
      tournaments/templates/tournaments/base.html
  9. 6
      tournaments/templates/tournaments/navigation_base.html
  10. 11
      tournaments/templates/tournaments/tournament_info.html
  11. 8
      tournaments/urls.py
  12. 44
      tournaments/utils/licence_validator.py
  13. 33
      tournaments/utils/player_search.py
  14. 234
      tournaments/views.py

@ -143,3 +143,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
from .settings_local import * from .settings_local import *
from .settings_app import * from .settings_app import *
# settings.py
LOGIN_REDIRECT_URL = '/' # Redirect to the homepage after login
LOGOUT_REDIRECT_URL = '/' # Redirect to the homepage after logout

@ -1,6 +1,8 @@
from django.contrib.auth.forms import UserCreationForm, UserChangeForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django import forms from django import forms
from .models import CustomUser from .models import CustomUser
import re # Import the re module for regular expressions
from .utils.licence_validator import LicenseValidator
class CustomUserCreationForm(UserCreationForm): class CustomUserCreationForm(UserCreationForm):
@ -8,6 +10,24 @@ class CustomUserCreationForm(UserCreationForm):
model = CustomUser model = CustomUser
fields = UserCreationForm.Meta.fields + ('umpire_code', 'clubs', 'phone', 'first_name', 'last_name', 'licence_id', 'country') 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')
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 CustomUserChangeForm(UserChangeForm):
class Meta: class Meta:
@ -17,3 +37,71 @@ class CustomUserChangeForm(UserChangeForm):
class SimpleForm(forms.Form): class SimpleForm(forms.Form):
# A single field for user input (e.g., a text field) # A single field for user input (e.g., a text field)
name = forms.CharField(label='Enter your name!', max_length=100) 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')
mobile_number = forms.CharField(
label='Téléphone (facultatif)',
max_length=15,
required=False
)
def is_registration_complete(self):
# Check if the minimum number of players (2) is met
return len(self.players) >= 2
def clean_mobile_number(self):
mobile_number = self.cleaned_data.get('mobile_number')
if mobile_number:
# Basic regex for mobile numbers, matching common formats
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 license (avec la lettre)', max_length=20)
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
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(self):
cleaned_data = super().clean()
# Retrieve the cleaned licence_id (from the previous step)
licence_id = cleaned_data.get('licence_id')
last_name = cleaned_data.get('last_name')
# Return the cleaned data with any modifications applied
return cleaned_data

@ -870,6 +870,13 @@ class Tournament(models.Model):
return False return False
return True return True
def online_register_is_enabled(self):
if self.supposedly_in_progress():
return False
if self.end_date is not None:
return False
return True
class MatchGroup: class MatchGroup:
def __init__(self, name, matches, formatted_schedule): def __init__(self, name, matches, formatted_schedule):
self.name = name self.name = name

@ -0,0 +1,80 @@
{% 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 au {{ tournament.display_name }} {{ tournament.get_federal_age_category_display}}</h1 >
<div class="bubble">
{% if registration_successful %}
<p>Merci, l'inscription a bien été envoyé au juge-arbitre.</p>
{% else %}
<form method="post">
{% csrf_token %}
<!-- Team Registration Form -->
<div>
{{ 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 %}
<ul>
{% for player in current_players %}
<li>{{ player.first_name }} {{ player.last_name }} ({{ player.licence_id }})</li>
{% endfor %}
</ul>
{% endif %}
<!-- Add Player Form -->
{% if current_players|length < 2 %}
<div>
{{ add_player_form.licence_id.label_tag }}
{{ add_player_form.licence_id }}
{% if add_player_form.first_tournament %}
Précisez les informations du joueur :
{{ add_player_form.first_name.label_tag }}
{{ add_player_form.first_name }}
{{ add_player_form.last_name.label_tag }}
{{ add_player_form.last_name }}
{% endif %}
<button type="submit" name="add_player" class="button">Ajouter un joueur</button>
</div>
{% endif %}
<!-- Show players added to the team only if there are players added -->
{% if current_players|length >= 2 %}
<button type="submit" name="register_team" class="button">Confirmer l'inscription</button>
{% endif %}
</form>
{% if registration_successful %}
<p>Registration was successful! Your team and players have been registered.</p>
{% endif %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,30 @@
<!-- templates/registration/login.html -->
{% extends 'tournaments/base.html' %}
{% block head_title %} {% endblock %}
{% block first_title %}{% endblock %}
{% block second_title %}{% endblock %}
{% block content %}
{% load static %}
{% load tz %}
<div class="grid-x">
<div class="cell medium-6 large-6 my-block">
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<label for="username">Username:</label>
<input type="text" name="username" id="username" value="{{ pre_login_username }}" required>
<label for="password">Password:</label>
<input type="password" name="password" id="password" value="{{ pre_login_password }}" required>
<button type="submit" class="button">Se connecter</button>
</form>
<p>Pas encore de compte ? <a href="{% url 'signup' %}"">Créer le tout de suite !</a>.</p>
</div>
</div>
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endblock %}

@ -0,0 +1,30 @@
{% extends 'tournaments/base.html' %}
{% block head_title %} {% endblock %}
{% block first_title %}{% endblock %}
{% block second_title %}{% endblock %}
{% block content %}
{% include 'tournaments/navigation_base.html' %}
{% load static %}
{% load tz %}
<div class="grid-x">
{% if tournaments %}
<div class="cell medium-6 large-6 topblock my-block">
<h1 class="club my-block topmargin20">Vos tournois, {{ user_name }}</h1 >
<div class="bubble">
{% for tournament in tournaments %}
{% include 'tournaments/tournament_row.html' %}
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% endblock %}

@ -0,0 +1,24 @@
<!-- templates/registration/signup.html -->
{% extends 'tournaments/base.html' %}
{% block first_title %}{% endblock %}
{% block second_title %}{% endblock %}
{% block content %}
{% load static %}
{% load tz %}
<div class="grid-x">
<div class="cell medium-6 large-6 my-block">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="button">Créer votre compte</button>
</form>
</div>
</div>
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endblock %}

@ -49,7 +49,11 @@
class="logo inline" class="logo inline"
/> />
<div class="inline padding-left"> <div class="inline padding-left">
{% if user.is_authenticated %}
<h1 class="club">Bienvenue sur Padel Club, {{ user.username }}</h1>
{% else %}
<h1 class="club">{% block first_title %}Page Title{% endblock %}</h1> <h1 class="club">{% block first_title %}Page Title{% endblock %}</h1>
{% endif %}
<h1 class="event">{% block second_title %}Page Title{% endblock %}</h1> <h1 class="event">{% block second_title %}Page Title{% endblock %}</h1>
</div> </div>
</a> </a>

@ -2,4 +2,10 @@
<nav class="margin10"> <nav class="margin10">
<a href="{% url 'index' %}">Accueil</a> <a href="{% url 'index' %}">Accueil</a>
<a href="{% url 'clubs' %}">Clubs</a> <a href="{% url 'clubs' %}">Clubs</a>
{% if user.is_authenticated %}
<a href="{% url 'profile' %}">Mon compte</a>
<a href="{% url 'logout' %}">Se déconnecter</a>
{% else %}
<a href="{% url 'login' %}">Se connecter</a>
{% endif %}
</nav> </nav>

@ -38,6 +38,17 @@
</p> </p>
{% endif %} {% endif %}
{% if tournament.online_register_is_enabled %}
{% if is_registered %}
<p>
<a href="{% url 'unregister_tournament' tournament.id %}" class="button danger">Unregister from Tournament</a>
</p>
{% else %}
<p>
<a href="{% url 'register_tournament' tournament.id %}?reset=true" class="button">Register for Tournament</a>
</p>
{% endif %}
{% endif %}
</div> </div>
</div> </div>

@ -1,3 +1,4 @@
from django.contrib.auth import views as auth_views
from django.urls import include, path from django.urls import include, path
from . import views from . import views
@ -38,4 +39,11 @@ urlpatterns = [
path('terms-of-use/', views.terms_of_use, name='terms-of-use'), path('terms-of-use/', views.terms_of_use, name='terms-of-use'),
path('utils/xls-to-csv/', views.xls_to_csv, name='xls-to-csv'), path('utils/xls-to-csv/', views.xls_to_csv, name='xls-to-csv'),
path('mail-test/', views.simple_form_view, name='mail-test'), path('mail-test/', views.simple_form_view, name='mail-test'),
path('login/', auth_views.LoginView.as_view(), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('signup/', views.signup, name='signup'), # URL pattern for signup
path('profile/', views.profile, name='profile'), # URL pattern for signup
path('tournaments/<str:tournament_id>/register/', views.register_tournament, name='register_tournament'),
path('tournaments/<str:tournament_id>/unregister/', views.unregister_tournament, name='unregister_tournament'),
] ]

@ -0,0 +1,44 @@
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_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,33 @@
import csv
import os
import re
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(licence_id):
# Define the file path
file_path = '/Users/razmig/Documents/XLR Sport/padelclub_backend/tournaments/static/rankings/CLASSEMENT-PADEL-MESSIEURS-11-2024.csv'
cleaned_licence_id = clean_licence_id(licence_id)
print(cleaned_licence_id)
# Open the file and read it
with open(file_path, newline='', encoding='utf-8') as file:
reader = csv.reader(file, delimiter=';')
# Iterate through each row in the CSV
for row in reader:
# Ensure the row is not empty and has the expected number of columns
if len(row) >= 13:
current_licence_id = row[5] # The 5th column contains the licence_id
# Check if the current row matches the given licence_id
if current_licence_id == str(cleaned_licence_id):
# Return first name and last name from the row (3rd and 4th columns)
first_name = row[3] # 4th column: first name
last_name = row[2] # 3rd column: last name
return first_name, last_name
# Return None if no match is found
return None, None

@ -33,6 +33,23 @@ from .forms import SimpleForm
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from datetime import timedelta from datetime import timedelta
from django.utils import timezone from django.utils import timezone
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login
from django.urls import reverse
from django.contrib import messages
from .forms import TournamentRegistrationForm, AddPlayerForm
from .utils.licence_validator import LicenseValidator
from .utils.player_search import get_player_name_from_csv
from django.contrib.auth.decorators import login_required
from .forms import SimpleCustomUserCreationForm
from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes
def index(request): def index(request):
@ -85,10 +102,23 @@ def future_tournaments(club_id):
def tournament_info(request, tournament_id): def tournament_info(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id) tournament = get_object_or_404(Tournament, pk=tournament_id)
is_registered = False
if request.user.is_authenticated:
# Assuming user's licence_id is stored in the user profile (e.g., request.user.profile.licence_id)
user_licence_id = request.user.licence_id
# Check if there is a PlayerRegistration for this user in this tournament
is_registered = PlayerRegistration.objects.filter(
licence_id__startswith=user_licence_id,
team_registration__tournament=tournament
).exists()
return render(request, 'tournaments/tournament_info.html', { return render(request, 'tournaments/tournament_info.html', {
'tournament': tournament, 'tournament': tournament,
'is_registered': is_registered,
}) })
def tournaments(request): def tournaments(request):
filter = int(request.GET.get('filter')) filter = int(request.GET.get('filter'))
@ -315,7 +345,8 @@ def activate(request, uidb64, token):
if user is not None and account_activation_token.check_token(user, token): if user is not None and account_activation_token.check_token(user, token):
user.is_active = True user.is_active = True
user.save() user.save()
return HttpResponse('Votre email est confirmé. Vous pouvez maintenant vous connecter.') login(request, user)
return redirect('index') # Redirect to the homepage or any other page you prefer
else: else:
return HttpResponse('Le lien est invalide.') return HttpResponse('Le lien est invalide.')
@ -483,3 +514,204 @@ def send_email(mail, name):
email = EmailMessage(subject, body, to=[mail]) email = EmailMessage(subject, body, to=[mail])
email.send() email.send()
@csrf_exempt
def signup(request):
if request.method == 'POST':
form = SimpleCustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_active = False # Deactivate account until email is verified
user.save()
# Send verification email
send_verification_email(request, user)
request.session['pre_login_username'] = user.username
request.session['pre_login_password'] = user.password # Store hashed password, or handle with caution
messages.success(request, "Votre compte a été créé ! Veuillez vérifier votre e-mail pour confirmer votre compte.")
return redirect('login') # Redirect to login page or a custom message page
else:
form = SimpleCustomUserCreationForm()
return render(request, 'registration/signup.html', {'form': form})
def send_verification_email(request, user):
current_site = get_current_site(request)
mail_subject = 'Activez votre compte Padel Club !'
message = render_to_string('tournaments/acc_active_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
email = EmailMessage(mail_subject, message, to=[user.email])
email.send()
@login_required
def profile(request):
user = request.user # Get the currently authenticated user
# Query tournaments where the user is registered
tournaments = Tournament.objects.all()
return render(request, 'registration/profile.html', {
'tournaments': tournaments,
'user_name': user.username
})
def register_tournament(request, tournament_id):
tournament = get_object_or_404(Tournament, id=tournament_id)
registration_successful = False # Flag for registration status
team_form = None
add_player_form = None
# Process forms
if request.method == 'POST':
team_form = TournamentRegistrationForm(request.POST)
add_player_form = AddPlayerForm(request.POST)
# Check if the add player form is submitted
if 'add_player' in request.POST and add_player_form.is_valid():
player_data = add_player_form.cleaned_data
# Validate the license ID before adding the player
licence_id = player_data['licence_id'].upper()
# Instantiate your custom validator and validate the license ID
validator = LicenseValidator(licence_id)
if validator.validate_license() is False:
messages.error(request, f"Le numéro de licence est invalide, la lettre ne correspond pas. {validator.computed_license_key}")
return render(request, 'register_tournament.html', {
'team_form': team_form,
'add_player_form': add_player_form,
'tournament': tournament,
'registration_successful': registration_successful,
'current_players': request.session['team_registration'],
})
# Check if the player with the same licence_id already exists in the session
existing_players = [player['licence_id'] for player in request.session['team_registration']]
if licence_id in existing_players:
messages.error(request, 'This player is already added to the team.')
return render(request, 'register_tournament.html', {
'team_form': team_form,
'add_player_form': add_player_form,
'tournament': tournament,
'registration_successful': registration_successful,
'current_players': request.session['team_registration'],
})
else:
# Check if a PlayerRegistration with the same licence_id already exists in the database
stripped_license = validator.stripped_license
if validate_license_id(stripped_license, tournament):
messages.error(request, 'A player with this licence ID is already registered in a team.')
return render(request, 'register_tournament.html', {
'team_form': team_form,
'add_player_form': add_player_form,
'tournament': tournament,
'registration_successful': registration_successful,
'current_players': request.session['team_registration'],
})
elif add_player_form.names_is_valid():
request.session['team_registration'].append(player_data)
request.session.modified = True # Ensure session is updated
else:
if add_player_form.first_tournament is False:
# Retrieve player names from the CSV file
first_name, last_name = get_player_name_from_csv(licence_id)
if first_name and last_name:
player_data['first_name'] = first_name
player_data['last_name'] = last_name
# If validation passes, add the player to the session without clearing previous ones
request.session['team_registration'].append(player_data)
request.session.modified = True # Ensure session is updated
else:
add_player_form.first_tournament = True
# Check if the team registration form is valid and finalize the registration
elif 'register_team' in request.POST and team_form.is_valid():
team_registration = TeamRegistration.objects.create(
tournament=tournament,
registration_date=timezone.now()
)
# Create PlayerRegistration objects for each player in the session
for player_data in request.session['team_registration']:
player_registration = PlayerRegistration.objects.create(
team_registration=team_registration,
first_name=player_data['first_name'],
last_name=player_data['last_name'],
licence_id=player_data['licence_id'],
email = player_data.get('email', None),
phone_number = player_data.get('phone_number', None)
)
request.session['team_registration'] = []
registration_successful = True
else:
request.session['team_registration'] = []
initial_data = {}
# Add the authenticated user to the session as the first player if not already added
if request.user.is_authenticated:
initial_data = {
'email': request.user.email,
'phone': request.user.phone,
}
existing_players = [player['licence_id'] for player in request.session['team_registration']]
if request.user.licence_id not in existing_players:
# Add the authenticated user as the first player in the session
player_data = {
'first_name': request.user.first_name,
'last_name': request.user.last_name.upper(),
'email': request.user.email,
'phone': request.user.phone,
'licence_id': request.user.licence_id,
}
request.session['team_registration'].insert(0, player_data) # Add them as the first player
request.session.modified = True # Ensure session is updated
team_form = TournamentRegistrationForm(initial=initial_data)
add_player_form = AddPlayerForm()
return render(request, 'register_tournament.html', {
'team_form': team_form,
'add_player_form': add_player_form,
'tournament': tournament,
'registration_successful': registration_successful,
'current_players': request.session['team_registration'],
})
@login_required
def unregister_tournament(request, tournament_id):
user_licence_id = request.user.licence_id
player_registration = PlayerRegistration.objects.filter(
licence_id__startswith=user_licence_id,
team_registration__tournament_id=tournament_id
).first() # Get the first match, if any
if player_registration:
team_registration = player_registration.team_registration # Get the related TeamRegistration
team_registration.delete() # Delete the team registration
request.session['team_registration'] = []
return redirect('tournament-info', tournament_id=tournament_id)
def validate_license_id(licence_id, tournament):
teams = TeamRegistration.objects.filter(tournament=tournament)
# Check if any player in any team in the tournament already has this licence_id
# Normalize the licence ID before querying
# Loop through each team and check if any of its players has the same licence_id
for team in teams:
for player in team.playerregistration_set.all():
if player.licence_id.startswith(licence_id):
return True
# If all checks pass, return True (you can add further logic here if needed)
return False

Loading…
Cancel
Save