From 576bc2f273baf6ca33c22495eb9cc3d50501822f Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 9 Jul 2025 12:15:11 +0200 Subject: [PATCH] fix fft search --- api/urls.py | 5 + api/utils.py | 53 +++- api/views.py | 297 +++++++++++++++++- requirements.txt | 1 + .../commands/test_fft_all_tournaments.py | 222 +++++++++++++ .../management/commands/test_fft_scraper.py | 103 ++++++ 6 files changed, 673 insertions(+), 8 deletions(-) create mode 100644 tournaments/management/commands/test_fft_all_tournaments.py create mode 100644 tournaments/management/commands/test_fft_scraper.py diff --git a/api/urls.py b/api/urls.py index 512d39d..af78bcd 100644 --- a/api/urls.py +++ b/api/urls.py @@ -44,6 +44,11 @@ urlpatterns = [ path('xls-to-csv/', views.xls_to_csv, name='xls-to-csv'), path('config/tournament/', views.get_tournament_config, name='tournament-config'), path('config/payment/', views.get_payment_config, name='payment-config'), + path('fft/club-tournaments/', views.get_fft_club_tournaments, name='get-fft-club-tournaments'), + path('fft/club-tournaments-complete/', views.get_fft_club_tournaments_with_umpire_data, name='get-fft-club-tournaments-complete'), + path('fft/all-tournaments/', views.get_fft_all_tournaments, name='get-fft-all-tournaments'), + path('fft/umpire//', views.get_fft_umpire_data, name='get-fft-umpire-data'), + path('fft/federal-clubs/', views.get_fft_federal_clubs, name='get-fft-federal-clubs'), # authentication path("change-password/", ChangePasswordView.as_view(), name="change_password"), diff --git a/api/utils.py b/api/utils.py index cc79258..8f03d24 100644 --- a/api/utils.py +++ b/api/utils.py @@ -40,7 +40,7 @@ def scrape_fft_club_tournaments(club_code, club_name, start_date=None, end_date= logger.info(f"Navigating to: {target_url}") page_obj.goto(target_url) - page_obj.wait_for_timeout(7000) + # page_obj.wait_for_timeout(7000) current_url = page_obj.url logger.info(f"Current URL: {current_url}") @@ -197,7 +197,7 @@ def scrape_fft_club_tournaments_all_pages(club_code, club_name, start_date=None, page += 1 logger.info(f"Moving to page {page}") - time.sleep(1) # Rate limiting + # time.sleep(1) # Rate limiting except Exception as e: logger.error(f"Error on page {page}: {e}") @@ -393,7 +393,7 @@ def scrape_fft_all_tournaments(sorting_option=None, page=0, start_date=None, end logger.info(f"Navigating to: {target_url}") page_obj.goto(target_url) - page_obj.wait_for_timeout(7000) + # page_obj.wait_for_timeout(7000) current_url = page_obj.url logger.info(f"Current URL: {current_url}") @@ -425,7 +425,46 @@ def scrape_fft_all_tournaments(sorting_option=None, page=0, start_date=None, end except ValueError: logger.warning(f"Invalid date format: {start_date}") - # Build city parameters with distance + # Build filter parameters + filter_params = "" + + # Add categories filter + if categories: + logger.info(f"Adding categories filter: {categories}") + for category in categories: + filter_params += f"&epreuve[{category}]={category}" + + # Add levels filter + if levels: + logger.info(f"Adding levels filter: {levels}") + for level in levels: + filter_params += f"&categorie_tournoi[{level}]={level}" + + # Add ages filter + if ages: + logger.info(f"Adding ages filter: {ages}") + for age in ages: + filter_params += f"&categorie_age[{age}]={age}" + + # Add types filter + if tournament_types: + logger.info(f"Adding types filter: {tournament_types}") + for t_type in tournament_types: + capitalized_type = t_type.capitalize() + filter_params += f"&type[{capitalized_type}]={capitalized_type}" + + # Add national cup filter + if national_cup: + logger.info("Adding national cup filter") + filter_params += "&tournoi_npc=1" + + # Fix the sorting parameter + if sorting_option: + sort_param = f"&sort={sorting_option}" + else: + sort_param = "&sort=dateDebut+asc" + + # Build city parameters with distance and location if city and city.strip(): city_name_encoded = city.strip().replace(" ", "+") @@ -440,11 +479,11 @@ def scrape_fft_all_tournaments(sorting_option=None, page=0, start_date=None, end if lat and lng: location_params = f"&ville[autocomplete][value_container][lat_field]={lat}&ville[autocomplete][value_container][lng_field]={lng}" - # Combine all parameters - params = f"{base_params}{location_params}{distance_param}&pratique=PADEL{date_component}&page={page}&sort=dateDebut+asc&form_build_id={form_build_id}&form_id=recherche_tournois_form&_triggering_element_name=submit_page&_triggering_element_value=Submit+page" + # Combine all parameters including filters + params = f"{base_params}{location_params}{distance_param}&pratique=PADEL{date_component}&page={page}{sort_param}&form_build_id={form_build_id}&form_id=recherche_tournois_form&_triggering_element_name=submit_page&_triggering_element_value=Submit+page" else: # Default to ligue search if no city provided - params = f"recherche_type=ligue&pratique=PADEL{date_component}&page={page}&sort=dateDebut+asc&form_build_id={form_build_id}&form_id=recherche_tournois_form&_triggering_element_name=submit_page&_triggering_element_value=Submit+page" + params = f"recherche_type=ligue&pratique=PADEL{date_component}{filter_params}&page={page}&sort={sorting_option or 'dateDebut+asc'}&form_build_id={form_build_id}&form_id=recherche_tournois_form&_triggering_element_name=submit_page&_triggering_element_value=Submit+page" logger.info(f"AJAX Parameters: {params}") diff --git a/api/views.py b/api/views.py index caed490..c41c58a 100644 --- a/api/views.py +++ b/api/views.py @@ -16,7 +16,7 @@ from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer, Image from .permissions import IsClubOwner -from .utils import check_version_smaller_than_1_1_12 +from .utils import check_version_smaller_than_1_1_12, scrape_fft_club_tournaments, scrape_fft_club_tournaments_all_pages, get_umpire_data, scrape_fft_all_tournaments from shared.discord import send_discord_log_message @@ -579,3 +579,298 @@ def is_granted_unlimited_access(request): if purchase.is_active(): can_create = True return JsonResponse({'can_create': can_create}, status=status.HTTP_200_OK) + +@api_view(['GET', 'POST']) +@permission_classes([]) +def get_fft_club_tournaments(request): + """ + API endpoint to get tournaments for a specific club + Handles pagination automatically to get all results + """ + try: + if request.method == 'POST': + data = request.data + else: + data = request.GET + + club_code = data.get('club_code', '62130180') + club_name = data.get('club_name', 'TENNIS SPORTING CLUB DE CASSIS') + start_date = data.get('start_date') + end_date = data.get('end_date') + paginate = data.get('paginate', 'true').lower() == 'true' + + if paginate: + # Get all pages automatically (matching Swift behavior) + result = scrape_fft_club_tournaments_all_pages( + club_code=club_code, + club_name=club_name, + start_date=start_date, + end_date=end_date + ) + else: + # Get single page + page = int(data.get('page', 0)) + result = scrape_fft_club_tournaments( + club_code=club_code, + club_name=club_name, + start_date=start_date, + end_date=end_date, + page=page + ) + + if result: + return JsonResponse({ + 'success': True, + 'tournaments': result.get('tournaments', []), + 'total_results': result.get('total_results', 0), + 'current_count': result.get('current_count', 0), + 'pages_scraped': result.get('pages_scraped', 1), + 'message': f'Successfully scraped {len(result.get("tournaments", []))} tournaments' + }, status=status.HTTP_200_OK) + else: + return JsonResponse({ + 'success': False, + 'tournaments': [], + 'total_results': 0, + 'current_count': 0, + 'message': 'Failed to scrape club tournaments' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + except Exception as e: + logger.error(f"Error in get_fft_club_tournaments endpoint: {e}") + return JsonResponse({ + 'success': False, + 'tournaments': [], + 'message': f'Unexpected error: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + +@api_view(['GET']) +@permission_classes([]) +def get_fft_umpire_data(request, tournament_id): + """ + API endpoint to get umpire data for a specific tournament + Returns data that can be used to populate japPhoneNumber field + """ + try: + name, email, phone = get_umpire_data(tournament_id) + + return JsonResponse({ + 'success': True, + 'umpire': { + 'name': name, + 'email': email, + 'phone': phone + }, + 'japPhoneNumber': phone, # Direct field for updating FederalTournament + 'message': 'Umpire data retrieved successfully' + }, status=status.HTTP_200_OK) + + except Exception as e: + logger.error(f"Error in get_fft_umpire_data endpoint: {e}") + return JsonResponse({ + 'success': False, + 'umpire': None, + 'japPhoneNumber': None, + 'message': f'Unexpected error: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + +@api_view(['GET', 'POST']) +@permission_classes([]) +def get_fft_all_tournaments(request): + """ + API endpoint to get all tournaments with filters + """ + try: + if request.method == 'POST': + data = request.data + else: + data = request.GET + + # Extract parameters + sorting_option = data.get('sorting_option', 'dateDebut+asc') + page = int(data.get('page', 0)) + start_date = data.get('start_date') + end_date = data.get('end_date') + city = data.get('city', '') + distance = float(data.get('distance', 15)) + categories = data.getlist('categories') if hasattr(data, 'getlist') else data.get('categories', []) + levels = data.getlist('levels') if hasattr(data, 'getlist') else data.get('levels', []) + lat = data.get('lat') + lng = data.get('lng') + ages = data.getlist('ages') if hasattr(data, 'getlist') else data.get('ages', []) + tournament_types = data.getlist('types') if hasattr(data, 'getlist') else data.get('types', []) + national_cup = data.get('national_cup', 'false').lower() == 'true' + + result = scrape_fft_all_tournaments( + sorting_option=sorting_option, + page=page, + start_date=start_date, + end_date=end_date, + city=city, + distance=distance, + categories=categories, + levels=levels, + lat=lat, + lng=lng, + ages=ages, + tournament_types=tournament_types, + national_cup=national_cup + ) + + if result: + return JsonResponse({ + 'success': True, + 'tournaments': result.get('tournaments', []), + 'total_results': result.get('total_results', 0), + 'current_count': result.get('current_count', 0), + 'page': page, + 'message': f'Successfully scraped {len(result.get("tournaments", []))} tournaments' + }, status=status.HTTP_200_OK) + else: + return JsonResponse({ + 'success': False, + 'tournaments': [], + 'total_results': 0, + 'current_count': 0, + 'message': 'Failed to scrape all tournaments' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + except Exception as e: + logger.error(f"Error in get_fft_all_tournaments endpoint: {e}") + return JsonResponse({ + 'success': False, + 'tournaments': [], + 'message': f'Unexpected error: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + +@api_view(['GET', 'POST']) +@permission_classes([]) +def get_fft_federal_clubs(request): + """ + API endpoint to get federal clubs + """ + try: + if request.method == 'POST': + data = request.data + else: + data = request.GET + + country = data.get('country', 'fr') + city = data.get('city', '') + radius = float(data.get('radius', 15)) + latitude = data.get('latitude') + longitude = data.get('longitude') + + if latitude: + latitude = float(latitude) + if longitude: + longitude = float(longitude) + + result = scrape_federal_clubs( + country=country, + city=city, + radius=radius, + latitude=latitude, + longitude=longitude + ) + + if result: + return JsonResponse({ + 'success': True, + 'clubs': result, + 'message': 'Federal clubs retrieved successfully' + }, status=status.HTTP_200_OK) + else: + return JsonResponse({ + 'success': False, + 'clubs': [], + 'message': 'Failed to retrieve federal clubs' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + except Exception as e: + logger.error(f"Error in get_fft_federal_clubs endpoint: {e}") + return JsonResponse({ + 'success': False, + 'clubs': [], + 'message': f'Unexpected error: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + +@api_view(['GET', 'POST']) +@permission_classes([]) +def get_fft_club_tournaments_with_umpire_data(request): + """ + Combined endpoint that gets club tournaments and enriches them with umpire data + This matches the complete workflow from your Swift code + """ + try: + if request.method == 'POST': + data = request.data + else: + data = request.GET + + club_code = data.get('club_code', '62130180') + club_name = data.get('club_name', 'TENNIS SPORTING CLUB DE CASSIS') + start_date = data.get('start_date') + end_date = data.get('end_date') + include_umpire_data = data.get('include_umpire_data', 'false').lower() == 'true' + + # Get all tournaments for the club + result = scrape_fft_club_tournaments_all_pages( + club_code=club_code, + club_name=club_name, + start_date=start_date, + end_date=end_date + ) + + if not result: + return JsonResponse({ + 'success': False, + 'tournaments': [], + 'message': 'Failed to scrape club tournaments' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + tournaments = result.get('tournaments', []) + + # Enrich with umpire data if requested + if include_umpire_data: + logger.info(f"Enriching {len(tournaments)} tournaments with umpire data...") + + for tournament in tournaments: + try: + tournament_id = tournament.get('id') + if tournament_id: + name, email, phone = get_umpire_data(tournament_id) + tournament['japPhoneNumber'] = phone + + # Also update jugeArbitre if we got more data + if name and not tournament.get('jugeArbitre'): + tournament['jugeArbitre'] = { + 'nom': name.split(' ')[-1] if name else None, + 'prenom': ' '.join(name.split(' ')[:-1]) if name and ' ' in name else name + } + + # Small delay to avoid rate limiting + time.sleep(0.5) + + except Exception as e: + logger.warning(f"Failed to get umpire data for tournament {tournament_id}: {e}") + continue + + return JsonResponse({ + 'success': True, + 'tournaments': tournaments, + 'total_results': result.get('total_results', 0), + 'current_count': len(tournaments), + 'pages_scraped': result.get('pages_scraped', 1), + 'umpire_data_included': include_umpire_data, + 'message': f'Successfully scraped {len(tournaments)} tournaments' + + (' with umpire data' if include_umpire_data else '') + }, status=status.HTTP_200_OK) + + except Exception as e: + logger.error(f"Error in get_fft_club_tournaments_with_umpire_data endpoint: {e}") + return JsonResponse({ + 'success': False, + 'tournaments': [], + 'message': f'Unexpected error: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/requirements.txt b/requirements.txt index f0cf5e5..892df2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,4 @@ cryptography==41.0.7 stripe==11.6.0 django-background-tasks==1.2.8 Pillow==10.2.0 +playwright==1.40.0 diff --git a/tournaments/management/commands/test_fft_all_tournaments.py b/tournaments/management/commands/test_fft_all_tournaments.py new file mode 100644 index 0000000..f0c3408 --- /dev/null +++ b/tournaments/management/commands/test_fft_all_tournaments.py @@ -0,0 +1,222 @@ +from django.core.management.base import BaseCommand +from datetime import datetime, timedelta +import logging + +class Command(BaseCommand): + help = 'Test FFT all tournaments scraping with various filters' + + def add_arguments(self, parser): + parser.add_argument( + '--sorting', + type=str, + default='dateDebut+asc', + choices=['dateDebut+asc', 'dateDebut+desc', '_DISTANCE_'], + help='Sorting option (default: dateDebut+asc)' + ) + parser.add_argument( + '--page', + type=int, + default=0, + help='Page number to scrape (default: 0)' + ) + parser.add_argument( + '--city', + type=str, + default='', + help='City to search around' + ) + parser.add_argument( + '--distance', + type=float, + default=15.0, + help='Distance in km (default: 15)' + ) + parser.add_argument( + '--categories', + nargs='*', + default=[], + help='Tournament categories to filter by' + ) + parser.add_argument( + '--levels', + nargs='*', + default=[], + help='Tournament levels to filter by' + ) + parser.add_argument( + '--ages', + nargs='*', + default=[], + help='Age categories to filter by' + ) + parser.add_argument( + '--types', + nargs='*', + default=[], + help='Tournament types to filter by' + ) + parser.add_argument( + '--national-cup', + action='store_true', + help='Filter for national cup tournaments only' + ) + parser.add_argument( + '--lat', + type=float, + help='Latitude for location-based search' + ) + parser.add_argument( + '--lng', + type=float, + help='Longitude for location-based search' + ) + parser.add_argument( + '--days-ahead', + type=int, + default=90, + help='How many days ahead to search (default: 90)' + ) + parser.add_argument( + '--start-date', + type=str, + help='Start date in DD/MM/YY format (overrides --days-ahead)' + ) + parser.add_argument( + '--end-date', + type=str, + help='End date in DD/MM/YY format (overrides --days-ahead)' + ) + parser.add_argument( + '--verbose', + action='store_true', + help='Enable verbose logging' + ) + + def handle(self, *args, **options): + if options['verbose']: + logging.basicConfig(level=logging.INFO) + + # Extract options + sorting_option = options['sorting'] + page = options['page'] + city = options['city'] + distance = options['distance'] + categories = options['categories'] + levels = options['levels'] + ages = options['ages'] + tournament_types = options['types'] + national_cup = options['national_cup'] + lat = options['lat'] + lng = options['lng'] + verbose = options['verbose'] + + # Calculate date range + if options['start_date'] and options['end_date']: + start_date_str = options['start_date'] + end_date_str = options['end_date'] + else: + start_date = datetime.now() + end_date = start_date + timedelta(days=options['days_ahead']) + start_date_str = start_date.strftime('%d/%m/%y') + end_date_str = end_date.strftime('%d/%m/%y') + + self.stdout.write(self.style.SUCCESS("=== FFT All Tournaments Scraper ===")) + self.stdout.write(f"Sorting: {sorting_option}") + self.stdout.write(f"Page: {page}") + self.stdout.write(f"Date range: {start_date_str} to {end_date_str}") + self.stdout.write(f"City: {city if city else 'Not specified'}") + self.stdout.write(f"Distance: {distance} km") + self.stdout.write(f"Categories: {categories if categories else 'All'}") + self.stdout.write(f"Levels: {levels if levels else 'All'}") + self.stdout.write(f"Ages: {ages if ages else 'All'}") + self.stdout.write(f"Types: {tournament_types if tournament_types else 'All'}") + self.stdout.write(f"National Cup: {'Yes' if national_cup else 'No'}") + if lat and lng: + self.stdout.write(f"Location: {lat}, {lng}") + self.stdout.write(f"Method: Playwright (Chrome-free)") + self.stdout.write("") + + try: + from api.utils import scrape_fft_all_tournaments + self.stdout.write("šŸš€ Testing general tournament scraping...") + + result = scrape_fft_all_tournaments( + sorting_option=sorting_option, + page=page, + start_date=start_date_str, + end_date=end_date_str, + city=city, + distance=distance, + categories=categories, + levels=levels, + lat=lat, + lng=lng, + ages=ages, + tournament_types=tournament_types, + national_cup=national_cup + ) + + # Debug: Show what we got (only in verbose mode) + if verbose: + self.stdout.write(f"šŸ” Raw result: {result}") + + if result: + tournaments = result.get('tournaments', []) + self.stdout.write(self.style.SUCCESS(f"āœ… SUCCESS: {len(tournaments)} tournaments found")) + + if tournaments: + self.stdout.write("\nšŸ“ Sample tournaments:") + # Show first 3 tournaments + for i, tournament in enumerate(tournaments[:3]): + self.stdout.write(f"\n Tournament {i+1}:") + self.stdout.write(f" ID: {tournament.get('id')}") + self.stdout.write(f" Name: {tournament.get('libelle')}") + self.stdout.write(f" Date: {tournament.get('dateDebut', {}).get('date', 'N/A')}") + self.stdout.write(f" Club: {tournament.get('nomClub', 'N/A')}") + self.stdout.write(f" City: {tournament.get('villeEngagement', 'N/A')}") + self.stdout.write(f" Category: {tournament.get('categorieTournoi', 'N/A')}") + self.stdout.write(f" Type: {tournament.get('type', 'N/A')}") + if tournament.get('jugeArbitre'): + self.stdout.write(f" Judge: {tournament.get('jugeArbitre', {}).get('nom', 'N/A')}") + + self.stdout.write(f"\nšŸ“Š Summary:") + self.stdout.write(f" Total tournaments: {len(tournaments)}") + self.stdout.write(f" Current page: {page}") + self.stdout.write(f" Total results available: {result.get('total_results', 'Unknown')}") + + # Analysis of results + if tournaments: + cities = set() + clubs = set() + categories = set() + types = set() + + for tournament in tournaments: + if tournament.get('villeEngagement'): + cities.add(tournament['villeEngagement']) + if tournament.get('nomClub'): + clubs.add(tournament['nomClub']) + if tournament.get('categorieTournoi'): + categories.add(tournament['categorieTournoi']) + if tournament.get('type'): + types.add(tournament['type']) + + self.stdout.write(f"\nšŸ” Analysis:") + self.stdout.write(f" Unique cities: {len(cities)}") + self.stdout.write(f" Unique clubs: {len(clubs)}") + self.stdout.write(f" Unique categories: {len(categories)}") + self.stdout.write(f" Unique types: {len(types)}") + + if verbose: + self.stdout.write(f"\n Cities: {sorted(list(cities))[:10]}") # Show first 10 + self.stdout.write(f" Categories: {sorted(list(categories))}") + self.stdout.write(f" Types: {sorted(list(types))}") + + else: + self.stdout.write(self.style.ERROR("āŒ FAILED: No tournaments found")) + + except Exception as e: + self.stdout.write(self.style.ERROR(f"āŒ ERROR: {e}")) + import traceback + if verbose: + self.stdout.write(traceback.format_exc()) diff --git a/tournaments/management/commands/test_fft_scraper.py b/tournaments/management/commands/test_fft_scraper.py new file mode 100644 index 0000000..5917c9e --- /dev/null +++ b/tournaments/management/commands/test_fft_scraper.py @@ -0,0 +1,103 @@ +from django.core.management.base import BaseCommand +from datetime import datetime, timedelta +import logging + +class Command(BaseCommand): + help = 'Test FFT tournament scraping with Playwright' + + def add_arguments(self, parser): + parser.add_argument( + '--club-code', + type=str, + default='62130180', + help='Club code for testing (default: 62130180)' + ) + parser.add_argument( + '--club-name', + type=str, + default='TENNIS SPORTING CLUB DE CASSIS', + help='Club name for testing' + ) + parser.add_argument( + '--all-pages', + action='store_true', + help='Test all pages scraping' + ) + parser.add_argument( + '--verbose', + action='store_true', + help='Enable verbose logging' + ) + + def handle(self, *args, **options): + if options['verbose']: + logging.basicConfig(level=logging.INFO) + + club_code = options['club_code'] + club_name = options['club_name'] + all_pages = options['all_pages'] + verbose = options['verbose'] + + # Calculate date range + start_date = datetime.now() + end_date = start_date + timedelta(days=90) + start_date_str = start_date.strftime('%d/%m/%y') + end_date_str = end_date.strftime('%d/%m/%y') + + self.stdout.write(self.style.SUCCESS("=== FFT Tournament Scraper ===")) + self.stdout.write(f"Club: {club_name} ({club_code})") + self.stdout.write(f"Date range: {start_date_str} to {end_date_str}") + self.stdout.write(f"Method: Playwright (Chrome-free)") + self.stdout.write("") + + try: + if all_pages: + from api.utils import scrape_fft_club_tournaments_all_pages + self.stdout.write("šŸš€ Testing complete tournament scraping...") + + result = scrape_fft_club_tournaments_all_pages( + club_code=club_code, + club_name=club_name, + start_date=start_date_str, + end_date=end_date_str + ) + else: + from api.utils import scrape_fft_club_tournaments + self.stdout.write("šŸš€ Testing single page scraping...") + + result = scrape_fft_club_tournaments( + club_code=club_code, + club_name=club_name, + start_date=start_date_str, + end_date=end_date_str, + page=0 + ) + + # Debug: Show what we got (only in verbose mode) + if verbose: + self.stdout.write(f"šŸ” Raw result: {result}") + + if result: + tournaments = result.get('tournaments', []) + self.stdout.write(self.style.SUCCESS(f"āœ… SUCCESS: {len(tournaments)} tournaments found")) + + if tournaments: + self.stdout.write("\nšŸ“ Sample tournament:") + sample = tournaments[0] + self.stdout.write(f" ID: {sample.get('id')}") + self.stdout.write(f" Name: {sample.get('libelle')}") + self.stdout.write(f" Date: {sample.get('dateDebut', {}).get('date', 'N/A')}") + self.stdout.write(f" Judge: {sample.get('jugeArbitre', {}).get('nom', 'N/A')}") + + self.stdout.write(f"\nšŸ“Š Summary:") + self.stdout.write(f" Total tournaments: {len(tournaments)}") + self.stdout.write(f" Pages scraped: {result.get('pages_scraped', 1)}") + + else: + self.stdout.write(self.style.ERROR("āŒ FAILED: No tournaments found")) + + except Exception as e: + self.stdout.write(self.style.ERROR(f"āŒ ERROR: {e}")) + import traceback + if verbose: + self.stdout.write(traceback.format_exc())