add session timer management

timetoconfirm
Raz 7 months ago
parent f2fcd83cc5
commit 8adceb2628
  1. 5
      tournaments/models/tournament.py
  2. 26
      tournaments/services/tournament_registration.py
  3. 97
      tournaments/templates/register_tournament.html
  4. 38
      tournaments/views.py

@ -1073,6 +1073,8 @@ class Tournament(BaseModel):
options.append(f"Remboursement possible jusqu'au {date}")
elif self.enable_online_payment_refund:
options.append("Remboursement possible")
else:
options.append("Remboursement impossible")
# Joueurs par équipe
min_players = self.minimum_player_per_team
@ -1175,7 +1177,7 @@ class Tournament(BaseModel):
return -1
# Get count of active teams (not walked out)
current_team_count = self.team_registrations.exclude(walk_out=True).count()
current_team_count = self.team_registrations.exclude(walk_out=True).count() + self.reserved_spots
# If current count is less than target count, next team is not in waiting list
if current_team_count < self.team_count:
@ -1731,7 +1733,6 @@ class Tournament(BaseModel):
return False
def is_refund_possible(self):
return True
if self.enable_online_payment_refund:
time = timezone.now()
if self.refund_date_limit:

@ -12,7 +12,7 @@ class RegistrationCartManager:
and checkout processes.
"""
CART_EXPIRY_MINUTES = 30
CART_EXPIRY_SECONDS = 300
def __init__(self, request):
self.request = request
@ -29,7 +29,7 @@ class RegistrationCartManager:
"""Get the cart expiry time from the session"""
if 'registration_cart_expiry' not in self.session:
# Set default expiry to 30 minutes from now
expiry = timezone.now() + datetime.timedelta(minutes=self.CART_EXPIRY_MINUTES)
expiry = timezone.now() + datetime.timedelta(seconds=self.CART_EXPIRY_SECONDS)
self.session['registration_cart_expiry'] = expiry.isoformat()
self.session.modified = True
return self.session['registration_cart_expiry']
@ -48,7 +48,7 @@ class RegistrationCartManager:
def reset_cart_expiry(self):
"""Reset the cart expiry time"""
expiry = timezone.now() + datetime.timedelta(minutes=self.CART_EXPIRY_MINUTES)
expiry = timezone.now() + datetime.timedelta(seconds=self.CART_EXPIRY_SECONDS)
self.session['registration_cart_expiry'] = expiry.isoformat()
self.session.modified = True
@ -69,12 +69,7 @@ class RegistrationCartManager:
# Update tournament reserved spots
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
waiting_list_position = tournament.get_waiting_list_position()
if waiting_list_position >= 0:
tournament.reserved_spots = 0
else:
tournament.reserved_spots += 1
tournament.save()
# Set up the new cart
@ -107,12 +102,25 @@ class RegistrationCartManager:
if hasattr(self.request.user, 'phone'):
user_phone = self.request.user.phone
# Parse the expiry time from ISO format to datetime
expiry_str = self.get_cart_expiry()
expiry_datetime = None
if expiry_str:
try:
# Parse the ISO format string to datetime
from django.utils.dateparse import parse_datetime
expiry_datetime = parse_datetime(expiry_str)
except (ValueError, TypeError):
# If parsing fails, set a new expiry
expiry_datetime = timezone.now() + datetime.timedelta(seconds=self.CART_EXPIRY_SECONDS)
cart_data = {
'cart_id': self.get_or_create_cart_id(),
'tournament_id': self.session.get('registration_tournament_id'),
'waiting_list_position': self.session.get('waiting_list_position'),
'players': self.session.get('registration_cart_players', []),
'expiry': self.get_cart_expiry(),
'expiry': expiry_datetime, # Now a datetime object, not a string
'is_cart_expired': self.is_cart_expired(),
'mobile_number': self.session.get('registration_mobile_number', user_phone)
}

@ -15,7 +15,7 @@
<div class="grid-x">
<div class="cell medium-6 large-6 padding10">
<h1 class="club padding10 topmargin20">Inscription : {{ tournament.display_name }} {{ tournament.get_federal_age_category_display}}</h1 >
<h1 class="club padding10">Inscription : {{ tournament.display_name }} {{ tournament.get_federal_age_category_display}}</h1 >
<div class="bubble">
@ -28,8 +28,15 @@
{% else %}
{% if not registration_successful %}
<div class="info-box">
<p>Votre session d'inscription est active. Complétez le formulaire pour confirmer votre participation.</p>
<p>DEBUG reserved_spots: {{ tournament.reserved_spots }}</p>
<p>Votre session d'inscription est active. Complétez le formulaire dans le délai accordé pour confirmer votre participation et garantir votre place.</p>
{% if not cart_data.is_cart_expired %}
<p class="semibold highlight">Votre session d'inscription expirera le {{ cart_data.expiry|date:"d/m/Y à H:i" }}</p>
<p>Temps restant: <span id="countdown" data-expiry="{{ cart_data.expiry|date:'Y-m-d H:i:s' }}">{{ cart_data.expiry|timeuntil }}</span></p>
{% else %}
<p class="alert alert-danger">
Votre session d'inscription a expiré. Veuillez recommencer le processus d'inscription. Votre place n'est plus garantie.
</p>
{% endif %}
</div>
{% endif %}
@ -196,4 +203,88 @@
</div>
</div>
</div>
<script>
// Safe countdown script with no automatic reloads
document.addEventListener('DOMContentLoaded', function() {
// Get the countdown element
const countdownElement = document.getElementById('countdown');
if (!countdownElement) return;
// Only proceed with countdown if cart is not expired according to backend
const cartExpiredDiv = document.querySelector('.alert-danger');
if (cartExpiredDiv && cartExpiredDiv.textContent.includes('expiré')) {
// Cart is already expired according to backend, don't set up countdown
return;
}
// Get the expiry date from the data attribute
const expiryDateStr = countdownElement.getAttribute('data-expiry');
if (!expiryDateStr) return;
// Parse the expiry date properly (keeping local time interpretation)
// Format received: "YYYY-MM-DD HH:MM:SS"
const [datePart, timePart] = expiryDateStr.split(' ');
const [year, month, day] = datePart.split('-').map(Number);
const [hours, minutes, seconds] = timePart.split(':').map(Number);
// Create date object using local time components (month is 0-indexed in JS)
const expiryDate = new Date(year, month-1, day, hours, minutes, seconds);
// Function to update countdown text
function updateCountdown() {
const now = new Date();
let timeRemaining = Math.max(0, Math.floor((expiryDate - now) / 1000)); // in seconds
if (timeRemaining <= 0) {
countdownElement.textContent = "Expiré";
// Set a flag in localStorage to prevent infinite reload
if (!localStorage.getItem('cartExpired')) {
localStorage.setItem('cartExpired', 'true');
// Reload once when expired
window.location.reload();
}
return;
}
// Calculate days, hours, minutes, seconds
const days = Math.floor(timeRemaining / 86400);
timeRemaining %= 86400;
const hours = Math.floor(timeRemaining / 3600);
timeRemaining %= 3600;
const minutes = Math.floor(timeRemaining / 60);
const seconds = timeRemaining % 60;
// Format the countdown text - ALWAYS include seconds
let countdownText = '';
if (days > 0) {
countdownText += `${days} jour${days > 1 ? 's' : ''}, `;
}
if (hours > 0 || days > 0) {
countdownText += `${hours} heure${hours > 1 ? 's' : ''}, `;
}
if (minutes > 0 || hours > 0 || days > 0) {
countdownText += `${minutes} minute${minutes > 1 ? 's' : ''}, `;
}
countdownText += `${seconds} seconde${seconds > 1 ? 's' : ''}`;
countdownElement.textContent = countdownText;
}
// Clear previous expiry flag when starting a new countdown
localStorage.removeItem('cartExpired');
// Update immediately
updateCountdown();
// Update every second
const countdownInterval = setInterval(updateCountdown, 1000);
// Clean up interval when page unloads
window.addEventListener('unload', function() {
clearInterval(countdownInterval);
});
});
</script>
{% endblock %}

@ -1186,6 +1186,11 @@ def tournament_payment_success(request, tournament_id):
success = payment_service.process_successful_payment(str(tournament_id), checkout_session)
if success:
# Set a flag for successful registration if the payment was from registration page
source_page = request.session.get('payment_source_page', 'tournament_info')
if source_page == 'register_tournament':
request.session['registration_successful'] = True
messages.success(request, "Paiement réussi et inscription confirmée !")
else:
messages.error(request, "Erreur lors du traitement du paiement.")
@ -1206,16 +1211,35 @@ def tournament_payment_success(request, tournament_id):
# Redirect to the appropriate page
if source_page == 'register_tournament':
# For payments during registration, redirect back to tournament info
# since the registration is now complete
return redirect('tournament-info', tournament_id=tournament_id)
# For payments during registration, redirect back to registration page
# with registration_successful flag in session
return redirect('register_tournament', tournament_id=tournament_id)
else:
# For direct payments, also go to tournament info
# For direct payments, go to tournament info
return redirect('tournament-info', tournament_id=tournament_id)
@csrf_protect
def register_tournament(request, tournament_id):
tournament = get_object_or_404(Tournament, id=tournament_id)
# Check for registration_successful flag
registration_successful = request.session.pop('registration_successful', False)
# If registration was successful, render success page immediately
if registration_successful:
storage = messages.get_messages(request)
for message in storage:
# Iterate through messages to clear them
pass # This actually consumes the messages
storage.used = True # Mark all messages as used
context = {
'tournament': tournament,
'registration_successful': True,
'current_players': [],
'cart_data': {'players': []}
}
return render(request, 'register_tournament.html', context)
cart_manager = RegistrationCartManager(request)
# Debug session content
@ -1239,6 +1263,12 @@ def register_tournament(request, tournament_id):
# We're returning from Stripe, don't reinitialize the cart
pass
else:
storage = messages.get_messages(request)
for message in storage:
# Iterate through messages to clear them
pass # This actually consumes the messages
storage.used = True # Mark all messages as used
# ALWAYS initialize a fresh cart when entering the registration page (GET request)
# This ensures no old cart data persists
cart_manager.initialize_cart(tournament_id)

Loading…
Cancel
Save