update APIs
This commit is contained in:
307
rog/views_apis/api_competition_status.py
Normal file
307
rog/views_apis/api_competition_status.py
Normal file
@ -0,0 +1,307 @@
|
||||
"""
|
||||
競技状態管理API
|
||||
サーバーAPI変更要求書20250904.mdに基づく実装
|
||||
|
||||
Author: システム開発チーム
|
||||
Date: 2025-09-04
|
||||
"""
|
||||
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from rog.models import NewEvent2, Entry, Location2025, GpsLog
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.contrib.gis.measure import D
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
def competition_status(request):
|
||||
"""
|
||||
競技状態取得API
|
||||
|
||||
パラメータ:
|
||||
- event_code: イベントコード
|
||||
- zekken_number: ゼッケン番号
|
||||
|
||||
レスポンス:
|
||||
{
|
||||
"status": "OK",
|
||||
"data": {
|
||||
"is_in_rog": true,
|
||||
"rogaining_counted": false,
|
||||
"ready_for_goal": false,
|
||||
"is_at_goal": false,
|
||||
"start_time": "2025-09-04T09:00:00+09:00",
|
||||
"goal_time": null,
|
||||
"last_checkin_time": "2025-09-04T09:30:00+09:00"
|
||||
}
|
||||
}
|
||||
"""
|
||||
logger.info("competition_status API called")
|
||||
|
||||
event_code = request.query_params.get('event_code')
|
||||
zekken_number = request.query_params.get('zekken_number')
|
||||
|
||||
logger.debug(f"Parameters: event_code={event_code}, zekken_number={zekken_number}")
|
||||
|
||||
# パラメータ検証
|
||||
if not event_code or not zekken_number:
|
||||
logger.warning("Missing required parameters")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "イベントコードとゼッケン番号が必要です"
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# イベントの存在確認
|
||||
event = NewEvent2.objects.filter(event_name=event_code).first()
|
||||
if not event:
|
||||
logger.warning(f"Event not found: {event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたイベントが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# エントリーの存在確認
|
||||
entry = Entry.objects.filter(
|
||||
event=event,
|
||||
zekken_number=zekken_number
|
||||
).first()
|
||||
|
||||
if not entry:
|
||||
logger.warning(f"Entry not found: zekken_number={zekken_number}, event={event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたゼッケン番号のエントリーが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 競技状態データを返す
|
||||
data = {
|
||||
"is_in_rog": entry.is_in_rog,
|
||||
"rogaining_counted": entry.rogaining_counted,
|
||||
"ready_for_goal": entry.ready_for_goal,
|
||||
"is_at_goal": entry.is_at_goal,
|
||||
"start_time": entry.start_time.strftime("%Y-%m-%dT%H:%M:%S%z") if entry.start_time else None,
|
||||
"goal_time": entry.goal_time.strftime("%Y-%m-%dT%H:%M:%S%z") if entry.goal_time else None,
|
||||
"last_checkin_time": entry.last_checkin_time.strftime("%Y-%m-%dT%H:%M:%S%z") if entry.last_checkin_time else None
|
||||
}
|
||||
|
||||
logger.info(f"Competition status retrieved for zekken {zekken_number} in event {event_code}")
|
||||
|
||||
return Response({
|
||||
"status": "OK",
|
||||
"data": data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in competition_status: {str(e)}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "サーバーエラーが発生しました"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
def update_competition_status(request):
|
||||
"""
|
||||
競技状態更新API
|
||||
|
||||
パラメータ:
|
||||
{
|
||||
"event_code": "EVENT2025",
|
||||
"zekken_number": "001",
|
||||
"is_in_rog": true,
|
||||
"rogaining_counted": false,
|
||||
"ready_for_goal": false,
|
||||
"is_at_goal": false
|
||||
}
|
||||
|
||||
レスポンス:
|
||||
{
|
||||
"status": "OK",
|
||||
"message": "Competition status updated successfully",
|
||||
"updated_at": "2025-09-04T10:00:00+09:00"
|
||||
}
|
||||
"""
|
||||
logger.info("update_competition_status API called")
|
||||
|
||||
event_code = request.data.get('event_code')
|
||||
zekken_number = request.data.get('zekken_number')
|
||||
is_in_rog = request.data.get('is_in_rog')
|
||||
rogaining_counted = request.data.get('rogaining_counted')
|
||||
ready_for_goal = request.data.get('ready_for_goal')
|
||||
is_at_goal = request.data.get('is_at_goal')
|
||||
|
||||
logger.debug(f"Parameters: event_code={event_code}, zekken_number={zekken_number}")
|
||||
|
||||
# パラメータ検証
|
||||
if not event_code or not zekken_number:
|
||||
logger.warning("Missing required parameters")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "イベントコードとゼッケン番号が必要です"
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# イベントの存在確認
|
||||
event = NewEvent2.objects.filter(event_name=event_code).first()
|
||||
if not event:
|
||||
logger.warning(f"Event not found: {event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたイベントが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# エントリーの存在確認
|
||||
entry = Entry.objects.filter(
|
||||
event=event,
|
||||
zekken_number=zekken_number
|
||||
).first()
|
||||
|
||||
if not entry:
|
||||
logger.warning(f"Entry not found: zekken_number={zekken_number}, event={event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたゼッケン番号のエントリーが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# トランザクション内で状態更新
|
||||
with transaction.atomic():
|
||||
# 状態の更新(Noneでない値のみ更新)
|
||||
if is_in_rog is not None:
|
||||
entry.is_in_rog = is_in_rog
|
||||
if rogaining_counted is not None:
|
||||
entry.rogaining_counted = rogaining_counted
|
||||
if ready_for_goal is not None:
|
||||
entry.ready_for_goal = ready_for_goal
|
||||
if is_at_goal is not None:
|
||||
entry.is_at_goal = is_at_goal
|
||||
|
||||
entry.save()
|
||||
|
||||
update_time = timezone.now()
|
||||
|
||||
logger.info(f"Competition status updated for zekken {zekken_number} in event {event_code}")
|
||||
|
||||
return Response({
|
||||
"status": "OK",
|
||||
"message": "Competition status updated successfully",
|
||||
"updated_at": update_time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in update_competition_status: {str(e)}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "サーバーエラーが発生しました"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
def checkpoint_status(request):
|
||||
"""
|
||||
チェックポイント状態取得API
|
||||
|
||||
パラメータ:
|
||||
- event_code: イベントコード
|
||||
- zekken_number: ゼッケン番号
|
||||
- cp_number: チェックポイント番号
|
||||
|
||||
レスポンス:
|
||||
{
|
||||
"status": "OK",
|
||||
"data": {
|
||||
"cp_number": -2,
|
||||
"is_checked_in": true,
|
||||
"checkin_time": "2025-09-04T09:00:00+09:00",
|
||||
"status": "競技中", // "未", "競技中", "競技終了"
|
||||
"points_earned": 0
|
||||
}
|
||||
}
|
||||
"""
|
||||
logger.info("checkpoint_status API called")
|
||||
|
||||
event_code = request.query_params.get('event_code')
|
||||
zekken_number = request.query_params.get('zekken_number')
|
||||
cp_number = request.query_params.get('cp_number')
|
||||
|
||||
logger.debug(f"Parameters: event_code={event_code}, zekken_number={zekken_number}, cp_number={cp_number}")
|
||||
|
||||
# パラメータ検証
|
||||
if not all([event_code, zekken_number, cp_number]):
|
||||
logger.warning("Missing required parameters")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "イベントコード、ゼッケン番号、チェックポイント番号が必要です"
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# イベントの存在確認
|
||||
event = NewEvent2.objects.filter(event_name=event_code).first()
|
||||
if not event:
|
||||
logger.warning(f"Event not found: {event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたイベントが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# エントリーの存在確認
|
||||
entry = Entry.objects.filter(
|
||||
event=event,
|
||||
zekken_number=zekken_number
|
||||
).first()
|
||||
|
||||
if not entry:
|
||||
logger.warning(f"Entry not found: zekken_number={zekken_number}, event={event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたゼッケン番号のエントリーが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# チェックイン状況の確認
|
||||
checkin_record = GpsLog.objects.filter(
|
||||
zekken_number=zekken_number,
|
||||
event_code=event_code,
|
||||
cp_number=cp_number
|
||||
).first()
|
||||
|
||||
# チェックポイント情報の取得
|
||||
checkpoint = Location2025.objects.filter(
|
||||
event=event,
|
||||
cp_number=cp_number
|
||||
).first()
|
||||
|
||||
# 競技状況の判定
|
||||
competition_status = "未"
|
||||
if entry.is_at_goal:
|
||||
competition_status = "競技終了"
|
||||
elif entry.is_in_rog:
|
||||
competition_status = "競技中"
|
||||
|
||||
# レスポンスデータ構築
|
||||
data = {
|
||||
"cp_number": int(cp_number),
|
||||
"is_checked_in": bool(checkin_record),
|
||||
"checkin_time": checkin_record.checkin_time.strftime("%Y-%m-%dT%H:%M:%S%z") if checkin_record else None,
|
||||
"status": competition_status,
|
||||
"points_earned": checkpoint.point if checkpoint else 0
|
||||
}
|
||||
|
||||
logger.info(f"Checkpoint status retrieved for CP {cp_number}, zekken {zekken_number} in event {event_code}")
|
||||
|
||||
return Response({
|
||||
"status": "OK",
|
||||
"data": data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in checkpoint_status: {str(e)}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "サーバーエラーが発生しました"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
@ -5,7 +5,7 @@ from django.utils import timezone
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rog.models import Location2025, NewEvent2, Entry, GpsLog
|
||||
from rog.models import Location2025, NewEvent2, Entry, GpsLog, GpsCheckin
|
||||
import logging
|
||||
import uuid
|
||||
import os
|
||||
@ -1089,18 +1089,19 @@ def get_checkin_list(request):
|
||||
logger.info("get_checkin_list called")
|
||||
|
||||
# リクエストからパラメータを取得(GET/POSTの両方に対応)
|
||||
# 両方のパラメータ名に対応
|
||||
if request.method == 'GET':
|
||||
zekken_number = request.GET.get('zekken')
|
||||
event_code = request.GET.get('event')
|
||||
zekken_number = request.GET.get('zekken_number') or request.GET.get('zekken')
|
||||
event_code = request.GET.get('event_code') or request.GET.get('event')
|
||||
else: # POST
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
zekken_number = data.get('zekken')
|
||||
event_code = data.get('event')
|
||||
zekken_number = data.get('zekken_number') or data.get('zekken')
|
||||
event_code = data.get('event_code') or data.get('event')
|
||||
except:
|
||||
data = request.POST
|
||||
zekken_number = data.get('zekken')
|
||||
event_code = data.get('event')
|
||||
zekken_number = data.get('zekken_number') or data.get('zekken')
|
||||
event_code = data.get('event_code') or data.get('event')
|
||||
|
||||
logger.info(f"[GET_CHECKIN_LIST] Request method: {request.method}")
|
||||
logger.info(f"[GET_CHECKIN_LIST] Parameters received - zekken: {zekken_number}, event: {event_code}")
|
||||
@ -1134,24 +1135,83 @@ def get_checkin_list(request):
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# チームの存在確認
|
||||
logger.info(f"[GET_CHECKIN_LIST] Searching for event: {event.event_name} (ID: {event.id})")
|
||||
logger.info(f"[GET_CHECKIN_LIST] Searching for zekken_number: {zekken_number}")
|
||||
|
||||
# まず、このイベントのすべてのEntryを確認
|
||||
all_entries = Entry.objects.filter(event=event)
|
||||
logger.info(f"[GET_CHECKIN_LIST] Total entries in event: {all_entries.count()}")
|
||||
|
||||
# ゼッケン番号でのチーム検索(複数の方法で試す)
|
||||
entry = Entry.objects.filter(
|
||||
event=event,
|
||||
team__zekken_number=zekken_number
|
||||
zekken_number=zekken_number
|
||||
).first()
|
||||
|
||||
if not entry:
|
||||
# team__zekken_numberでも試してみる
|
||||
entry = Entry.objects.filter(
|
||||
event=event,
|
||||
team__zekken_number=zekken_number
|
||||
).first()
|
||||
|
||||
if not entry:
|
||||
logger.warning(f"Team with zekken number {zekken_number} not found in event: {event_code}")
|
||||
# デバッグ用:存在するゼッケン番号を表示
|
||||
existing_zekkens = list(Entry.objects.filter(event=event).values_list('zekken_number', flat=True))
|
||||
logger.info(f"[GET_CHECKIN_LIST] Existing zekken numbers: {existing_zekkens[:10]}") # 最初の10件のみ
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# チェックイン記録を取得
|
||||
checkpoints = GpsLog.objects.filter(
|
||||
zekken_number=entry.team.zekken_number,
|
||||
logger.info(f"[GET_CHECKIN_LIST] Found entry: {entry.team.team_name} (zekken: {entry.zekken_number})")
|
||||
|
||||
# 複数のテーブルからチェックイン記録を取得
|
||||
# GpsLogテーブルから
|
||||
gps_checkpoints = GpsLog.objects.filter(
|
||||
zekken_number=str(entry.zekken_number),
|
||||
event_code=event_code
|
||||
).order_by('checkin_time')
|
||||
|
||||
# GpsCheckinテーブルからも取得
|
||||
gps_checkins = GpsCheckin.objects.filter(
|
||||
zekken=str(entry.zekken_number),
|
||||
event_code=event_code
|
||||
).order_by('checkin_time')
|
||||
|
||||
logger.info(f"[GET_CHECKIN_LIST] Found {gps_checkpoints.count()} records in GpsLog")
|
||||
logger.info(f"[GET_CHECKIN_LIST] Found {gps_checkins.count()} records in GpsCheckin")
|
||||
|
||||
# すべてのチェックイン記録を統合
|
||||
all_checkpoints = []
|
||||
|
||||
# GpsLogからの記録を追加
|
||||
for cp in gps_checkpoints:
|
||||
all_checkpoints.append({
|
||||
'source': 'GpsLog',
|
||||
'id': cp.id,
|
||||
'cp_number': cp.cp_number,
|
||||
'checkin_time': cp.checkin_time,
|
||||
'image_address': getattr(cp, 'image_address', None),
|
||||
'is_service_checked': getattr(cp, 'is_service_checked', False)
|
||||
})
|
||||
|
||||
# GpsCheckinからの記録を追加
|
||||
for cp in gps_checkins:
|
||||
all_checkpoints.append({
|
||||
'source': 'GpsCheckin',
|
||||
'id': cp.id,
|
||||
'cp_number': cp.cp_number,
|
||||
'checkin_time': cp.checkin_time,
|
||||
'image_address': None, # GpsCheckinには画像URLがない
|
||||
'is_service_checked': False
|
||||
})
|
||||
|
||||
# 時間順にソート
|
||||
all_checkpoints.sort(key=lambda x: x['checkin_time'] or timezone.now())
|
||||
|
||||
# スタート情報を取得
|
||||
start_info = None
|
||||
if hasattr(entry, 'start_info'):
|
||||
@ -1170,20 +1230,21 @@ def get_checkin_list(request):
|
||||
|
||||
# チェックイン記録をシリアライズ
|
||||
checkpoint_list = []
|
||||
for cp in checkpoints:
|
||||
for cp in all_checkpoints:
|
||||
checkpoint_data = {
|
||||
"id": cp.id,
|
||||
"cp_number": cp.cp_number,
|
||||
"checkin_time": cp.checkin_time.strftime("%Y-%m-%d %H:%M:%S") if cp.checkin_time else None,
|
||||
"image_url": cp.image_address,
|
||||
"is_service_checked": cp.is_service_checked if hasattr(cp, 'is_service_checked') else False
|
||||
"id": cp['id'],
|
||||
"cp_number": cp['cp_number'],
|
||||
"checkin_time": cp['checkin_time'].strftime("%Y-%m-%d %H:%M:%S") if cp['checkin_time'] else None,
|
||||
"image_url": cp['image_address'],
|
||||
"is_service_checked": cp['is_service_checked'],
|
||||
"source": cp['source']
|
||||
}
|
||||
|
||||
# チェックポイントの得点情報を取得( Location2025 モデルがある場合)
|
||||
try:
|
||||
event_cp = Location2025.objects.filter(
|
||||
event_id=event.id,
|
||||
cp_number=cp.cp_number
|
||||
cp_number=cp['cp_number']
|
||||
).first()
|
||||
|
||||
if event_cp:
|
||||
|
||||
@ -361,19 +361,27 @@ def start_from_rogapp(request):
|
||||
with transaction.atomic():
|
||||
# スタート情報をGpsLogとして登録
|
||||
logger.info(f"[START_API] Creating start record - ID: {request_id}")
|
||||
start_time = timezone.now()
|
||||
start_info = GpsLog.objects.create(
|
||||
zekken_number=entry.zekken_number,
|
||||
event_code=event.event_name,
|
||||
cp_number="START",
|
||||
serial_number=0,
|
||||
checkin_time=timezone.now(),
|
||||
create_at=timezone.now(),
|
||||
update_at=timezone.now(),
|
||||
checkin_time=start_time,
|
||||
create_at=start_time,
|
||||
update_at=start_time,
|
||||
buy_flag=False,
|
||||
colabo_company_memo=""
|
||||
)
|
||||
|
||||
# 競技状態を更新
|
||||
entry.is_in_rog = True
|
||||
entry.start_time = start_time
|
||||
entry.last_checkin_time = start_time
|
||||
entry.save()
|
||||
|
||||
logger.info(f"[START_API] ✅ Start record created - ID: {request_id}, GpsLog ID: {start_info.id}")
|
||||
logger.info(f"[START_API] ✅ Competition status updated - is_in_rog: True")
|
||||
|
||||
# 統計情報取得
|
||||
try:
|
||||
@ -387,9 +395,20 @@ def start_from_rogapp(request):
|
||||
response_data = {
|
||||
"status": "OK",
|
||||
"message": "スタート処理が完了しました",
|
||||
"competition_status": {
|
||||
"is_in_rog": entry.is_in_rog,
|
||||
"rogaining_counted": entry.rogaining_counted,
|
||||
"ready_for_goal": entry.ready_for_goal,
|
||||
"is_at_goal": entry.is_at_goal,
|
||||
"start_time": start_info.checkin_time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
||||
},
|
||||
"checkin_record": {
|
||||
"id": start_info.id,
|
||||
"cp_number": "START",
|
||||
"checkin_time": start_info.checkin_time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
||||
},
|
||||
"team_name": team_name,
|
||||
"event_code": event_code,
|
||||
"start_time": start_info.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"zekken_number": entry.zekken_number,
|
||||
"entry_id": entry.id
|
||||
}
|
||||
@ -659,6 +678,13 @@ def checkin_from_rogapp(request):
|
||||
|
||||
logger.info(f"[CHECKIN] ✅ SUCCESS - Team: {team_name}, Zekken: {entry.zekken_number}, CP: {cp_number}, Points: {point_value}, Bonus: {bonus_points}, Time: {checkpoint.checkin_time}, Has Image: {bool(image_url)}, Buy Flag: {buy_flag}, Client IP: {client_ip}, User: {user_info}")
|
||||
|
||||
# 競技状態を更新(スタート・ゴール以外のチェックイン時)
|
||||
if cp_number not in ["START", "GOAL", -2, -1]:
|
||||
entry.rogaining_counted = True
|
||||
entry.last_checkin_time = checkpoint.checkin_time
|
||||
entry.save()
|
||||
logger.info(f"[CHECKIN] ✅ Competition status updated - rogaining_counted: True")
|
||||
|
||||
# 拡張情報があれば保存
|
||||
if gps_coordinates or camera_metadata:
|
||||
try:
|
||||
@ -689,7 +715,13 @@ def checkin_from_rogapp(request):
|
||||
"scoring_breakdown": scoring_breakdown,
|
||||
"validation_status": "pending",
|
||||
"requires_manual_review": bool(gps_coordinates.get('accuracy', 0) > 10), # 10m以上は要審査
|
||||
"image_url": s3_image_url # S3画像URLを返す
|
||||
"image_url": s3_image_url, # S3画像URLを返す
|
||||
"competition_status": {
|
||||
"is_in_rog": entry.is_in_rog,
|
||||
"rogaining_counted": entry.rogaining_counted,
|
||||
"ready_for_goal": entry.ready_for_goal,
|
||||
"is_at_goal": entry.is_at_goal
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
@ -880,11 +912,25 @@ def goal_from_rogapp(request):
|
||||
colabo_company_memo=""
|
||||
)
|
||||
|
||||
# 競技状態を更新
|
||||
entry.is_at_goal = True
|
||||
entry.goal_time = goal_time
|
||||
entry.last_checkin_time = goal_time
|
||||
entry.save()
|
||||
|
||||
logger.info(f"[GOAL] ✅ SUCCESS - Team: {team_name}, Zekken: {entry.zekken_number}, Event: {event_code}, Goal Time: {goal_time}, Score: {score}, Has Image: {bool(image_url)}, Client IP: {client_ip}, User: {user_info}")
|
||||
logger.info(f"[GOAL] ✅ Competition status updated - is_at_goal: True")
|
||||
|
||||
return Response({
|
||||
"status": "OK",
|
||||
"message": "ゴール処理が正常に完了しました",
|
||||
"competition_status": {
|
||||
"is_in_rog": entry.is_in_rog,
|
||||
"rogaining_counted": entry.rogaining_counted,
|
||||
"ready_for_goal": entry.ready_for_goal,
|
||||
"is_at_goal": entry.is_at_goal,
|
||||
"goal_time": goal_time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
||||
},
|
||||
"team_name": team_name,
|
||||
"goal_time": goal_info.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"score": score,
|
||||
|
||||
@ -49,6 +49,10 @@ def submit_qr_points(request):
|
||||
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:")
|
||||
@ -58,7 +62,11 @@ def submit_qr_points(request):
|
||||
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} - 📸 Has Image: {image_data is not None}")
|
||||
logger.info(f"[QR_SUBMIT] ID: {request_id} - <EFBFBD> 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]}...")
|
||||
@ -89,12 +97,21 @@ def submit_qr_points(request):
|
||||
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}")
|
||||
# パラメータ検証 - 実際のリクエストデータに基づく
|
||||
# 基本的な必須パラメータ: 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": "イベントコード、チーム名、QRコードデータが必要です",
|
||||
"message": f"必須パラメータが不足しています: {', '.join(missing_params)}",
|
||||
"missing_parameters": missing_params,
|
||||
"request_id": request_id
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@ -124,51 +141,85 @@ def submit_qr_points(request):
|
||||
"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)
|
||||
# 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 = {"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)
|
||||
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)}
|
||||
|
||||
# チェックポイント情報の取得(cp_numberが指定されている場合)
|
||||
# チェックポイント情報の取得
|
||||
location = None
|
||||
if cp_number:
|
||||
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:
|
||||
logger.info(f"[QR_SUBMIT] 📍 Found location: CP{cp_number} - {location.cp_name} - ID: {request_id}")
|
||||
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()
|
||||
|
||||
# GpsCheckinレコードを作成(QRコード情報を含む)
|
||||
# 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 0, # cp_numberが指定されていない場合は0
|
||||
'checkin_time': current_time,
|
||||
'cp_number': cp_number or (location.cp_number if location else 0),
|
||||
'checkin_time': checkin_time,
|
||||
'is_service_checked': True, # QRコードはサービスポイントとして扱う
|
||||
}
|
||||
|
||||
@ -178,9 +229,17 @@ def submit_qr_points(request):
|
||||
checkin_data['longitude'] = float(longitude)
|
||||
logger.info(f"[QR_SUBMIT] 🌍 GPS coordinates recorded: {latitude}, {longitude} - ID: {request_id}")
|
||||
|
||||
# QRコードデータを格納(JSONフィールドがある場合)
|
||||
# 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_data
|
||||
checkin_data['qr_code_data'] = qr_metadata
|
||||
|
||||
# 画像データを格納(URLまたはパスの場合)
|
||||
if image_data:
|
||||
@ -191,11 +250,6 @@ def submit_qr_points(request):
|
||||
# レコードを作成
|
||||
gps_checkin = GpsCheckin.objects.create(**checkin_data)
|
||||
|
||||
# ポイント計算
|
||||
point_value = 0
|
||||
if location:
|
||||
point_value = location.cp_point or 0
|
||||
|
||||
# 成功レスポンス
|
||||
response_data = {
|
||||
"status": "OK",
|
||||
@ -205,9 +259,11 @@ def submit_qr_points(request):
|
||||
"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(),
|
||||
"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),
|
||||
@ -215,7 +271,7 @@ def submit_qr_points(request):
|
||||
}
|
||||
}
|
||||
|
||||
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}")
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user