You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
padelclub_backend/tournaments/views.py

1149 lines
44 KiB

# Standard library imports
import os
import csv
import pandas as pd
from .utils.extensions import create_random_filename
from api.serializers import GroupStageSerializer, MatchSerializer, PlayerRegistrationSerializer, TeamRegistrationSerializer, TeamScoreSerializer
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth import logout
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.views import PasswordResetCompleteView
from django.shortcuts import redirect
from django.contrib.auth import login
from django.contrib.auth import get_user_model
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse, HttpResponse
from django.utils.encoding import force_str
from django.utils.http import urlsafe_base64_decode
from django.urls import reverse
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.contrib.admin.views.decorators import staff_member_required
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from django.views.generic import View
from django.db.models import Q
from .models import Club, Tournament, CustomUser, Event, Round, Match, TeamScore, TeamRegistration, PlayerRegistration, UserOrigin
from datetime import datetime, timedelta
import time
import json
import asyncio
import zipfile
from api.tokens import account_activation_token
# Third-party imports
from qr_code.qrcode.utils import QRCodeOptions
# Django imports
from django.http import Http404
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.template.loader import render_to_string
from django.contrib import messages
from django.contrib.sites.shortcuts import get_current_site
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import PasswordResetConfirmView
from django.core.mail import EmailMessage
from django.views.decorators.csrf import csrf_protect
from .services.tournament_registration import TournamentRegistrationService
from .services.tournament_unregistration import TournamentUnregistrationService
from django.core.exceptions import ValidationError
from .forms import (
ProfileUpdateForm,
SimpleCustomUserCreationForm,
SimpleForm
)
from .utils.apns import send_push_notification
from .utils.licence_validator import LicenseValidator
from django.views.generic.edit import UpdateView
from .forms import CustomPasswordChangeForm
def index(request):
now = timezone.now()
thirty_days_ago = now - timedelta(days=30)
thirty_days_future = now + timedelta(days=30)
club_id = request.GET.get('club')
if club_id:
tournaments = tournaments_query(Q(end_date__isnull=True), club_id, True, 50)
else:
tournaments = tournaments_query(Q(end_date__isnull=True, start_date__gte=thirty_days_ago, start_date__lte=thirty_days_future), club_id, True, 50)
display_tournament = [t for t in tournaments if t.display_tournament()]
live = []
future = []
for t in display_tournament:
if t.supposedly_in_progress():
live.append(t)
elif t.starts_in_the_future():
future.append(t)
clean_ended_tournaments = tournaments_query(Q(end_date__isnull=False), club_id, False, 50)
clean_ended_tournaments = [t for t in clean_ended_tournaments if t.display_tournament()]
ended_tournaments = [t for t in display_tournament if t.should_be_over()]
# Combine both lists
finished = clean_ended_tournaments + ended_tournaments
# Sort the combined list by start_date in descending order
finished.sort(key=lambda t: t.start_date, reverse=True)
club = None
if club_id:
club = get_object_or_404(Club, pk=club_id)
return render(
request,
"tournaments/tournaments.html",
{
'future': future[:10],
'live': live[:10],
'ended': finished[:10],
'club': club,
}
)
def tournaments_query(query, club_id, ascending, limit=None):
queries = [query, Q(is_private=False, is_deleted=False, event__club__isnull=False)]
club = None
if club_id:
club = get_object_or_404(Club, pk=club_id)
q_club = Q(event__club=club)
queries.append(q_club)
sortkey = 'start_date'
if not ascending:
sortkey = '-start_date'
queryset = Tournament.objects.filter(*queries).prefetch_related(
'group_stages',
'rounds',
'team_registrations',
).order_by(sortkey)
# Apply limit directly in the database query
if limit is not None and isinstance(limit, int) and limit > 0:
queryset = queryset[:limit]
return queryset
def finished_tournaments(club_id, limit=None):
clean_ended_tournaments = tournaments_query(Q(end_date__isnull=False), club_id, False, limit)
clean_ended_tournaments = [t for t in clean_ended_tournaments if t.display_tournament()]
one_day_ago = timezone.now() - timedelta(days=1)
ended_tournaments = tournaments_query(
Q(end_date__isnull=True, start_date__lt=one_day_ago),
club_id,
False,
limit
)
ended_tournaments = [t for t in ended_tournaments if t.display_tournament() and t.should_be_over()]
# Combine both lists
all_tournaments = clean_ended_tournaments + ended_tournaments
# Sort the combined list by start_date in descending order
all_tournaments.sort(key=lambda t: t.start_date, reverse=True)
return all_tournaments
def live_tournaments(club_id, limit=None):
tournaments = tournaments_query(Q(end_date__isnull=True), club_id, True, limit)
return [t for t in tournaments if t.display_tournament() and t.supposedly_in_progress()]
def future_tournaments(club_id, limit=None):
tournaments = tournaments_query(Q(end_date__isnull=True), club_id, True, limit)
return [t for t in tournaments if t.display_tournament() and t.starts_in_the_future()]
def tournament_info(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
registered_user = None
team_registration = None
is_captain = False
player_register_check = None
if request.user.is_authenticated:
# Assuming user's licence_id is stored in the user profile (e.g., request.user.licence_id)
user_licence_id = request.user.licence_id
player_register_check = tournament.player_register_check(user_licence_id)
if user_licence_id is not None and player_register_check is None:
validator = LicenseValidator(user_licence_id)
stripped_license = validator.stripped_license
# Check if there is a PlayerRegistration for this user in this tournament
registered_user = PlayerRegistration.objects.filter(
licence_id__icontains=stripped_license,
team_registration__tournament=tournament,
team_registration__walk_out=False,
).first()
# If the user is registered, retrieve their team registration
if registered_user:
is_captain = registered_user.captain
team_registration = registered_user.team_registration
return render(request, 'tournaments/tournament_info.html', {
'tournament': tournament,
'team': team_registration,
'is_captain': is_captain,
'player_register_check': player_register_check
})
def tournaments(request):
filter_param = request.GET.get('filter')
filter = None
if filter_param:
try:
filter = int(filter_param)
if filter not in [0, 1, 2]: # Valid filter values
return redirect('/')
except:
return redirect('/')
club_id = request.GET.get('club')
title = ''
tournaments = []
if filter==0:
title = 'À venir'
tournaments = future_tournaments(club_id)
elif filter==1:
title = 'En cours'
tournaments = live_tournaments(club_id)
elif filter==2:
title = 'Terminés'
tournaments = finished_tournaments(club_id)
return render(
request,
"tournaments/tournaments_list.html",
{
'tournaments': tournaments,
'title': title,
'club': club_id,
}
)
def clubs(request):
all_clubs = Club.objects.all().order_by('name')
clubs = []
for club in all_clubs:
if club.events_count() > 0:
clubs.append(club)
return render(request, 'tournaments/clubs.html', {
'clubs': clubs,
})
def club(request, club_id):
club = get_object_or_404(Club, pk=club_id)
return render(request, 'tournaments/summons.html', {
'tournament': tournament,
'team_summons': team_summons,
})
def tournament(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
round_id = request.GET.get('round')
group_stage_id = request.GET.get('group_stage')
match_groups = tournament.match_groups(False, group_stage_id, round_id)
bracket_rounds = tournament.rounds.filter(parent=None, group_stage_loser_bracket=False).order_by('-index')
rounds = list(tournament.rounds.filter(group_stage_loser_bracket=True))
rounds.extend(bracket_rounds)
group_stages = sorted(tournament.sorted_group_stages(), key=lambda s: (s.step, s.index))
if tournament.display_matches() or tournament.display_group_stages():
return render(request, 'tournaments/matches.html', {
'tournament': tournament,
'rounds': rounds,
'group_stages': group_stages,
'match_groups': match_groups,
})
else:
return tournament_info(request, tournament_id)
def tournament_teams(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
teams = tournament.teams(True)
selected_teams = [team for team in teams if team.stage != "Attente"]
waiting_teams = [team for team in teams if team.stage == "Attente"]
return render(request, 'tournaments/teams.html', {
'tournament': tournament,
'selected_teams': selected_teams,
'waiting_teams': waiting_teams,
})
def tournament_summons(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
team_summons = tournament.team_summons()
return render(request, 'tournaments/summons.html', {
'tournament': tournament,
'team_summons': team_summons,
})
def tournament_broadcasted_summons(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcasted_summons.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
def tournament_summons_json(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
team_summons = [summon.to_dict() for summon in tournament.team_summons()]
data = json.dumps(team_summons)
return HttpResponse(data, content_type='application/json')
def tournament_broadcast_home(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcast.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
def automatic_broadcast(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcasted_auto.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
def automatic_broadcast_event(request, event_id):
event = get_object_or_404(Event, pk=event_id)
tournaments = Tournament.objects.filter(event=event)
tournament_ids = [str(tournament.id) for tournament in tournaments]
return render(request, 'tournaments/broadcast/broadcasted_auto_event.html', {
'tournament_ids': tournament_ids,
})
def qr_code_url(request, tournament_id):
qr_code_url = reverse('tournament', args=[tournament_id])
return request.build_absolute_uri(qr_code_url)
def qr_code_url_with_query(request, club_id):
url_with_query = f"{request.build_absolute_uri('/')}?club={str(club_id)}"
return url_with_query
def qr_code_options():
return QRCodeOptions(size=10, border=4, error_correction='L')
def tournament_matches(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcasted_matches.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
def broadcast_json(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
broadcast = tournament.broadcast_content()
data = json.dumps(broadcast)
return HttpResponse(data, content_type='application/json')
def tournament_matches_json(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
matches, group_stages = tournament.broadcasted_matches_and_group_stages()
live_matches_dicts = [match.live_match().to_dict() for match in matches]
data = json.dumps(live_matches_dicts, default=vars)
return HttpResponse(data, content_type='application/json')
def tournament_prog_json(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
matches = tournament.broadcasted_prog()
# Convert matches to JSON-serializable format, handling None/empty slots
live_matches = []
for match in matches:
if match is None or isinstance(match, dict) and match.get('empty'):
live_matches.append({"empty": True})
else:
live_matches.append(match.live_match().to_dict())
data = json.dumps(live_matches, default=vars)
return HttpResponse(data, content_type='application/json')
def tournament_group_stages(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
live_group_stages = list(tournament.live_group_stages())
unique_steps = sorted(set(gs.step for gs in live_group_stages), reverse=True)
return render(request, 'tournaments/group_stages.html', {
'tournament': tournament,
'group_stages': live_group_stages,
'unique_steps': unique_steps,
})
def tournament_broadcasted_group_stages(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcasted_group_stages.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
def tournament_broadcasted_prog(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcasted_prog.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
def tournament_broadcasted_bracket(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcasted_bracket.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
def tournament_bracket_json(request, tournament_id):
"""
JSON endpoint for tournament bracket data.
"""
tournament = get_object_or_404(Tournament, pk=tournament_id)
context = get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=True, display_loser_final=True)
data = {
'match_groups': [match_group.to_dict() for match_group in context['match_groups']],
'double_butterfly_mode': context['double_butterfly_mode'],
'display_loser_final': context['display_loser_final']
}
return JsonResponse(data)
def tournament_live_group_stage_json(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
# Get all live group stages and filter for the last running step
live_group_stages = [gs.live_group_stages() for gs in tournament.last_group_stage_step()]
gs_dicts = [gs.to_dict() for gs in live_group_stages]
data = json.dumps(gs_dicts)
return HttpResponse(data, content_type='application/json')
def tournament_rankings(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
rankings = tournament.rankings()
return render(request, 'tournaments/rankings.html', {
'tournament': tournament,
'rankings': rankings,
})
def tournament_rankings_json(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
rankings = tournament.rankings()
data = json.dumps(rankings, default=vars)
return HttpResponse(data, content_type='application/json')
def tournament_broadcast_rankings(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcasted_rankings.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
def players(request):
return render(request, 'tournaments/test.html')
def activate(request, uidb64, token):
try:
uid = force_str(urlsafe_base64_decode(uidb64))
user = CustomUser.objects.get(pk=uid)
except(TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
user = None
if user is not None and account_activation_token.check_token(user, token):
user.is_active = True
user.save()
# Specify the authentication backend when logging in
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
return redirect('activation_success')
else:
# Redirect to a custom error page
return redirect('activation_failed') # Define this URL in your urls.py
def activation_success(request):
return render(request, 'registration/activation_success.html')
def activation_failed(request):
return render(request, 'registration/activation_failed.html')
def club_broadcast(request, broadcast_code):
club = get_object_or_404(Club, broadcast_code=broadcast_code)
q_not_deleted = Q(is_deleted=False, event__club=club)
tournaments = Tournament.objects.filter(q_not_deleted).order_by('-start_date')
return render(request, 'tournaments/broadcast/broadcast_club.html', {
'club': club,
'tournaments': tournaments,
})
def club_broadcast_auto(request, broadcast_code):
club = get_object_or_404(Club, broadcast_code=broadcast_code)
q_not_deleted = Q(is_deleted=False, event__club=club)
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
recent_time_window = now - timedelta(hours=4)
tournaments = Tournament.objects.filter(
q_not_deleted,
Q(is_private=False), # Exclude private tournaments
(
(
Q(end_date__isnull=True) & # Include ongoing tournaments
(Q(start_date__gte=now - timedelta(days=3)) & Q(start_date__lt=now + timedelta(days=3))) # Include tournaments that are ongoing and start within 3 days
) |
(Q(end_date__isnull=False) & Q(end_date__gte=recent_time_window)) # Include tournaments finished within the last 4 hours
)
)
tournament_ids = [str(tournament.id) for tournament in tournaments]
#print(tournament_ids)
return render(request, 'tournaments/broadcast/broadcasted_auto_event.html', {
'tournament_ids': tournament_ids,
'club_name': club.name,
'qr_code_url': qr_code_url_with_query(request, club.id),
'qr_code_options': qr_code_options(),
})
def download(request):
return render(request, 'tournaments/download.html')
def test_apns(request):
user = CustomUser.objects.filter(username='laurent').first()
for device_token in user.device_tokens.all():
asyncio.run(send_push_notification(device_token.value, 'LOL?!'))
# token = DeviceToken.objects.first()
return HttpResponse('OK!')
def test_websocket(request):
return render(request, 'tournaments/test_websocket.html')
def terms_of_use(request):
return render(request, 'terms_of_use.html')
@csrf_exempt
def xls_to_csv(request):
if request.method == 'POST':
# Check if the request has a file
if 'file' in request.FILES:
uploaded_file = request.FILES['file']
# Save the uploaded file
directory = 'tmp/csv/'
file_path = os.path.join(directory, uploaded_file.name)
file_name = default_storage.save(file_path, ContentFile(uploaded_file.read()))
# Check available sheets and look for 'inscriptions'
xls = pd.ExcelFile(file_name)
sheet_names = xls.sheet_names
# Determine which sheet to use
target_sheet = 0 # Default to first sheet
if 'inscriptions' in [name.lower() for name in sheet_names]:
for i, name in enumerate(sheet_names):
if name.lower() == 'inscriptions':
target_sheet = i # or use the name directly: target_sheet = name
break
# Convert to csv and save
data_xls = pd.read_excel(file_name, sheet_name=target_sheet, index_col=None)
csv_file_name = create_random_filename('players', 'csv')
output_path = os.path.join(directory, csv_file_name)
data_xls.to_csv(output_path, sep=';', index=False, encoding='utf-8')
# Send the processed file back
with default_storage.open(output_path, 'rb') as file:
response = HttpResponse(file.read(), content_type='application/octet-stream')
response['Content-Disposition'] = f'attachment; filename="players.csv"'
# Clean up: delete both files
default_storage.delete(file_path)
default_storage.delete(output_path)
return response
else:
return HttpResponse("No file was uploaded", status=400)
else:
return HttpResponse("Only POST requests are allowed", status=405)
def simple_form_view(request):
if request.method == 'POST':
# If this is a POST request, we need to process the form data
form = SimpleForm(request.POST)
if form.is_valid():
# Process the data in form.cleaned_data
name = form.cleaned_data['name']
# Do something with the data (e.g., save to the database)
send_email_to_jap_list()
# send_email('laurent@staxriver.com', 'Laurent')
return HttpResponse(f"Hello, {name}!") # Simple response with name
else:
form = SimpleForm()
# print(request.method)
# If this is a GET request, we display an empty form
return render(request, 'tournaments/admin/mail_test.html', {'form': form})
def send_email_to_jap_list():
# Open the CSV file
with open('tournaments/static/misc/jap-test.csv', 'r') as file:
csv_reader = csv.reader(file)
def log_error(message):
with open('email_errors.log', 'a') as log_file:
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_file.write(f"{timestamp}: {message}\n")
batch = []
for row in csv_reader:
if len(row) >= 4:
name = row[2]
mail = row[3]
batch.append((mail, name))
if len(batch) == 10:
for email, recipient_name in batch:
try:
send_email(email, recipient_name)
print(f"Name: {recipient_name}, mail: {email}")
except Exception as e:
error_message = f"Error sending email to {email}: {str(e)}"
log_error(error_message)
print(error_message)
batch = []
time.sleep(2)
else:
print(f"Row doesn't have enough elements: {row}")
# Send any remaining emails in the last batch
for email, recipient_name in batch:
try:
send_email(email, recipient_name)
print(f"Name: {recipient_name}, mail: {email}")
except Exception as e:
error_message = f"Error sending email to {email}: {str(e)}"
log_error(error_message)
print(error_message)
def send_email(mail, name):
if name.lower() not in mail.lower():
name = ""
subject = "Tes tournois en toute simplicité avec Padel Club"
body = f"Salut {name} !\n\nJe me permets de t'écrire car je suis JAP2 en région PACA et développeur, et je viens de lancer Padel Club, une app iOS qui facilite enfin l'organisation des tournois.\n\nAvec elle, tu peux convoquer rapidement, simuler et programmer tes structures, diffuser tous les résultats à tous les joueurs, et ce depuis un iPhone.\n\nTu peux l'essayer gratuitement pour découvrir tout son potentiel ! Télécharge l'app ici et teste la dès ton prochain tournoi: https://padelclub.app/download/\n\nJe suis disponible pour échanger avec toi par mail ou téléphone au 06 81 59 81 93 et voir ce que tu en penses.\nÀ bientôt j'espère !\n\nRazmig"
email = EmailMessage(subject, body, to=[mail])
email.send()
def signup(request):
next_url = request.GET.get('next', '/')
if request.method == 'POST':
form = SimpleCustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_active = False
user.origin = UserOrigin.SITE
user.save()
if not settings.DEBUG:
send_verification_email(request, user, next_url)
return render(request, 'registration/signup_success.html', {
'next_url': next_url,
'user_email': user.email # Add the user's email to the context
})
else:
form = SimpleCustomUserCreationForm()
response = render(request, 'registration/signup.html', {'form': form})
return response
def send_verification_email(request, user, next_url):
print('next_url', next_url)
current_site = get_current_site(request)
mail_subject = 'Activez votre compte Padel Club !'
# Prepare the email subject and message
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),
'next': next_url, # Pass next URL to the template
})
email = EmailMessage(mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
@login_required
def profile(request):
user = request.user # Get the currently authenticated user
return render(request, 'registration/profile.html', {
'user_name': user.username
})
@csrf_protect
def register_tournament(request, tournament_id):
tournament = get_object_or_404(Tournament, id=tournament_id)
service = TournamentRegistrationService(request, tournament)
service.initialize_context()
print("initialize_context")
if request.method == 'POST':
service.handle_post_request()
else:
service.handle_get_request()
return render(request, 'register_tournament.html', service.context)
@login_required
def unregister_tournament(request, tournament_id):
tournament = get_object_or_404(Tournament, id=tournament_id)
service = TournamentUnregistrationService(request, tournament)
if not service.can_unregister():
return redirect('tournament-info', tournament_id=tournament_id)
service.process_unregistration()
return redirect('tournament-info', tournament_id=tournament_id)
class CustomPasswordResetConfirmView(PasswordResetConfirmView):
template_name = 'registration/password_reset_confirm.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['custom_message'] = "Veuillez entrer un nouveau mot de passe."
return context
def form_valid(self, form):
response = super().form_valid(form)
return response
def get_user(self, uidb64):
"""
Override this method to decode the uid and return the corresponding user.
"""
try:
# Use get_user_model() instead of direct User import
User = get_user_model()
uid = urlsafe_base64_decode(uidb64).decode()
user = User.objects.get(pk=uid)
return user
except (TypeError, ValueError, User.DoesNotExist):
raise Http404("User not found")
class CustomPasswordResetCompleteView(PasswordResetCompleteView):
template_name = 'registration/password_reset_complete.html'
def get(self, request, *args, **kwargs):
# Get the user from the session
username = request.session.get('reset_username')
if username:
try:
# Get the user
User = get_user_model()
user = User.objects.get(username=username)
# Log the user in
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
# Clean up the session
if 'reset_username' in request.session:
del request.session['reset_username']
# Redirect to the profile page
return redirect('profile')
except User.DoesNotExist:
pass
# If no username in session or user not found, proceed with normal view
return super().get(request, *args, **kwargs)
@login_required
def custom_password_change(request):
if request.method == 'POST':
form = CustomPasswordChangeForm(user=request.user, data=request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user) # Important to keep user logged in
messages.success(request, 'Votre mot de passe a été mis à jour avec succès!')
return redirect('profile')
else:
# Form is invalid, show errors
profile_form = ProfileUpdateForm(instance=request.user)
return render(request, 'profile.html', {
'form': profile_form,
'password_change_form': form
})
# If not POST, redirect to profile page
return redirect('profile')
@login_required
def my_tournaments(request):
user = request.user
user_licence_id = request.user.licence_id
# If no licence_id, return empty lists
if user_licence_id is None:
return render(request, 'registration/my_tournaments.html', {
'upcoming_tournaments': [],
'running_tournaments': [],
'ended_tournaments': [],
'user_name': user.username
})
# Get all tournaments for the user based on their licence
validator = LicenseValidator(user_licence_id)
stripped_license = validator.stripped_license
def filter_user_tournaments(tournaments):
return [t for t in tournaments if t.team_registrations.filter(
player_registrations__licence_id__icontains=stripped_license,
walk_out=False
).exists()]
# Get filtered tournaments using the helper function
upcoming_tournaments = filter_user_tournaments(future_tournaments(None))
running_tournaments = filter_user_tournaments(live_tournaments(None))
ended_tournaments = filter_user_tournaments([t for t in finished_tournaments(None) if not t.is_canceled()])
return render(request, 'registration/my_tournaments.html', {
'upcoming_tournaments': upcoming_tournaments,
'running_tournaments': running_tournaments,
'ended_tournaments': ended_tournaments[:12],
'user_name': user.username
})
@login_required
def all_my_ended_tournaments(request):
user_licence_id = request.user.licence_id
# If no licence_id, return empty lists
if user_licence_id is None:
return render(request, '/tournaments.html', {
'tournaments': [],
'title': "Palmarès",
})
# Get all tournaments for the user based on their licence
validator = LicenseValidator(user_licence_id)
stripped_license = validator.stripped_license
def filter_user_tournaments(tournaments):
return [t for t in tournaments if t.team_registrations.filter(
player_registrations__licence_id__icontains=stripped_license,
walk_out=False
).exists()]
ended_tournaments = filter_user_tournaments(finished_tournaments(None))
return render(request,
"tournaments/tournaments_list.html",
{
'tournaments': ended_tournaments,
'title': "Palmarès",
})
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
model = CustomUser
form_class = ProfileUpdateForm
template_name = 'profile.html'
success_url = reverse_lazy('profile')
login_url = '/login/' # Specify where to redirect if user is not logged in
def get_object(self, queryset=None):
return self.request.user
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['password_change_form'] = CustomPasswordChangeForm(user=self.request.user)
return context
@staff_member_required
def tournament_import_view(request):
if request.method == 'POST':
zip_file = request.FILES.get('tournament_zip')
if zip_file:
print('yes')
try:
tournament_id = os.path.splitext(zip_file.name)[0]
tournament = Tournament.objects.get(id=tournament_id)
# Delete existing relationships
print('round delete')
rounds = Round.objects.filter(tournament=tournament,parent=None)
for round in rounds:
round.delete_dependencies()
for gs in tournament.group_stages.all():
gs.delete_dependencies()
for tr in tournament.team_registrations.all():
tr.delete_dependencies()
# with transaction.atomic():
# for round in tournament.rounds.all():
# round.delete()
tournament.rounds.all().delete()
tournament.group_stages.all().delete()
tournament.team_registrations.all().delete()
# tournament.player_registrations.all().delete()
# tournament.matches.all().delete()
# tournament.team_scores.all().delete()
print(f'round count = {tournament.rounds.count()}')
print(f'group_stages count = {tournament.group_stages.count()}')
print(f'team_registrations count = {tournament.team_registrations.count()}')
print(f'PR count = {PlayerRegistration.objects.count()}')
print(f'TS count = {TeamScore.objects.count()}')
print(f'M count = {Match.objects.count()}')
with zipfile.ZipFile(zip_file) as z:
# First, process rounds
rounds_data = get_file_data(z, f"{tournament_id}/rounds.json")
# print(f'rounds count = {len(rounds_data)}')
if rounds_data:
# First pass: Create rounds with preserved UUIDs
for item in rounds_data:
# print('round #1 create')
item['tournament'] = tournament.id
round_id = item['id'] # Preserve the original UUID
Round.objects.create(
id=round_id,
tournament_id=tournament.id,
index=item['index'],
format=item.get('format'),
start_date=item.get('start_date'),
group_stage_loser_bracket=item.get('group_stage_loser_bracket', False),
loser_bracket_mode=item.get('loser_bracket_mode', 0)
)
# Second pass: Set parent relationships
for item in rounds_data:
# print('round #2 set parent')
if item.get('parent'):
round_obj = Round.objects.get(id=item['id'])
round_obj.parent_id = item['parent']
round_obj.save()
# Then process all other files
serializer_mapping = {
# 'rounds.json': RoundSerializer,
'group-stages.json': GroupStageSerializer,
'team-registrations.json': TeamRegistrationSerializer,
'matches.json': MatchSerializer,
'player-registrations.json': PlayerRegistrationSerializer,
'team-scores.json': TeamScoreSerializer
}
# Process each remaining file
for filename, serializer_class in serializer_mapping.items():
process_file(z, filename, tournament_id, tournament, serializer_class)
return JsonResponse({'status': 'success'})
except Exception as e:
print(f'error = {str(e)}')
return JsonResponse({'status': 'error', 'message': str(e)})
else:
return render(request, 'tournaments/admin/tournament_cleaner.html')
def process_file(zip_file, filename, tournament_id, tournament, serializer_class):
"""Helper function to process individual files"""
try:
print(f'process {filename}')
file_path = f"{tournament_id}/{filename}"
json_data = get_file_data(zip_file, file_path)
if json_data:
# Add tournament to each item
for item in json_data:
# print(f'process {item}')
item['tournament'] = tournament.id
item['store_id'] = str(tournament.id)
serializer = serializer_class(data=json_data, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
except Exception as e:
print(f"Error processing {filename}: {str(e)}")
def get_file_data(zip_file, file_path):
"""Helper function to read and parse JSON data from zip file"""
try:
file_content = zip_file.read(file_path)
return json.loads(file_content)
except KeyError:
print(f"File not found: {file_path}")
return None
except json.JSONDecodeError as e:
print(f"JSON Error for {file_path}: {str(e)}")
raise Exception(f"Invalid JSON in file {file_path}")
def status_page(request):
return render(request, 'tournaments/admin/status.html')
def team_details(request, tournament_id, team_id):
# First check if team_id is None or invalid
if team_id is None or team_id == 'None':
# Redirect to tournament page or show an error
return redirect('tournament-info', tournament_id=tournament_id)
tournament = get_object_or_404(Tournament, id=tournament_id)
try:
team = get_object_or_404(TeamRegistration, id=team_id)
except (ValueError, ValidationError):
# Handle invalid UUID
return redirect('tournament-info', tournament_id=tournament_id)
# Get all matches for this team
all_matches = team.get_matches()
return render(request, 'tournaments/team_details.html', {
'tournament': tournament,
'team': team,
'matches': all_matches,
'debug': False # Set to False in production
})
def tournament_bracket(request, tournament_id):
"""
View to display tournament bracket structure.
"""
tournament = get_object_or_404(Tournament, pk=tournament_id)
context = get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=False, display_loser_final=True)
return render(request, 'tournaments/tournament_bracket.html', context)
def round_bracket(request, tournament_id, round_id):
"""
View to display round bracket structure.
"""
tournament = get_object_or_404(Tournament, pk=tournament_id)
round = get_object_or_404(Round, pk=round_id)
context = get_butterfly_bracket_view_context(tournament, round, double_butterfly_mode=False, display_loser_final=False)
return render(request, 'tournaments/tournament_bracket.html', context)
def get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=False, display_loser_final=True):
if parent_round:
double_butterfly_mode = False
display_loser_final = True
serializable_match_groups = tournament.get_butterfly_bracket_match_group(parent_round, double_butterfly_mode, display_loser_final)
context = {
'tournament': tournament,
'match_groups': serializable_match_groups,
'double_butterfly_mode': double_butterfly_mode,
'display_loser_final': display_loser_final
}
return context
def tournament_prog(request, tournament_id):
tournament = get_object_or_404(Tournament, id=tournament_id)
# Get matches from all_groups
match_groups = tournament.all_groups(broadcasted=False)
# Flatten matches and add necessary attributes
all_matches = []
for group in match_groups:
for match in group.matches:
if match.start_date: # Only include matches with start dates
all_matches.append({
'start_date': match.start_date,
'court_index': match.court_index,
# Add other match attributes needed for match_cell.html
'match': match
})
# Sort matches by date and court
all_matches.sort(key=lambda x: (x['start_date'], x['court_index'] or 999))
context = {
'tournament': tournament,
'match_groups': {'matches': all_matches}
}
return render(request, 'tournaments/prog.html', context)
def custom_logout(request):
logout(request)
return redirect('index') # or whatever URL you want to redirect to
class UserListExportView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
users = CustomUser.objects.order_by('date_joined')
# Prepare the response
response = HttpResponse(content_type='text/plain; charset=utf-8')
# Write header
headers = [
'Prenom', 'Nom', 'Club', 'Email', 'Telephone', 'Username',
'Origine', 'Actif', 'Inscription', 'Tournois'
]
response.write('\t'.join(headers) + '\n')
# Write data rows
for user in users:
row = [
str(user.first_name or ''),
str(user.last_name or ''),
str(user.latest_event_club_name() or ''),
str(user.email or ''),
str(user.phone or ''),
user.username,
str(user.get_origin_display()),
'Oui' if user.is_active else 'Non',
user.date_joined.strftime('%Y-%m-%d %H:%M:%S'),
str(user.event_count())
]
# Replace any tabs or newlines in the data to prevent formatting issues
row = [field.replace('\t', ' ') for field in row]
response.write('\t'.join(row) + '\r\n')
return response