diff --git a/tournaments/static/tournaments/css/basics.css b/tournaments/static/tournaments/css/basics.css
index 19dee98..e7371bf 100644
--- a/tournaments/static/tournaments/css/basics.css
+++ b/tournaments/static/tournaments/css/basics.css
@@ -60,6 +60,10 @@
margin: 20px 0px;
}
+.bmargin10 {
+ margin-bottom: 10px;
+}
+
/* WIDTH PERCENTAGE */
.w15 {
diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css
index ccd3358..8ab3594 100644
--- a/tournaments/static/tournaments/css/style.css
+++ b/tournaments/static/tournaments/css/style.css
@@ -1015,7 +1015,6 @@ h-margin {
color: white; /* White text on hover */
}
.download-button {
- margin-right: 6px;
color: #fff7ed;
padding: 8px 12px;
background-color: #1a223a;
diff --git a/tournaments/templates/tournaments/tournaments_list.html b/tournaments/templates/tournaments/tournaments_list.html
index 8c34d99..7ec61bf 100644
--- a/tournaments/templates/tournaments/tournaments_list.html
+++ b/tournaments/templates/tournaments/tournaments_list.html
@@ -1,4 +1,5 @@
{% extends 'tournaments/base.html' %}
+{% load tournament_tags %}
{% block head_title %}{{ head_title }}{% endblock %}
{% block first_title %}{{ first_title }}{% endblock %}
@@ -7,6 +8,42 @@
{% block content %}
{% include 'tournaments/navigation_base.html' %}
+
+ {% if filter == 0 or filter == 2 %}
+
+ {% if available_years %}
+
+
+
+ {% endif %}
+ {% endif %}
+
{% if tournaments %}
@@ -18,6 +55,18 @@
+ {% elif selected_month and selected_year %}
+
+
+
Aucun tournoi trouvé pour {{ month_names|array_lookup:selected_month }} {{ selected_year }}.
+
+
+ {% elif selected_year %}
+
+
+
Aucun tournoi trouvé pour l'année {{ selected_year }}.
+
+
{% endif %}
{% if first_tournament_prog_url and tournaments %}
diff --git a/tournaments/templatetags/tournament_tags.py b/tournaments/templatetags/tournament_tags.py
index 072fd07..b3104a7 100644
--- a/tournaments/templatetags/tournament_tags.py
+++ b/tournaments/templatetags/tournament_tags.py
@@ -5,3 +5,19 @@ register = template.Library()
@register.filter
def get_player_status(tournament, user):
return tournament.get_player_registration_status_by_licence(user)
+
+@register.filter
+def lookup(dictionary, key):
+ """Template filter to lookup dictionary values by key"""
+ return dictionary.get(key, '')
+
+@register.filter
+def array_lookup(array, index):
+ """Template filter to lookup array values by index"""
+ try:
+ index = int(index)
+ if 0 <= index < len(array):
+ return array[index]
+ return ''
+ except (ValueError, TypeError, IndexError):
+ return ''
diff --git a/tournaments/views.py b/tournaments/views.py
index bdcb002..e15b6b2 100644
--- a/tournaments/views.py
+++ b/tournaments/views.py
@@ -1,58 +1,53 @@
# Standard library imports
+
+from django.contrib import messages
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.contrib.auth import login, get_user_model, logout, update_session_auth_hash
+from django.contrib.auth.views import PasswordResetCompleteView, PasswordResetConfirmView
+from django.contrib.sites.shortcuts import get_current_site
+from django.contrib.auth.decorators import login_required
+from django.contrib.admin.views.decorators import staff_member_required
+from django.shortcuts import redirect, render, get_object_or_404
+from django.utils import timezone, formats
+from django.utils.encoding import force_str, force_bytes
+from django.utils.http import urlsafe_base64_decode
+from django.http import JsonResponse, HttpResponse, Http404
+from django.urls import reverse, reverse_lazy
+from django.conf import settings
+from django.db.models import Q
+from django.views.generic import View
+from django.views.generic.edit import UpdateView
+from django.views.decorators.csrf import csrf_protect, csrf_exempt
+from django.template.loader import render_to_string
+from django.core.mail import EmailMessage
+from django.core.exceptions import ValidationError
+from django.core.files.storage import default_storage
+from django.core.files.base import ContentFile
+
import os
import csv
+import locale
+import calendar
+
from api.serializers import GroupStageSerializer, MatchSerializer, PlayerRegistrationSerializer, TeamRegistrationSerializer, TeamScoreSerializer
-from django.contrib.auth.mixins import LoginRequiredMixin
-from django.contrib.auth import logout
from .utils.extensions import is_not_sqlite_backend
import stripe
-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.contrib.admin.views.decorators import staff_member_required
-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, Purchase
+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
import pandas as pd
-from tournaments.utils.extensions import create_random_filename
-from django.core.files.storage import default_storage
-from django.core.files.base import ContentFile
-from django.utils import formats
+from tournaments.utils.extensions import create_random_filename
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_unregistration import TournamentUnregistrationService
-from django.core.exceptions import ValidationError
+
from .forms import (
ProfileUpdateForm,
SimpleCustomUserCreationForm,
@@ -62,12 +57,11 @@ from .forms import (
)
from .utils.apns import send_push_notification
from .utils.licence_validator import LicenseValidator
-from django.views.generic.edit import UpdateView
+
from .forms import CustomPasswordChangeForm
from .services.email_service import TournamentEmailService
from .services.tournament_registration import RegistrationCartManager
from .services.payment_service import PaymentService
-from django.views.decorators.csrf import csrf_exempt
import logging
@@ -171,6 +165,60 @@ 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 future_tournaments_by_month(club_id, year, month):
+ """Get future tournaments filtered by year and month"""
+
+ logger.info(f'filter {year} {month}')
+ tournaments = tournaments_query(Q(end_date__isnull=True), club_id, True)
+ filtered_tournaments = []
+ for t in tournaments:
+ if (t.display_tournament() and t.starts_in_the_future() and
+ t.start_date.year == year and t.start_date.month == month + 1):
+ filtered_tournaments.append(t)
+ return filtered_tournaments
+
+def finished_tournaments_by_month(club_id, year, month):
+ """Get finished tournaments filtered by year and month"""
+ clean_ended_tournaments = tournaments_query(Q(end_date__isnull=False), club_id, False)
+ 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
+ )
+ 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
+
+ # Filter by year and month
+ filtered_tournaments = []
+ for t in all_tournaments:
+ if t.start_date.year == year and t.start_date.month == month:
+ filtered_tournaments.append(t)
+
+ # Sort by start_date descending
+ filtered_tournaments.sort(key=lambda t: t.start_date, reverse=True)
+ return filtered_tournaments
+
+def get_available_years_for_tournaments(club_id, filter_type):
+ """Get available years for tournaments based on filter type"""
+ if filter_type == 0: # Future tournaments
+ tournaments = tournaments_query(Q(end_date__isnull=True), club_id, True)
+ tournaments = [t for t in tournaments if t.display_tournament() and t.starts_in_the_future()]
+ elif filter_type == 2: # Finished tournaments
+ tournaments = finished_tournaments(club_id)
+ else:
+ return []
+
+ years = set()
+ for t in tournaments:
+ years.add(t.start_date.year)
+
+ return sorted(years, reverse=(filter_type == 2))
+
def tournament_info(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
registered_user = None
@@ -217,18 +265,45 @@ def tournaments(request):
return redirect('/')
club_id = request.GET.get('club')
+ year_param = request.GET.get('year')
+ month_param = request.GET.get('month')
title = ''
tournaments = []
- if filter==0:
+ available_years = []
+ selected_year = None
+ if month_param:
+ selected_month = int(month_param)
+ else:
+ selected_month = timezone.now().month - 1
+
+ if filter == 0:
title = 'À venir'
- tournaments = future_tournaments(club_id)
- elif filter==1:
+ available_years, selected_year = handle_year_month_filtering(year_param, club_id, filter, is_future=True)
+
+ if selected_year and selected_month:
+ tournaments = future_tournaments_by_month(club_id, selected_year, selected_month)
+ elif selected_year:
+ tournaments = future_tournaments(club_id)
+ else:
+ tournaments = future_tournaments(club_id)
+
+ elif filter == 1:
title = 'En cours'
tournaments = live_tournaments(club_id)
- elif filter==2:
+
+ elif filter == 2:
title = 'Terminés'
- tournaments = finished_tournaments(club_id)
+ available_years, selected_year = handle_year_month_filtering(year_param, club_id, filter, is_future=False)
+
+ if selected_year and selected_month:
+ tournaments = finished_tournaments_by_month(club_id, selected_year, selected_month)
+ elif selected_year:
+ tournaments = finished_tournaments(club_id)
+ else:
+ tournaments = finished_tournaments(club_id)
+
+ locale.setlocale(locale.LC_TIME, 'fr_FR.UTF-8') # Define month names in french
return render(
request,
@@ -239,9 +314,39 @@ def tournaments(request):
'second_title': title,
'head_title': title,
'club': club_id,
+ 'filter': filter,
+ 'available_years': available_years,
+ 'available_months': range(0, 12),
+ 'selected_year': selected_year,
+ 'selected_month': selected_month,
+ 'month_names': list(calendar.month_name)[1:],
+ 'year': year_param,
+ 'month': month_param,
}
)
+def handle_year_month_filtering(year_param, club_id, filter_type, is_future=True):
+ """Helper function to handle year/month filtering for future and finished tournaments"""
+ available_years = get_available_years_for_tournaments(club_id, filter_type)
+ selected_year = None
+
+ # Handle year selection
+ current_year = timezone.now().year
+ if not year_param and available_years:
+ if is_future:
+ selected_year = current_year if current_year in available_years else available_years[0]
+ else:
+ selected_year = available_years[0] # Most recent year for finished tournaments
+ elif year_param:
+ try:
+ selected_year = int(year_param)
+ if selected_year not in available_years:
+ selected_year = available_years[0] if available_years else None
+ except:
+ selected_year = available_years[0] if available_years else None
+
+ return available_years, selected_year
+
def clubs(request):
all_clubs = Club.objects.all().order_by('name')
clubs = []