From 7abdfbe903755e089d89f457663bc120ee1be791 Mon Sep 17 00:00:00 2001 From: Akira Date: Thu, 4 Sep 2025 10:10:24 +0900 Subject: [PATCH] add submit_qr_points --- rog/urls.py | 5 + rog/views_apis/api_play.py | 21 +++ rog/views_apis/api_qr_points.py | 280 ++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 rog/views_apis/api_qr_points.py diff --git a/rog/urls.py b/rog/urls.py index 01ddda7..f24127b 100755 --- a/rog/urls.py +++ b/rog/urls.py @@ -17,6 +17,7 @@ from .views_apis.api_ranking import get_ranking,all_ranking_top3 from .views_apis.api_photos import get_photo_list, get_photo_list_prod, get_team_photos from .views_apis.s3_views import upload_checkin_image, upload_standard_image, get_standard_image, list_event_images, delete_image from .views_apis.api_scoreboard import get_scoreboard,download_scoreboard,reprint,make_all_scoreboard,make_cp_list_sheet +from .views_apis.api_qr_points import submit_qr_points, qr_points_status from .views_apis.api_bulk_upload import bulk_upload_photos, confirm_checkin_validation from .views_apis.api_admin_validation import get_event_participants_ranking, get_participant_validation_details, get_event_zekken_list from .views_apis.api_simulator import rogaining_simulator @@ -271,6 +272,10 @@ urlpatterns += [ path('api/routes/gpx-test-data/', gpx_test_data, name='gpx_test_data'), path('api/routes/available/', available_routes, name='available_routes'), + # QR Points API + path('api/submit_qr_points', submit_qr_points, name='submit_qr_points'), + path('api/qr_points_status', qr_points_status, name='qr_points_status'), + ] if settings.DEBUG: diff --git a/rog/views_apis/api_play.py b/rog/views_apis/api_play.py index 9225552..84c74d7 100755 --- a/rog/views_apis/api_play.py +++ b/rog/views_apis/api_play.py @@ -488,6 +488,27 @@ def checkin_from_rogapp(request): gps_coordinates = request.data.get('gps_coordinates', {}) camera_metadata = request.data.get('camera_metadata', {}) + # 🔍 詳細なパラメータログ(QRコード問題調査用) + logger.info(f"[CHECKIN] 🔍 DETAILED PARAMS - ID: {request_id}") + logger.info(f"[CHECKIN] 📊 Basic params: event_code='{event_code}', team_name='{team_name}', cp_number={cp_number}") + logger.info(f"[CHECKIN] 🖼️ Image params: has_image={bool(image_url)}, image_size={len(image_url) if image_url else 0}, image_type={type(image_url)}") + logger.info(f"[CHECKIN] 🛒 Purchase params: buy_flag={buy_flag} (type: {type(buy_flag)})") + logger.info(f"[CHECKIN] 📱 Client params: user_agent='{user_agent[:100]}...', client_ip='{client_ip}'") + logger.info(f"[CHECKIN] 🔐 Auth params: user_authenticated={request.user.is_authenticated}, user='{user_info}'") + + # 全リクエストデータをダンプ(QRコード問題調査用) + try: + import json + 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"[CHECKIN] 📥 FULL REQUEST DATA: {json.dumps(request_data_safe, ensure_ascii=False, indent=2)}") + except Exception as e: + logger.warning(f"[CHECKIN] Failed to log request data: {e}") + logger.info(f"[CHECKIN] Request parameters - ID: {request_id}, event_code: '{event_code}', team_name: '{team_name}', cp_number: {cp_number}, has_image: {bool(image_url)}, image_size: {len(image_url) if image_url else 0} chars, buy_flag: {buy_flag}, user_agent: '{user_agent[:100]}'") # GPS座標情報をログに記録 diff --git a/rog/views_apis/api_qr_points.py b/rog/views_apis/api_qr_points.py new file mode 100644 index 0000000..c640167 --- /dev/null +++ b/rog/views_apis/api_qr_points.py @@ -0,0 +1,280 @@ +""" +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') + + # 📊 詳細なパラメータログ + 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}") + + # パラメータ検証 + if not all([event_code, team_name, qr_code_data]): + logger.warning(f"[QR_SUBMIT] ❌ Missing required parameters - ID: {request_id}") + return Response({ + "status": "ERROR", + "message": "イベントコード、チーム名、QRコードデータが必要です", + "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コードデータの解析 + 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} - ID: {request_id}") + return Response({ + "status": "ERROR", + "message": "QRコードデータの形式が正しくありません", + "request_id": request_id + }, status=status.HTTP_400_BAD_REQUEST) + + # チェックポイント情報の取得(cp_numberが指定されている場合) + location = None + if cp_number: + location = Location2025.objects.filter( + event_id=event.id, + cp_number=cp_number + ).first() + + if location: + logger.info(f"[QR_SUBMIT] 📍 Found location: CP{cp_number} - {location.cp_name} - ID: {request_id}") + else: + logger.warning(f"[QR_SUBMIT] ⚠️ Location not found for CP{cp_number} - ID: {request_id}") + + # QRポイント登録処理 + current_time = timezone.now() + + # GpsCheckinレコードを作成(QRコード情報を含む) + checkin_data = { + 'event': event, + 'entry': entry, + 'zekken_number': entry.zekken_number, + 'cp_number': cp_number or 0, # cp_numberが指定されていない場合は0 + 'checkin_time': current_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コードデータを格納(JSONフィールドがある場合) + if hasattr(GpsCheckin, 'qr_code_data'): + checkin_data['qr_code_data'] = qr_data + + # 画像データを格納(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) + + # ポイント計算 + point_value = 0 + if location: + point_value = location.cp_point or 0 + + # 成功レスポンス + 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, + "point_value": point_value, + "checkin_time": current_time.isoformat(), + "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}, Points: {point_value}, QR: {bool(qr_code_data)}, 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)