Files
rogaining_srv/rog/views_apis/api_qr_points.py
2025-09-04 19:25:14 +09:00

355 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
QRコードサービスポイント処理API
このモジュールは、QRコードを使用したサービスポイントの登録・処理を行います。
"""
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from rog.models import NewEvent2, Entry, Location2025, GpsCheckin
from django.db import transaction
from django.utils import timezone
from datetime import datetime
import logging
import json
import uuid
logger = logging.getLogger(__name__)
@api_view(['POST'])
def submit_qr_points(request):
"""
QRコードサービスポイント登録API
パラメータ:
- event_code: イベントコード
- team_name: チーム名
- qr_code_data: QRコードデータ
- latitude: 緯度(オプション)
- longitude: 経度(オプション)
- image: 画像データ(オプション)
- cp_number: チェックポイント番号(オプション)
"""
# リクエストIDを生成してログで追跡できるようにする
request_id = str(uuid.uuid4())[:8]
logger.info(f"[QR_SUBMIT] 🚀 Starting QR points submission - ID: {request_id}")
# クライアント情報を取得
user_agent = request.META.get('HTTP_USER_AGENT', 'Unknown')
client_ip = request.META.get('HTTP_X_FORWARDED_FOR') or request.META.get('REMOTE_ADDR', 'Unknown')
user_info = str(request.user) if request.user.is_authenticated else 'Anonymous'
# リクエストからパラメータを取得
event_code = request.data.get('event_code')
team_name = request.data.get('team_name')
qr_code_data = request.data.get('qr_code_data')
latitude = request.data.get('latitude')
longitude = request.data.get('longitude')
image_data = request.data.get('image')
cp_number = request.data.get('cp_number')
point_value = request.data.get('point_value')
location_id = request.data.get('location_id')
timestamp = request.data.get('timestamp')
qr_scan = request.data.get('qr_scan')
# 📋 パラメータをログ出力(デバッグ用)
logger.info(f"[QR_SUBMIT] ID: {request_id} - 📋 Request Parameters:")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 🏷️ Event Code: '{event_code}'")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 👥 Team Name: '{team_name}'")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 📱 QR Code Data: '{qr_code_data}'")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 📍 Latitude: {latitude}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 🌍 Longitude: {longitude}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 🏁 CP Number: {cp_number}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - <20> Point Value: {point_value}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 📍 Location ID: {location_id}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - ⏰ Timestamp: {timestamp}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 📱 QR Scan: {qr_scan}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - <20>📸 Has Image: {image_data is not None}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 🌐 Client IP: {client_ip}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 👤 User: {user_info}")
logger.info(f"[QR_SUBMIT] ID: {request_id} - 🔧 User Agent: {user_agent[:100]}...")
# 全リクエストデータをログ出力(セキュリティ上重要でないデータのみ)
safe_data = {k: v for k, v in request.data.items() if k not in ['image', 'password']}
logger.info(f"[QR_SUBMIT] ID: {request_id} - 📊 Full Request Data: {json.dumps(safe_data, ensure_ascii=False, indent=2)}")
cp_number = request.data.get('cp_number')
# 📊 詳細なパラメータログ
logger.info(f"[QR_SUBMIT] 📊 DETAILED PARAMS - ID: {request_id}")
logger.info(f"[QR_SUBMIT] 🏷️ Basic params: event_code='{event_code}', team_name='{team_name}'")
logger.info(f"[QR_SUBMIT] 📱 QR params: qr_code_data='{qr_code_data}', cp_number={cp_number}")
logger.info(f"[QR_SUBMIT] 🌍 Location params: lat={latitude}, lng={longitude}")
logger.info(f"[QR_SUBMIT] 🖼️ Image params: has_image={bool(image_data)}, image_size={len(str(image_data)) if image_data else 0}")
logger.info(f"[QR_SUBMIT] 📱 Client params: user_agent='{user_agent[:100]}...', client_ip='{client_ip}'")
logger.info(f"[QR_SUBMIT] 🔐 Auth params: user_authenticated={request.user.is_authenticated}, user='{user_info}'")
# 全リクエストデータをダンプ(デバッグ用)
try:
request_data_safe = {}
for key, value in request.data.items():
if key == 'image' and value:
request_data_safe[key] = f"[IMAGE_DATA:{len(str(value))}chars]"
else:
request_data_safe[key] = value
logger.info(f"[QR_SUBMIT] 📥 FULL REQUEST DATA: {json.dumps(request_data_safe, ensure_ascii=False, indent=2)}")
except Exception as e:
logger.warning(f"[QR_SUBMIT] Failed to log request data: {e}")
# パラメータ検証 - 実際のリクエストデータに基づく
# 基本的な必須パラメータ: event_code, team_name
# オプション: qr_code_data, cp_number, point_value, location_id, qr_scan
if not all([event_code, team_name]):
missing_params = []
if not event_code:
missing_params.append('event_code')
if not team_name:
missing_params.append('team_name')
logger.warning(f"[QR_SUBMIT] ❌ Missing required parameters: {missing_params} - ID: {request_id}")
return Response({
"status": "ERROR",
"message": f"必須パラメータが不足しています: {', '.join(missing_params)}",
"missing_parameters": missing_params,
"request_id": request_id
}, status=status.HTTP_400_BAD_REQUEST)
try:
with transaction.atomic():
# イベントの存在確認
event = NewEvent2.objects.filter(event_name=event_code).first()
if not event:
logger.warning(f"[QR_SUBMIT] ❌ Event not found: {event_code} - ID: {request_id}")
return Response({
"status": "ERROR",
"message": f"指定されたイベント '{event_code}' が見つかりません",
"request_id": request_id
}, status=status.HTTP_404_NOT_FOUND)
# チームの存在確認
entry = Entry.objects.filter(
event=event,
team__team_name=team_name
).first()
if not entry:
logger.warning(f"[QR_SUBMIT] ❌ Team not found: {team_name} in event: {event_code} - ID: {request_id}")
return Response({
"status": "ERROR",
"message": f"指定されたチーム '{team_name}' がイベント '{event_code}' に見つかりません",
"request_id": request_id
}, status=status.HTTP_404_NOT_FOUND)
# QRコードデータの解析オプション
qr_data = None
if qr_code_data:
try:
if isinstance(qr_code_data, str):
# JSON文字列の場合はパース
if qr_code_data.startswith('{'):
qr_data = json.loads(qr_code_data)
else:
# 単純な文字列の場合
qr_data = {"code": qr_code_data}
else:
qr_data = qr_code_data
logger.info(f"[QR_SUBMIT] 📱 Parsed QR data: {qr_data} - ID: {request_id}")
except json.JSONDecodeError as e:
logger.warning(f"[QR_SUBMIT] ⚠️ Invalid QR code data format: {e}, using as string - ID: {request_id}")
qr_data = {"code": str(qr_code_data)}
# チェックポイント情報の取得
location = None
calculated_point_value = 0
# location_idが指定されている場合、それを優先
if location_id:
location = Location2025.objects.filter(
id=location_id,
event_id=event.id
).first()
if location:
calculated_point_value = location.cp_point or 0
logger.info(f"[QR_SUBMIT] 📍 Found location by ID: {location_id} - CP{location.cp_number} - {location.cp_name} - Points: {calculated_point_value} - ID: {request_id}")
else:
logger.warning(f"[QR_SUBMIT] ⚠️ Location not found for location_id: {location_id} - ID: {request_id}")
# cp_numberが指定されている場合も確認
elif cp_number:
location = Location2025.objects.filter(
event_id=event.id,
cp_number=cp_number
).first()
if location:
calculated_point_value = location.cp_point or 0
logger.info(f"[QR_SUBMIT] 📍 Found location by CP: CP{cp_number} - {location.cp_name} - Points: {calculated_point_value} - ID: {request_id}")
else:
logger.warning(f"[QR_SUBMIT] ⚠️ Location not found for CP{cp_number} - ID: {request_id}")
# point_valueが明示的に指定されている場合、それを使用
final_point_value = point_value if point_value is not None else calculated_point_value
logger.info(f"[QR_SUBMIT] 💯 Point calculation: provided={point_value}, calculated={calculated_point_value}, final={final_point_value} - ID: {request_id}")
# QRポイント登録処理
current_time = timezone.now()
# timestampが提供されている場合は使用、そうでなければ現在時刻
if timestamp:
try:
# ISO形式のタイムスタンプをパース
checkin_time = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
if checkin_time.tzinfo is None:
checkin_time = timezone.make_aware(checkin_time)
logger.info(f"[QR_SUBMIT] ⏰ Using provided timestamp: {checkin_time} - ID: {request_id}")
except (ValueError, AttributeError) as e:
logger.warning(f"[QR_SUBMIT] ⚠️ Invalid timestamp format, using current time: {e} - ID: {request_id}")
checkin_time = current_time
else:
checkin_time = current_time
# GpsCheckinレコードを作成
checkin_data = {
'event': event,
'entry': entry,
'zekken_number': entry.zekken_number,
'cp_number': cp_number or (location.cp_number if location else 0),
'checkin_time': checkin_time,
'is_service_checked': True, # QRコードはサービスポイントとして扱う
}
# 位置情報が提供されている場合は追加
if latitude and longitude:
checkin_data['latitude'] = float(latitude)
checkin_data['longitude'] = float(longitude)
logger.info(f"[QR_SUBMIT] 🌍 GPS coordinates recorded: {latitude}, {longitude} - ID: {request_id}")
# QRコードデータを格納qr_scanフラグも含む
qr_metadata = {
'qr_scan': qr_scan,
'location_id': location_id,
'point_value': final_point_value,
'original_qr_data': qr_data,
'timestamp': timestamp
}
if hasattr(GpsCheckin, 'qr_code_data'):
checkin_data['qr_code_data'] = qr_metadata
# 画像データを格納URLまたはパスの場合
if image_data:
if hasattr(GpsCheckin, 'image_url'):
checkin_data['image_url'] = image_data
logger.info(f"[QR_SUBMIT] 🖼️ Image data recorded - ID: {request_id}")
# レコードを作成
gps_checkin = GpsCheckin.objects.create(**checkin_data)
# 成功レスポンス
response_data = {
"status": "OK",
"message": "QRコードサービスポイントが正常に登録されました",
"request_id": request_id,
"data": {
"event_code": event_code,
"team_name": team_name,
"zekken_number": entry.zekken_number,
"cp_number": cp_number or (location.cp_number if location else 0),
"point_value": final_point_value,
"location_id": location_id,
"checkin_time": checkin_time.isoformat(),
"qr_scan": qr_scan,
"qr_code_processed": True,
"has_location": bool(latitude and longitude),
"has_image": bool(image_data),
"checkin_id": gps_checkin.id if gps_checkin else None
}
}
logger.info(f"[QR_SUBMIT] ✅ SUCCESS - Team: {team_name}, Zekken: {entry.zekken_number}, CP: {cp_number or (location.cp_number if location else 0)}, Points: {final_point_value}, QR: {qr_scan}, Location ID: {location_id}, Client IP: {client_ip}, User: {user_info} - ID: {request_id}")
return Response(response_data, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"[QR_SUBMIT] ❌ EXCEPTION - ID: {request_id}, Error: {str(e)}", exc_info=True)
return Response({
"status": "ERROR",
"message": "QRコードサービスポイント処理中にエラーが発生しました",
"request_id": request_id,
"error_detail": str(e) if logger.getEffectiveLevel() <= logging.DEBUG else None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['GET'])
def qr_points_status(request):
"""
QRポイント処理状況確認API
パラメータ:
- event_code: イベントコード
- team_name: チーム名(オプション)
"""
event_code = request.query_params.get('event_code')
team_name = request.query_params.get('team_name')
logger.info(f"[QR_STATUS] QR points status check - event: {event_code}, team: {team_name}")
if not event_code:
return Response({
"status": "ERROR",
"message": "イベントコードが必要です"
}, status=status.HTTP_400_BAD_REQUEST)
try:
# イベントの存在確認
event = NewEvent2.objects.filter(event_name=event_code).first()
if not event:
return Response({
"status": "ERROR",
"message": f"指定されたイベント '{event_code}' が見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
# QRコード関連のチェックイン状況を取得
query = GpsCheckin.objects.filter(event=event, is_service_checked=True)
if team_name:
query = query.filter(entry__team__team_name=team_name)
qr_checkins = query.order_by('-checkin_time')[:50] # 最新50件
checkin_list = []
for checkin in qr_checkins:
checkin_list.append({
"checkin_id": checkin.id,
"team_name": checkin.entry.team.team_name,
"zekken_number": checkin.zekken_number,
"cp_number": checkin.cp_number,
"checkin_time": checkin.checkin_time.isoformat(),
"has_qr_data": hasattr(checkin, 'qr_code_data') and bool(checkin.qr_code_data),
"has_location": bool(checkin.latitude and checkin.longitude),
"is_service_checked": checkin.is_service_checked
})
return Response({
"status": "OK",
"message": "QRポイント状況を取得しました",
"data": {
"event_code": event_code,
"total_count": len(checkin_list),
"checkins": checkin_list
}
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"[QR_STATUS] Error getting QR points status: {str(e)}", exc_info=True)
return Response({
"status": "ERROR",
"message": "QRポイント状況取得中にエラーが発生しました",
"error_detail": str(e) if logger.getEffectiveLevel() <= logging.DEBUG else None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)