from django.http import HttpResponse, JsonResponse
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib import messages
from django.shortcuts import redirect
from datetime import datetime
import requests
import json
import time
import unicodedata
import urllib.parse # Add this import
import os
from django.conf import settings
from django.template import Template, Context
from django.middleware.csrf import get_token
import concurrent.futures
from functools import partial
default_sexe = 'F'
default_id_homologation = "82546485"
default_session_id = "JSESSIONID=CEC70DF4428E76E1FD1BFE5C66904708; AWSALB=omN79AoahQc27iH5vvO14U7ZrjH30faWu5delXAthjiYVq4jzbeXJ0IOmVTGjG6YDoi7Do2uCswhEaO/smz1QG733RpYlsw7ShlFV/X2aLn2L7/DZ5KUBA/8LPNr; AWSALBCORS=omN79AoahQc27iH5vvO14U7ZrjH30faWu5delXAthjiYVq4jzbeXJ0IOmVTGjG6YDoi7Do2uCswhEaO/smz1QG733RpYlsw7ShlFV/X2aLn2L7/DZ5KUBA/8LPNr; incap_ses_2223_2712217=g6xvVwmOBh66wpenPa/ZHpN2ZmgAAAAAcmuXPCKJ1/mEqKuQEXJS2Q==; tc_cj_v2=m_iZZZ%22**%22%27%20ZZZKQNRSMQNLMRQLZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQOKMQKQONJKOZZZ%5D777_rn_lh%5BfyfcheZZZ222H%2B%7B%7E%20%27-%20%21%20-%20%29%7D%20H%7D*%28ZZZKQOKMQKRLNNPMZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQOKNRJOLLQOJZZZ%5D777_rn_lh%5BfyfcheZZZ%2F%20%290%2BH%2C0%200%20G%24%2FH%29%20%2FZZZKQOKOLRLNNQJLZZZ%5D; tc_cj_v2_cmp=; tc_cj_v2_med=; SSESS7ba44afc36c80c3faa2b8fa87e7742c5=4-IzUXNKXq_BQFMLjjivcLW14OXgk3lLPl18WYgSmU0; xtan=-; xtant=1; pa_vid=%22mckhos3iasswydjm%22; datadome=oi7wKIO2uaUDCcpICiRO1_hEYcwyQWDVbXyNCSkAmr315~8pnPcuXWKfvNEEz~jKcoORIOThSRe~AxoRRrPLUsr0miWm7AdAcy~_3hABc1ZWfRt~SKGa_uhyqiE0Hzfj; _pcid=%7B%22browserId%22%3A%22mckhos3iasswydjm%22%2C%22_t%22%3A%22ms8wm9hs%7Cmckhos5s%22%7D; _pctx=%7Bu%7DN4IgrgzgpgThIC4B2YA2qA05owMoBcBDfSREQpAeyRCwgEt8oBJAE0RXSwH18yBbCAA4A7vwCcACwgAffgGMA1pMoQArPAC%2BQA; EA_SESSION_ID=E15E1DD5A23272A1A0CC3B8CEDF56B65; refresh_token=eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzYjQ2ODk1ZC0zN2EzLTQzM2QtYmQ1My01N2QxZTM1YTI3NzkifQ.eyJleHAiOjE3NTY1NTM5MjgsImlhdCI6MTc1MTM3MjAwNCwianRpIjoiYzJiNzA3N2UtZmQ5MS00ZGM4LWI4ZDEtMzA2MDdkYjk5MTgxIiwiaXNzIjoiaHR0cHM6Ly9sb2dpbi5mZnQuZnIvcmVhbG1zL2Nvbm5lY3QiLCJhdWQiOiJodHRwczovL2xvZ2luLmZmdC5mci9yZWFsbXMvY29ubmVjdCIsInN1YiI6IjI3ZDQ5NzRjLTEwZWUtNDNlOC1iOTczLWUyMzc2MDM1ZTE0MSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJtZWEtc2l0ZSIsInNpZCI6IjM5NTZjMzZlLTczMWItNDJkNy1iNjI2LTE2MGViY2Y2YTY2ZiIsInNjb3BlIjoib3BlbmlkIHJvbGVzIHJlYWQ6bGljZW5jZSByZWFkOmlkZW50aXR5IGVtYWlsIHByb2ZpbGUifQ.e6v5vlen985vSFJhrgMQTTB3fzzsnwugPfXKoyib1QSIBZ9kC47h1cYwcpam0VmZ9vRD_y0hVC14jDvBR6d1dQ; user_login=10000984864; user_token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJRaTV3bWx2bTNuX2p1YW4tSTl1dHo3UGZRLU1tVVlvektwSExhbm9lTXI4In0.eyJleHAiOjE3NTEzNzIzMDQsImlhdCI6MTc1MTM3MjAwNCwianRpIjoiMzEzMGVhODUtNjFjNC00OGRjLWFlNGMtZTIwZmZhYTU3YTlhIiwiaXNzIjoiaHR0cHM6Ly9sb2dpbi5mZnQuZnIvcmVhbG1zL2Nvbm5lY3QiLCJhdWQiOlsiZmVkLWFwaSIsImFjY291bnQiXSwic3ViIjoiMjdkNDk3NGMtMTBlZS00M2U4LWI5NzMtZTIzNzYwMzVlMTQxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoibWVhLXNpdGUiLCJzaWQiOiIzOTU2YzM2ZS03MzFiLTQyZDctYjYyNi0xNjBlYmNmNmE2NmYiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1jb25uZWN0Il19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCJdfX0sInNjb3BlIjoib3BlbmlkIHJlYWQ6bGljZW5jZSByZWFkOmlkZW50aXR5IGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaWRDcm0iOiIxMDAwMDk4NDg2NCIsIm5hbWUiOiJSYXptaWcgU0FSS0lTU0lBTiIsInByZWZlcnJlZF91c2VybmFtZSI6InJhem1vZyIsImdpdmVuX25hbWUiOiJSYXptaWciLCJzZXNzaW9uX3N0YXRlIjoiMzk1NmMzNmUtNzMxYi00MmQ3LWI2MjYtMTYwZWJjZjZhNjZmIiwibG9jYWxlIjoiZnIiLCJmYW1pbHlfbmFtZSI6IlNBUktJU1NJQU4iLCJlbWFpbCI6InJhem1pZy5zYXJraXNzaWFuQGdtYWlsLmNvbSJ9.VSjG2htaUMt_acrqL3VcAjVMhAno9q0vdb7LTzw8UVbjIiDLzhR5msRxI8h8gSJ38kFLaa7f_SFGLIsRCSdcmhYRd2zKIrcPE-QFKbsPnH69xN2i3giMMiYEy3hj__IIyijt9z3W4KXeQdwUrlXPxprlXQ2sYTlZG63HlCGq1iI3Go9eXFmNDNM6p1jBypXcHEvJr6HwNcRdn6ZGfZ9LLMZ2aMEJAhDqL2CLrFrOZkGQpFz7ITUi_DVJAqh5DmTK1JqPswcOjhuZhDT7qWNfIleV-L7XCwvofxBwkSX9ve9l_3COZJXbsMiiRdCSTZtewlFRfgo4IuAu3g06fmJw7g; TCID=; nlbi_2712217=Ok4tKplxIEN+k1gmb9lUTgAAAAA70zbGXpiElrV2qkRjBeXO; visid_incap_2712217=LW/brcN4Rwml/7waoG/rloFBYmgAAAAAQUIPAAAAAAAlHbwlYSPbNS2qq3UBZNK8; TCPID=125629554310878226394; xtvrn=$548419$"
def calculate_age_from_birth_date(birth_date_str):
"""
Calculate age from birth date string (format: DD/MM/YYYY)
Returns age as integer or None if parsing fails
"""
if not birth_date_str:
return None
try:
# Parse French date format DD/MM/YYYY
birth_date = datetime.strptime(birth_date_str, '%d/%m/%Y')
today = datetime.now()
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
return age
except (ValueError, TypeError):
return None
def find_best_license_match(license_results, player):
"""
Find the best matching license from multiple results using ageSportif comparison
Also filters out players without valid classement data
Args:
license_results: List of license data from API
player_age_sportif: Age from ranking data
Returns:
Tuple of (best_match, match_info)
"""
# Get player's age from ranking data for duplicate matching
player_age_sportif = player.get('ageSportif')
rank = player.get('classement')
lastname = player.get('nom')
firstname = player.get('prenom')
if not license_results:
return None, {"reason": "no_results"}
# First, filter out players without valid classement data
def has_valid_classement(license_data, rank):
"""Check if a license has valid classement data"""
classement = license_data.get('classement', {})
if not classement:
return False
# Check if any of the key classement fields have meaningful data
date_fr = classement.get('dateFr', '').strip()
rang = classement.get('rang')
points = classement.get('points')
date = classement.get('date')
# Consider it valid if at least one of these conditions is met:
# - dateFr is not empty
# - rang is not None
# - points is not None (and > 0)
# - date is not None
return (
rang is not None and rang == rank
)
# First, filter out players without valid classement data
def has_valid_name(license_data, firstname, lastname):
lk_firstname = license_data.get('prenom', '')
lk_lastname = license_data.get('nom', '')
if not lk_firstname and not lk_lastname:
return False
return (
lk_firstname == firstname and lk_lastname == lastname
)
# Filter license results to only include those with valid classement
valid_license_results = [
license_data for license_data in license_results
if has_valid_name(license_data, firstname, lastname)
if has_valid_classement(license_data, rank)
]
# If no valid results after filtering, return None
if not valid_license_results:
return None, {
"reason": "no_valid_classement",
"original_count": len(license_results),
"filtered_count": 0
}
# If only one valid result, return it
if len(valid_license_results) == 1:
return valid_license_results[0], {
"reason": "single_valid_result",
"original_count": len(license_results),
"filtered_count": 1,
"age_match": "n/a"
}
# If we don't have ageSportif from ranking, take the first valid match
if player_age_sportif is None:
return valid_license_results[0], {
"reason": "no_age_data_used_first_valid",
"original_count": len(license_results),
"filtered_count": len(valid_license_results),
"used_first_result": True
}
best_match = None
best_age_diff = float('inf')
match_details = []
best_match_count = 0
for i, license_data in enumerate(valid_license_results):
birth_date_fr = license_data.get('dateNaissanceFr')
calculated_age = calculate_age_from_birth_date(birth_date_fr)
match_detail = {
"index": i,
"dateNaissanceFr": birth_date_fr,
"calculated_age": calculated_age,
"player_age_sportif": player_age_sportif,
"age_difference": None,
"license": license_data.get('licence'),
"classement": license_data.get('classement', {})
}
if calculated_age is not None:
age_diff = abs(calculated_age - player_age_sportif)
match_detail["age_difference"] = age_diff
if age_diff < best_age_diff and best_age_diff > 1 and age_diff < 2:
best_age_diff = age_diff
best_match = license_data
best_match_count = 1
elif age_diff <= best_age_diff:
best_match_count += 1
match_details.append(match_detail)
# If no match found with valid age, use first valid result
if best_match is None:
match_info = {
"reason": "no_valid_ages_used_first_valid",
"original_count": len(license_results),
"filtered_count": len(valid_license_results),
"used_first_result": True,
"match_details": match_details
}
return valid_license_results[0], match_info
else:
if best_match_count == 1:
match_info = {
"reason": "age_matched",
"best_age_difference": best_age_diff,
"total_candidates": len(license_results),
"valid_candidates": len(valid_license_results),
"match_details": match_details
}
return best_match, match_info
else:
match_info = {
"reason": "multiple_matches",
"best_age_difference": best_age_diff,
"total_candidates": len(license_results),
"valid_candidates": len(valid_license_results),
"match_details": match_details
}
return None, match_info
@staff_member_required
def test_player_details_apis(request):
"""
Test different API endpoints to find player license information
"""
# Sample idCrm values from your data
test_ids = [5417299111, 9721526122]
results = {
"timestamp": datetime.now().isoformat(),
"test_results": []
}
for idCrm in test_ids:
player_results = {
"idCrm": idCrm,
"tests": []
}
# Test 1: Try player detail endpoint
try:
url = f"https://tenup.fft.fr/back/public/v1/joueurs/{idCrm}"
response = requests.get(url, timeout=10)
player_results["tests"].append({
"method": "GET",
"url": url,
"status_code": response.status_code,
"response_preview": response.text[:500] if response.status_code == 200 else response.text
})
except Exception as e:
player_results["tests"].append({
"method": "GET",
"url": url,
"error": str(e)
})
# Test 2: Try with different endpoint structure
try:
url = f"https://tenup.fft.fr/back/public/v1/players/{idCrm}"
response = requests.get(url, timeout=10)
player_results["tests"].append({
"method": "GET",
"url": url,
"status_code": response.status_code,
"response_preview": response.text[:500] if response.status_code == 200 else response.text
})
except Exception as e:
player_results["tests"].append({
"method": "GET",
"url": url,
"error": str(e)
})
# Test 3: Try POST with idCrm in body
try:
url = "https://tenup.fft.fr/back/public/v1/joueurs/detail"
payload = {"idCrm": idCrm}
response = requests.post(url, json=payload, headers={'Content-Type': 'application/json'}, timeout=10)
player_results["tests"].append({
"method": "POST",
"url": url,
"payload": payload,
"status_code": response.status_code,
"response_preview": response.text[:500] if response.status_code == 200 else response.text
})
except Exception as e:
player_results["tests"].append({
"method": "POST",
"url": url,
"payload": payload,
"error": str(e)
})
# Test 4: Try the classements endpoint with more parameters
try:
url = "https://tenup.fft.fr/back/public/v1/classements/recherche"
payload = {
"pratique": "padel",
"sexe": default_sexe,
"idCrm": idCrm
}
response = requests.post(url, json=payload, headers={'Content-Type': 'application/json'}, timeout=10)
player_results["tests"].append({
"method": "POST",
"url": url,
"payload": payload,
"status_code": response.status_code,
"response_preview": response.text[:500] if response.status_code == 200 else response.text
})
except Exception as e:
player_results["tests"].append({
"method": "POST",
"url": url,
"payload": payload,
"error": str(e)
})
results["test_results"].append(player_results)
time.sleep(0.5) # Small delay between tests
# Create filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"fft_api_tests_{timestamp}.json"
# Return results as downloadable JSON
http_response = HttpResponse(
json.dumps(results, indent=2, ensure_ascii=False),
content_type='application/json; charset=utf-8'
)
http_response['Content-Disposition'] = f'attachment; filename="{filename}"'
return http_response
@staff_member_required
def explore_fft_api_endpoints(request):
"""
Try to discover FFT API endpoints
"""
base_url = "https://tenup.fft.fr/back/public/v1"
# Common API endpoint patterns to try
endpoints_to_test = [
"/joueurs",
"/players",
"/licences",
"/licenses",
"/classements",
"/rankings",
"/pratiques",
"/sports",
"/search",
"/recherche",
"/tournaments",
"/tournois"
]
results = {
"base_url": base_url,
"timestamp": datetime.now().isoformat(),
"endpoint_tests": []
}
for endpoint in endpoints_to_test:
try:
url = base_url + endpoint
response = requests.get(url, timeout=10)
results["endpoint_tests"].append({
"endpoint": endpoint,
"url": url,
"status_code": response.status_code,
"content_type": response.headers.get('content-type', ''),
"response_preview": response.text[:300] if len(response.text) < 1000 else response.text[:300] + "..."
})
except Exception as e:
results["endpoint_tests"].append({
"endpoint": endpoint,
"url": url,
"error": str(e)
})
time.sleep(0.2) # Small delay
# Create filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"fft_api_discovery_{timestamp}.json"
# Return results as downloadable JSON
http_response = HttpResponse(
json.dumps(results, indent=2, ensure_ascii=False),
content_type='application/json; charset=utf-8'
)
http_response['Content-Disposition'] = f'attachment; filename="{filename}"'
return http_response
@staff_member_required
def download_french_padel_rankings(request):
"""
Download French padel rankings from FFT API and save locally for later enrichment
"""
if request.method == 'POST':
start_tranche = int(request.POST.get('start_tranche', 1001))
end_tranche = int(request.POST.get('end_tranche', 1222))
save_locally = request.POST.get('save_locally', 'true') == 'true'
try:
# API endpoint and parameters
url = "https://tenup.fft.fr/back/public/v1/classements/recherche"
headers = {
'Content-Type': 'application/json'
}
all_players = []
total_tranches = end_tranche - start_tranche + 1
successful_calls = 0
failed_calls = 0
print(f"Starting to fetch tranches {start_tranche} to {end_tranche} ({total_tranches} total)...")
for tranche in range(start_tranche, end_tranche + 1):
try:
payload = {
"pratique": "padel",
"sexe": default_sexe,
"tranche": tranche
}
response = requests.post(url, json=payload, headers=headers, timeout=30)
if response.status_code == 200:
json_data = response.json()
if 'joueurs' in json_data and json_data['joueurs']:
# Add metadata to each player for enrichment tracking
for player in json_data['joueurs']:
player['source_tranche'] = tranche
player['license_lookup_status'] = 'not_attempted'
player['license_data'] = None
all_players.extend(json_data['joueurs'])
successful_calls += 1
print(f"Tranche {tranche}: Found {len(json_data['joueurs'])} players (Total: {len(all_players)})")
else:
print(f"Tranche {tranche}: No players found")
else:
failed_calls += 1
print(f"Tranche {tranche}: HTTP {response.status_code}")
except requests.exceptions.RequestException as e:
failed_calls += 1
print(f"Tranche {tranche}: Network error - {str(e)}")
except Exception as e:
failed_calls += 1
print(f"Tranche {tranche}: Unexpected error - {str(e)}")
if tranche % 10 == 0:
time.sleep(0.1)
current_progress = tranche - start_tranche + 1
print(f"Progress: {current_progress}/{total_tranches} tranches processed...")
print(f"Completed! Total players found: {len(all_players)}")
print(f"Successful calls: {successful_calls}, Failed calls: {failed_calls}")
if all_players:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
final_data = {
"metadata": {
"total_players": len(all_players),
"successful_tranches": successful_calls,
"failed_tranches": failed_calls,
"total_tranches_requested": total_tranches,
"tranche_range": f"{start_tranche}-{end_tranche}",
"download_timestamp": datetime.now().isoformat(),
"last_enrichment_update": None,
"enrichment_progress": {
"players_with_licenses": 0,
"players_without_licenses": len(all_players),
"last_processed_index": -1
},
"parameters": {
"pratique": "padel",
"sexe": default_sexe,
"tranche_start": start_tranche,
"tranche_end": end_tranche
}
},
"joueurs": all_players
}
# Save locally if requested
if save_locally:
rankings_dir = os.path.join(settings.BASE_DIR, 'data', 'rankings')
os.makedirs(rankings_dir, exist_ok=True)
filename = f"french_padel_rankings_{start_tranche}-{end_tranche}_{timestamp}.json"
local_file_path = os.path.join(rankings_dir, filename)
with open(local_file_path, 'w', encoding='utf-8') as f:
json.dump(final_data, f, indent=2, ensure_ascii=False)
print(f"Rankings saved locally to: {local_file_path}")
messages.success(request, f"Rankings saved locally to: {local_file_path}")
# Create download response
download_filename = f"french_padel_rankings_{start_tranche}-{end_tranche}_{timestamp}.json"
http_response = HttpResponse(
json.dumps(final_data, indent=2, ensure_ascii=False),
content_type='application/json; charset=utf-8'
)
http_response['Content-Disposition'] = f'attachment; filename="{download_filename}"'
return http_response
else:
messages.error(request, f"No players found in tranches {start_tranche}-{end_tranche}.")
except Exception as e:
messages.error(request, f"Unexpected error: {str(e)}")
csrf_token = get_token(request)
html_template = Template('''
Download French Padel Rankings - Debug Tools
๐ Download French Padel Rankings
๐ New Workflow:
1. Use this tool to download and save rankings locally
2. Then use "Enrich Rankings with Licenses" to add license data
3. Process is resumable - you can stop and restart enrichment
๐ก How to use: You'll need a valid sessionId (from Cookie header) and idHomologation (competition ID) from the FFT Beach-Padel website. Use browser dev tools to get these values.
๐ฌ API Research Tools
Tools to help discover and explore the FFT API endpoints.
'''
return HttpResponse(html_content)
@staff_member_required
def get_player_license_info(request):
"""
Get player license information using sessionId and idHomologation
"""
if request.method == 'POST':
session_id = request.POST.get('sessionId', '').strip()
id_homologation = request.POST.get('idHomologation', '').strip()
license_id = request.POST.get('licenseId', '').strip()
if not session_id or not id_homologation or not license_id:
messages.error(request, "sessionId, idHomologation, and licenseId are all required.")
return redirect(request.path)
try:
# Construct the URL (equivalent to Swift's URL construction)
url = f"https://beach-padel.app.fft.fr/beachja/rechercheJoueur/licencies?idHomologation={id_homologation}&numeroLicence={license_id}"
# Set up headers (equivalent to Swift's URLRequest headers)
headers = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"Sec-Fetch-Site": "same-origin",
"Accept-Language": "fr-FR,fr;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Fetch-Mode": "cors",
"Host": "beach-padel.app.fft.fr",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15",
"Connection": "keep-alive",
"Referer": f"https://beach-padel.app.fft.fr/beachja/competitionFiche/inscrireEquipe?identifiantHomologation={id_homologation}",
"X-Requested-With": "XMLHttpRequest",
"Cookie": session_id
}
print(f"Making request to: {url}")
print(f"Headers: {headers}")
# Make the GET request (equivalent to Swift's URLSession.shared.data)
response = requests.get(url, headers=headers, timeout=30)
print(f"Response status: {response.status_code}")
print(f"Raw JSON response: {response.text}")
if response.status_code == 200:
try:
json_data = response.json()
# Create result structure
result = {
"request_info": {
"url": url,
"license_id": license_id,
"id_homologation": id_homologation,
"timestamp": datetime.now().isoformat()
},
"response_info": {
"status_code": response.status_code,
"headers": dict(response.headers),
"raw_response": response.text
},
"parsed_data": json_data
}
# Create filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"player_license_info_{license_id}_{timestamp}.json"
# Return as downloadable JSON
http_response = HttpResponse(
json.dumps(result, indent=2, ensure_ascii=False),
content_type='application/json; charset=utf-8'
)
http_response['Content-Disposition'] = f'attachment; filename="{filename}"'
return http_response
except json.JSONDecodeError as e:
messages.error(request, f"Failed to parse JSON response: {str(e)}")
else:
messages.error(request, f"Request failed with status {response.status_code}: {response.text}")
except requests.exceptions.RequestException as e:
messages.error(request, f"Network error: {str(e)}")
except Exception as e:
messages.error(request, f"Unexpected error: {str(e)}")
csrf_token = get_token(request)
# Default values
default_license_id = "5186803"
html_template = Template('''
Player License Lookup - Debug Tools
๐ Player License Lookup
โ Default values loaded!
The form is pre-filled with your provided sessionId, idHomologation, and license ID.
You can modify any field if needed before submitting.
''')
context = Context({
'csrf_token': csrf_token,
'default_session_id': default_session_id,
'default_id_homologation': default_id_homologation,
'default_license_ids': default_license_ids
})
return HttpResponse(html_template.render(context))
@staff_member_required
def search_player_by_name(request):
"""
Search for players by nom and prenom
"""
if request.method == 'POST':
session_id = request.POST.get('sessionId', '').strip()
id_homologation = request.POST.get('idHomologation', '').strip()
nom = request.POST.get('nom', '')
prenom = request.POST.get('prenom', '')
if not session_id or not id_homologation:
messages.error(request, "sessionId and idHomologation are required.")
return redirect(request.path)
if not nom and not prenom:
messages.error(request, "At least nom or prenom is required.")
return redirect(request.path)
try:
# Construct the URL for name search
base_url = "https://beach-padel.app.fft.fr/beachja/rechercheJoueur/licencies"
params = {
"idHomologation": id_homologation
}
# Add name parameters if provided
if nom:
params["nom"] = nom
if prenom:
params["prenom"] = prenom
# Build query string
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
url = f"{base_url}?{query_string}"
# Set up headers
headers = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"Sec-Fetch-Site": "same-origin",
"Accept-Language": "fr-FR,fr;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Fetch-Mode": "cors",
"Host": "beach-padel.app.fft.fr",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15",
"Connection": "keep-alive",
"Referer": f"https://beach-padel.app.fft.fr/beachja/competitionFiche/inscrireEquipe?identifiantHomologation={id_homologation}",
"X-Requested-With": "XMLHttpRequest",
"Cookie": session_id
}
print(f"Making request to: {url}")
print(f"Headers: {headers}")
# Make the GET request
response = requests.get(url, headers=headers, timeout=30)
print(f"Response status: {response.status_code}")
print(f"Raw JSON response: {response.text}")
if response.status_code == 200:
try:
json_data = response.json()
# Create result structure
result = {
"search_info": {
"url": url,
"nom": nom,
"prenom": prenom,
"id_homologation": id_homologation,
"timestamp": datetime.now().isoformat()
},
"response_info": {
"status_code": response.status_code,
"headers": dict(response.headers),
"raw_response": response.text
},
"parsed_data": json_data
}
# Create filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
search_term = f"{nom}_{prenom}".replace(" ", "_")
filename = f"player_search_{search_term}_{timestamp}.json"
# Return as downloadable JSON
http_response = HttpResponse(
json.dumps(result, indent=2, ensure_ascii=False),
content_type='application/json; charset=utf-8'
)
http_response['Content-Disposition'] = f'attachment; filename="{filename}"'
return http_response
except json.JSONDecodeError as e:
messages.error(request, f"Failed to parse JSON response: {str(e)}")
else:
messages.error(request, f"Request failed with status {response.status_code}: {response.text}")
except requests.exceptions.RequestException as e:
messages.error(request, f"Network error: {str(e)}")
except Exception as e:
messages.error(request, f"Unexpected error: {str(e)}")
csrf_token = get_token(request)
# Default values
html_template = Template('''
Search Player by Name - Debug Tools
๐ Search Player by Name
โ Default values loaded!
ID Homologation is pre-filled. Add your session ID and enter the player's name to search.
โน๏ธ How it works:
โข You can search by nom (last name), prenom (first name), or both
โข Partial names should work (e.g., "DUPON" might find "DUPONT")
โข Leave one field empty to search by the other
โข Results will include license numbers and detailed player information
๐ Example Searches
Search by last name only: nom = "MARTIN", prenom = empty
Search by first name only: nom = empty, prenom = "Jean"
Search by both: nom = "DUPONT", prenom = "Pierre"
Partial search: nom = "DUPON" (might find "DUPONT", "DUPONTEL", etc.)