Files
rogaining_srv/rog/views_apis/api_play.py

1033 lines
50 KiB
Python
Raw Normal View History

2025-08-20 19:15:19 +09:00
# 既存のインポート部分に追加
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
2025-09-02 17:10:33 +09:00
from rog.models import NewEvent2, Entry, Location2025, GpsLog
2025-08-20 19:15:19 +09:00
from rog.models import GpsLog
import logging
2025-09-02 23:14:14 +09:00
from django.db.models import F, Q, Max
2025-08-20 19:15:19 +09:00
from django.conf import settings
import os
from urllib.parse import urljoin
2025-09-02 17:01:40 +09:00
from django.db import transaction
from django.utils import timezone
from datetime import datetime
import uuid
import time
2025-09-02 20:47:04 +09:00
from django.http import JsonResponse
2025-08-20 19:15:19 +09:00
logger = logging.getLogger(__name__)
2025-09-03 20:22:39 +09:00
# S3アップローダーを安全にインポート
try:
from rog.utils.s3_image_uploader import s3_uploader
S3_AVAILABLE = True
except ImportError as e:
logger.warning(f"S3 uploader not available: {e}")
s3_uploader = None
S3_AVAILABLE = False
2025-08-20 19:15:19 +09:00
"""
解説
この実装では以下の処理を行っています
1.ゼッケン番号イベントコードチェックポイント番号画像アドレスのパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントとチームの存在を確認します
4.既に同じチェックポイントが登録されていないかチェックします
- 既に登録されている場合は警告メッセージを返します
5.新しいチェックポイント情報を登録します
6.成功した場合登録情報と共に成功メッセージを返します
GpsLog モデルはチェックポイント通過情報を保存するための独自のモデルです
既存のシステムに類似のモデルがある場合はそちらを使用してください
"""
@api_view(['POST'])
def input_cp(request):
"""
チェックポイント通過情報を登録
パラメータ:
- zekken_number: ゼッケン番号
- event_code: イベントコード
- cp_number: チェックポイント番号
- image_address: 画像アドレス
"""
logger.info("input_cp called")
# リクエストからパラメータを取得
zekken_number = request.data.get('zekken_number')
event_code = request.data.get('event_code')
cp_number = request.data.get('cp_number')
image_address = request.data.get('image_address')
logger.debug(f"Parameters: zekken_number={zekken_number}, event_code={event_code}, "
f"cp_number={cp_number}, image_address={image_address}")
# パラメータ検証
if not all([zekken_number, event_code, 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"Team not found with zekken: {zekken_number} in event: {event_code}")
return Response({
"status": "ERROR",
"message": "指定されたゼッケン番号のチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
# 既に同じCPを登録済みかチェック
existing_checkpoint = GpsLog.objects.filter(
2025-09-02 23:14:14 +09:00
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
2025-08-20 19:15:19 +09:00
cp_number=cp_number
).first()
if existing_checkpoint:
2025-09-02 23:14:14 +09:00
logger.warning(f"Checkpoint {cp_number} already registered for team: {entry.team.team_name}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "WARNING",
"message": "このチェックポイントは既に登録されています",
"checkpoint_id": existing_checkpoint.id
})
# トランザクション開始
with transaction.atomic():
# チェックポイント登録
2025-09-02 23:14:14 +09:00
# serial_numberを自動生成既存の最大値+1
max_serial = GpsLog.objects.filter(
zekken_number=entry.zekken_number,
event_code=entry.event.event_name
).aggregate(max_serial=Max('serial_number'))['max_serial'] or 0
2025-08-20 19:15:19 +09:00
checkpoint = GpsLog.objects.create(
2025-09-02 23:14:14 +09:00
serial_number=max_serial + 1,
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
2025-08-20 19:15:19 +09:00
cp_number=cp_number,
image_address=image_address,
2025-09-02 23:14:14 +09:00
checkin_time=timezone.now(),
create_at=timezone.now(),
update_at=timezone.now(),
buy_flag=False,
colabo_company_memo=""
2025-08-20 19:15:19 +09:00
)
2025-09-02 23:14:14 +09:00
logger.info(f"Successfully registered CP {cp_number} for team: {entry.team.team_name} "
2025-08-20 19:15:19 +09:00
f"with zekken: {zekken_number}")
return Response({
"status": "OK",
"message": "チェックポイントが正常に登録されました",
"checkpoint_id": checkpoint.id,
"checkin_time": checkpoint.checkin_time.strftime("%Y-%m-%d %H:%M:%S")
})
except Exception as e:
logger.error(f"Error in input_cp: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています
1.eventパラメータを受け取りイベントコードを指定します
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントコードでイベントを検索します
- イベントが存在しない場合はエラーを返します
4.そのイベントに関連する全てのチェックポイント情報を取得します
5.各チェックポイントの詳細情報をリスト形式で整理します
6.チェックポイントリストをJSON形式で返します
EventCheckpoint()=>Location)モデルはイベントごとのチェックポイント設定を保存するためのモデルです
実際のシステムではこのモデルと同等の機能を持つモデルがすでに存在している可能性があります
その場合はそのモデルを使用して実装してください
このエンドポイントはロゲイニングアプリがイベントのチェックポイント情報を取得するのに役立ちます
"""
@api_view(['GET'])
def get_checkpoint_list(request):
"""
指定イベントの全チェックポイント情報を取得
パラメータ:
- event: イベントコード
"""
logger.info("get_checkpoint_list called")
event_code = request.query_params.get('event')
logger.debug(f"Parameters: event={event_code}")
if not event_code:
logger.warning("Event code not provided")
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)
# イベントのチェックポイント情報を取得
2025-08-30 02:20:25 +09:00
checkpoints = Location2025.objects.filter(event_id=event.id).order_by('cp_number')
2025-08-20 19:15:19 +09:00
checkpoint_list = []
for cp in checkpoints:
checkpoint_info = {
"cp_number": cp.cp_number,
"cp_name": cp.cp_name,
"cp_point": cp.cp_point,
"latitude": cp.latitude,
"longitude": cp.longitude,
"cp_description": cp.description,
2025-08-30 02:20:25 +09:00
"is_service_cp": cp.buy_point > 0 # buy_pointが0より大きい場合はサービスポイント
2025-08-20 19:15:19 +09:00
}
checkpoint_list.append(checkpoint_info)
logger.info(f"Successfully retrieved {len(checkpoint_list)} checkpoints for event {event_code}")
return Response({
"status": "OK",
"checkpoints": checkpoint_list,
"total_count": len(checkpoint_list)
})
except Exception as e:
logger.error(f"Error in get_checkpoint_list: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
この実装では以下の処理を行っています
1.イベントコードとチーム名のパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントとチームの存在を確認します
4.チームが既にスタート済みかどうかをチェックします
- 既にスタート済みの場合は警告メッセージを返します
5.スタート情報を登録します
6.成功した場合スタート時間と共に成功メッセージを返します
このエンドポイントによりロゲイニングアプリからチームのスタート処理を行うことができ開始時間が正確に記録されます
"""
@api_view(['POST'])
def start_from_rogapp(request):
"""
アプリからスタート処理を実行
パラメータ:
- event_code: イベントコード
- team_name: チーム名
"""
2025-09-02 17:01:40 +09:00
# リクエスト詳細情報を取得
client_ip = request.META.get('REMOTE_ADDR', 'Unknown')
user_agent = request.META.get('HTTP_USER_AGENT', 'Unknown')
user_info = request.user.email if request.user.is_authenticated else 'Anonymous'
request_time = timezone.now()
request_id = str(uuid.uuid4())[:8]
2025-09-02 20:47:04 +09:00
# 初期ログ出力
logger.info(f"[START_API] 🚀 REQUEST_START - ID: {request_id}")
logger.info(f"[START_API] Request details - Time: {request_time}, Client: {client_ip}, User: {user_info}")
logger.info(f"[START_API] Headers - User-Agent: {user_agent[:200]}")
logger.info(f"[START_API] Content-Type: {request.content_type}")
logger.info(f"[START_API] Method: {request.method}")
2025-08-20 19:15:19 +09:00
try:
2025-09-02 20:47:04 +09:00
# リクエストデータの解析
logger.info(f"[START_API] Raw request body type: {type(request.body)}")
logger.info(f"[START_API] Raw request body length: {len(request.body) if request.body else 0}")
# リクエストからパラメータを取得
logger.info(f"[START_API] Attempting to parse request data...")
event_code = request.data.get('event_code')
team_name = request.data.get('team_name')
latitude = request.data.get('latitude', 0.0)
longitude = request.data.get('longitude', 0.0)
extra_data = request.data.get('extra_data', {})
logger.info(f"[START_API] Parameters parsed - ID: {request_id}")
logger.info(f"[START_API] event_code: '{event_code}' (type: {type(event_code)})")
logger.info(f"[START_API] team_name: '{team_name}' (type: {type(team_name)})")
logger.info(f"[START_API] GPS: lat={latitude}, lon={longitude}")
logger.info(f"[START_API] extra_data: {extra_data}")
# パラメータ検証
if not event_code:
logger.warning(f"[START_API] ❌ Missing event_code - ID: {request_id}")
return Response({
"status": "ERROR",
"message": "イベントコードが必要です"
}, status=status.HTTP_400_BAD_REQUEST)
if not team_name:
logger.warning(f"[START_API] ❌ Missing team_name - ID: {request_id}")
return Response({
"status": "ERROR",
"message": "チーム名が必要です"
}, status=status.HTTP_400_BAD_REQUEST)
logger.info(f"[START_API] ✅ Parameter validation passed - ID: {request_id}")
# データベース操作開始
logger.info(f"[START_API] Starting database operations - ID: {request_id}")
2025-08-20 19:15:19 +09:00
# イベントの存在確認
2025-09-02 20:47:04 +09:00
logger.info(f"[START_API] Searching for event: '{event_code}' - ID: {request_id}")
2025-08-20 19:15:19 +09:00
event = NewEvent2.objects.filter(event_name=event_code).first()
2025-09-02 20:47:04 +09:00
2025-08-20 19:15:19 +09:00
if not event:
2025-09-02 20:47:04 +09:00
logger.warning(f"[START_API] ❌ Event not found - ID: {request_id}, event_code: '{event_code}'")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたイベントが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 20:47:04 +09:00
logger.info(f"[START_API] ✅ Event found - ID: {request_id}, Event ID: {event.id}, Name: '{event.event_name}'")
logger.info(f"[START_API] Event details - Start: {event.start_datetime}, End: {event.end_datetime}")
2025-09-02 17:01:40 +09:00
2025-08-20 19:15:19 +09:00
# チームの存在確認
2025-09-02 20:47:04 +09:00
logger.info(f"[START_API] Searching for team: '{team_name}' in event: '{event_code}' - ID: {request_id}")
2025-08-20 19:15:19 +09:00
entry = Entry.objects.filter(
event=event,
2025-09-02 23:14:14 +09:00
team__team_name=team_name
2025-08-20 19:15:19 +09:00
).first()
if not entry:
2025-09-02 20:47:04 +09:00
logger.warning(f"[START_API] ❌ Team not found - ID: {request_id}, team: '{team_name}', event: '{event_code}'")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 20:47:04 +09:00
logger.info(f"[START_API] ✅ Team found - ID: {request_id}, Entry ID: {entry.id}")
logger.info(f"[START_API] Team details - Zekken: {entry.zekken_number}, Category: '{entry.category.category_name if entry.category else 'N/A'}'")
2025-09-02 17:01:40 +09:00
2025-09-02 20:47:04 +09:00
# 既にスタート済みかチェック
logger.info(f"[START_API] Checking if team already started - ID: {request_id}")
2025-09-02 17:10:33 +09:00
existing_start = GpsLog.objects.filter(
2025-09-02 23:14:14 +09:00
zekken_number=entry.zekken_number,
event_code=event.event_name,
2025-09-02 17:10:33 +09:00
cp_number="START",
serial_number=0
).first()
if existing_start:
2025-09-02 20:47:04 +09:00
logger.warning(f"[START_API] ⚠️ Team already started - ID: {request_id}, start_time: {existing_start.checkin_time}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "WARNING",
"message": "このチームは既にスタートしています",
2025-09-02 20:47:04 +09:00
"start_time": existing_start.checkin_time.strftime("%Y-%m-%d %H:%M:%S")
2025-08-20 19:15:19 +09:00
})
2025-09-02 20:47:04 +09:00
logger.info(f"[START_API] ✅ Team not started yet - ID: {request_id}")
2025-08-20 19:15:19 +09:00
# トランザクション開始
2025-09-02 20:47:04 +09:00
logger.info(f"[START_API] Starting database transaction - ID: {request_id}")
2025-08-20 19:15:19 +09:00
with transaction.atomic():
2025-09-02 17:10:33 +09:00
# スタート情報をGpsLogとして登録
2025-09-02 20:47:04 +09:00
logger.info(f"[START_API] Creating start record - ID: {request_id}")
2025-09-04 19:25:14 +09:00
start_time = timezone.now()
2025-09-02 17:10:33 +09:00
start_info = GpsLog.objects.create(
2025-09-02 23:14:14 +09:00
zekken_number=entry.zekken_number,
event_code=event.event_name,
2025-09-02 20:47:04 +09:00
cp_number="START",
serial_number=0,
2025-09-04 19:25:14 +09:00
checkin_time=start_time,
create_at=start_time,
update_at=start_time,
2025-09-02 23:14:14 +09:00
buy_flag=False,
colabo_company_memo=""
2025-08-20 19:15:19 +09:00
)
2025-09-04 19:25:14 +09:00
# 競技状態を更新
entry.is_in_rog = True
entry.start_time = start_time
entry.last_checkin_time = start_time
entry.save()
2025-09-02 20:47:04 +09:00
logger.info(f"[START_API] ✅ Start record created - ID: {request_id}, GpsLog ID: {start_info.id}")
2025-09-04 19:25:14 +09:00
logger.info(f"[START_API] ✅ Competition status updated - is_in_rog: True")
2025-09-02 17:01:40 +09:00
2025-09-02 20:47:04 +09:00
# 統計情報取得
try:
total_checkpoints = Location2025.objects.filter(event=event).count()
logger.info(f"[START_API] Total checkpoints available: {total_checkpoints} - ID: {request_id}")
except Exception as e:
logger.warning(f"[START_API] Could not get checkpoint count: {str(e)} - ID: {request_id}")
total_checkpoints = 0
2025-08-20 19:15:19 +09:00
2025-09-02 20:47:04 +09:00
# 成功レスポンス準備
response_data = {
2025-08-20 19:15:19 +09:00
"status": "OK",
"message": "スタート処理が完了しました",
2025-09-04 19:25:14 +09:00
"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")
},
2025-08-20 19:15:19 +09:00
"team_name": team_name,
"event_code": event_code,
2025-09-02 20:47:04 +09:00
"zekken_number": entry.zekken_number,
"entry_id": entry.id
}
logger.info(f"[START_API] 🎉 SUCCESS - ID: {request_id}, Team: '{team_name}', Zekken: {entry.zekken_number}")
logger.info(f"[START_API] Response data: {response_data}")
return Response(response_data)
2025-08-20 19:15:19 +09:00
except Exception as e:
2025-09-02 20:47:04 +09:00
# 詳細なエラー情報をログに出力
logger.error(f"[START_API] 💥 CRITICAL ERROR - ID: {request_id}")
logger.error(f"[START_API] Error type: {type(e).__name__}")
logger.error(f"[START_API] Error message: {str(e)}")
logger.error(f"[START_API] Request data: event_code='{event_code if 'event_code' in locals() else 'UNDEFINED'}', team_name='{team_name if 'team_name' in locals() else 'UNDEFINED'}'")
logger.error(f"[START_API] Client: {client_ip}, User: {user_info}")
logger.error(f"[START_API] Full traceback:", exc_info=True)
# エラーレスポンス
try:
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました",
"error_id": request_id
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as response_error:
logger.error(f"[START_API] 💥 DOUBLE ERROR - Could not create error response: {str(response_error)}")
# 最後の手段として、シンプルなJSONレスポンスを返す
return JsonResponse({
"status": "ERROR",
"message": "サーバーエラーが発生しました",
"error_id": request_id
}, status=500)
finally:
# 処理完了ログ
end_time = timezone.now()
duration = (end_time - request_time).total_seconds()
logger.info(f"[START_API] 🏁 REQUEST_END - ID: {request_id}, Duration: {duration:.3f}s")
2025-08-20 19:15:19 +09:00
"""
解説
この実装では以下の処理を行っています
1.イベントコードチーム名チェックポイント番号画像URLのパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントとチームの存在を確認します
4.チームがスタートしているかを確認します
- スタートしていない場合はエラーを返します
5.既に同じチェックポイントが登録されていないかチェックします
- 既に登録されている場合は警告メッセージを返します
6.イベントのチェックポイント定義があればそのデータを取得します
7.チェックポイント通過情報を登録します
8.成功した場合登録情報と獲得ポイントを含む成功メッセージを返します
このエンドポイントはロゲイニングアプリからチェックポイント通過情報を登録するためのもので
/input_cpエンドポイントと類似していますがチームをゼッケン番号ではなくチーム名で指定する点や
チェックポイントのポイント情報も返す点が異なります
"""
@api_view(['POST'])
def checkin_from_rogapp(request):
"""
アプリからチェックイン処理を実行
パラメータ:
- event_code: イベントコード
- team_name: チーム名
- cp_number: チェックポイント番号
- image: 画像URL
2025-08-27 15:01:06 +09:00
- buy_flag: 購入フラグ (新規)
- gps_coordinates: GPS座標情報 (新規)
- camera_metadata: カメラメタデータ (新規)
2025-08-20 19:15:19 +09:00
"""
# リクエスト詳細情報を取得
client_ip = request.META.get('REMOTE_ADDR', 'Unknown')
user_agent = request.META.get('HTTP_USER_AGENT', 'Unknown')
user_info = request.user.email if request.user.is_authenticated else 'Anonymous'
request_time = timezone.now()
request_id = str(uuid.uuid4())[:8]
# APIの最初にログを記録問題の特定のため
logger.info(f"[CHECKIN] 🚀 API Request Received - ID: {request_id}, Time: {request_time}, Client IP: {client_ip}, User: {user_info}, S3_Available: {S3_AVAILABLE}")
logger.info(f"[CHECKIN] 📍 API Request Started - ID: {request_id}, Time: {request_time}, Client IP: {client_ip}, User: {user_info}")
# リクエストからパラメータを取得
2025-08-20 19:15:19 +09:00
event_code = request.data.get('event_code')
team_name = request.data.get('team_name')
cp_number = request.data.get('cp_number')
image_url = request.data.get('image')
2025-08-27 15:01:06 +09:00
# API変更要求書対応: 新パラメータ追加
buy_flag = request.data.get('buy_flag', False)
gps_coordinates = request.data.get('gps_coordinates', {})
camera_metadata = request.data.get('camera_metadata', {})
2025-09-04 10:10:24 +09:00
# 🔍 詳細なパラメータログ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}")
2025-09-02 17:01:40 +09:00
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座標情報をログに記録
if gps_coordinates:
logger.info(f"[CHECKIN] GPS coordinates - ID: {request_id}, lat: {gps_coordinates.get('latitude')}, lng: {gps_coordinates.get('longitude')}, accuracy: {gps_coordinates.get('accuracy')}m, timestamp: {gps_coordinates.get('timestamp')}")
# カメラメタデータをログに記録
if camera_metadata:
logger.info(f"[CHECKIN] Camera metadata - ID: {request_id}, capture_time: {camera_metadata.get('capture_time')}, device: {camera_metadata.get('device_info')}")
2025-08-20 19:15:19 +09:00
# パラメータ検証
if not all([event_code, team_name, cp_number]):
2025-09-02 17:01:40 +09:00
logger.warning(f"[CHECKIN] ❌ Missing required parameters - ID: {request_id}, event_code: '{event_code}', team_name: '{team_name}', cp_number: {cp_number}, Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "イベントコード、チーム名、チェックポイント番号が必要です"
}, status=status.HTTP_400_BAD_REQUEST)
try:
# イベントの存在確認
event = NewEvent2.objects.filter(event_name=event_code).first()
if not event:
2025-09-02 17:01:40 +09:00
logger.warning(f"[CHECKIN] ❌ Event not found - ID: {request_id}, event_code: '{event_code}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたイベントが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
logger.info(f"[CHECKIN] ✅ Event found - ID: {request_id}, Event ID: {event.id}, Name: '{event.event_name}'")
2025-08-20 19:15:19 +09:00
# チームの存在確認
entry = Entry.objects.filter(
event=event,
2025-09-02 23:14:14 +09:00
team__team_name=team_name
2025-08-20 19:15:19 +09:00
).first()
if not entry:
2025-09-02 17:01:40 +09:00
logger.warning(f"[CHECKIN] ❌ Team not found - ID: {request_id}, team_name: '{team_name}', event_code: '{event_code}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
logger.info(f"[CHECKIN] ✅ Team found - ID: {request_id}, Entry ID: {entry.id}, Team: '{team_name}', Zekken: {entry.zekken_number}, Category: '{entry.category.category_name if entry.category else 'N/A'}'")
2025-08-20 19:15:19 +09:00
# チームがスタートしているか確認
2025-09-02 23:14:14 +09:00
start_record = GpsLog.objects.filter(
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
cp_number="START",
serial_number=0
).first()
if not start_record:
2025-09-02 17:01:40 +09:00
logger.warning(f"[CHECKIN] ❌ Team has not started yet - ID: {request_id}, team_name: '{team_name}', zekken: {entry.zekken_number}, cp_number: {cp_number}, Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "このチームはまだスタートしていません。先にスタート処理を行ってください。"
}, status=status.HTTP_400_BAD_REQUEST)
2025-09-02 23:14:14 +09:00
logger.info(f"[CHECKIN] ✅ Team has started - ID: {request_id}, start_time: {start_record.checkin_time}")
2025-08-20 19:15:19 +09:00
# 既に同じCPを登録済みかチェック
existing_checkpoint = GpsLog.objects.filter(
2025-09-02 23:14:14 +09:00
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
2025-08-20 19:15:19 +09:00
cp_number=cp_number
).first()
if existing_checkpoint:
2025-09-02 17:01:40 +09:00
logger.warning(f"[CHECKIN] ⚠️ Checkpoint already registered - ID: {request_id}, team_name: '{team_name}', zekken: {entry.zekken_number}, cp_number: {cp_number}, previous_checkin_time: {existing_checkpoint.checkin_time}, Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "WARNING",
"message": "このチェックポイントは既に登録されています",
"checkpoint_id": existing_checkpoint.id,
"checkin_time": existing_checkpoint.checkin_time.strftime("%Y-%m-%d %H:%M:%S")
})
# イベントのチェックポイント定義を確認(存在する場合)
event_cp = None
try:
event_cp = Location2025.objects.filter(
2025-08-20 19:15:19 +09:00
event=event,
cp_number=cp_number
).first()
2025-09-02 17:01:40 +09:00
if event_cp:
logger.info(f"[CHECKIN] ✅ Event checkpoint found - ID: {request_id}, CP: {cp_number}, Name: '{event_cp.cp_name}', Points: {getattr(event_cp, 'checkin_point', 'N/A')}, Category: '{event_cp.category}'")
else:
logger.info(f"[CHECKIN] ⚠️ Event checkpoint not defined - ID: {request_id}, CP: {cp_number}")
except Exception as e:
logger.warning(f"[CHECKIN] Location2025 model issue - ID: {request_id}, CP: {cp_number}, Error: {e}")
2025-08-20 19:15:19 +09:00
# トランザクション開始
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] 🔄 Starting database transaction - ID: {request_id}")
2025-08-20 19:15:19 +09:00
with transaction.atomic():
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] 🔒 Database transaction started successfully - ID: {request_id}")
2025-09-03 07:56:40 +09:00
# S3に画像をアップロードし、S3 URLを取得
s3_image_url = image_url
2025-09-03 20:22:39 +09:00
if image_url and S3_AVAILABLE and s3_uploader:
2025-09-03 07:56:40 +09:00
try:
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] 📤 Starting S3 upload - ID: {request_id}, image_size: {len(image_url)} chars")
2025-09-03 07:56:40 +09:00
s3_image_url = s3_uploader.upload_checkin_image(
image_data=image_url,
event_code=entry.event.event_name,
zekken_number=entry.zekken_number,
cp_number=cp_number
)
logger.info(f"[CHECKIN] S3 upload - Original: {image_url[:50]}..., S3: {s3_image_url}")
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] ✅ S3 upload completed - ID: {request_id}, new_url: {s3_image_url[:50]}...")
2025-09-03 07:56:40 +09:00
except Exception as e:
logger.error(f"[CHECKIN] S3 upload failed, using original URL: {e}")
2025-09-06 02:23:25 +09:00
logger.error(f"[GPSLOG] ❌ S3 upload failed - ID: {request_id}, error: {e}")
2025-09-03 07:56:40 +09:00
s3_image_url = image_url
2025-09-03 20:22:39 +09:00
elif image_url:
logger.info(f"[CHECKIN] S3 not available, using original URL")
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] S3 not available - ID: {request_id}, using original URL")
2025-09-03 07:56:40 +09:00
2025-09-02 23:14:14 +09:00
# serial_numberを自動生成既存の最大値+1
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] 🔢 Calculating serial number for zekken: {entry.zekken_number}, event: {entry.event.event_name}")
2025-09-02 23:14:14 +09:00
max_serial = GpsLog.objects.filter(
zekken_number=entry.zekken_number,
event_code=entry.event.event_name
).aggregate(max_serial=Max('serial_number'))['max_serial'] or 0
2025-09-06 02:23:25 +09:00
new_serial = max_serial + 1
logger.info(f"[GPSLOG] 📊 Serial number calculation - max_existing: {max_serial}, new_serial: {new_serial}")
# GpsLogテーブルへの書き込み準備
gpslog_data = {
'serial_number': new_serial,
'zekken_number': entry.zekken_number,
'event_code': entry.event.event_name,
'cp_number': cp_number,
'image_address': s3_image_url, # S3 URLを保存
'checkin_time': timezone.now(),
'create_at': timezone.now(),
'update_at': timezone.now(),
'buy_flag': False,
'is_service_checked': False, # Location2025にはis_service_cpがないので、デフォルトでFalse
'colabo_company_memo': ""
}
logger.info(f"[GPSLOG] 📝 Preparing GpsLog record - ID: {request_id}")
logger.info(f"[GPSLOG] 🏷️ Data: serial={new_serial}, zekken={entry.zekken_number}, event={entry.event.event_name}, cp={cp_number}")
logger.info(f"[GPSLOG] 🖼️ Image: has_image={bool(s3_image_url)}, url_length={len(s3_image_url) if s3_image_url else 0}")
logger.info(f"[GPSLOG] ⏰ Timestamps: checkin_time={gpslog_data['checkin_time']}")
2025-09-03 07:56:40 +09:00
# チェックポイント登録S3 URLを使用
2025-09-06 02:23:25 +09:00
try:
checkpoint = GpsLog.objects.create(**gpslog_data)
logger.info(f"[GPSLOG] ✅ GpsLog record created successfully - ID: {checkpoint.id}, request_id: {request_id}")
logger.info(f"[GPSLOG] 🎯 Created record details - DB_ID: {checkpoint.id}, serial: {checkpoint.serial_number}, checkin_time: {checkpoint.checkin_time}")
# 作成されたレコードの検証
if checkpoint.id:
logger.info(f"[GPSLOG] 🔍 Verification - Record exists in database with ID: {checkpoint.id}")
else:
logger.warning(f"[GPSLOG] ⚠️ Warning - Record created but ID is None")
except Exception as create_error:
logger.error(f"[GPSLOG] ❌ Failed to create GpsLog record - ID: {request_id}, Error: {create_error}")
logger.error(f"[GPSLOG] 📊 Failed data: {gpslog_data}")
raise create_error
2025-08-20 19:15:19 +09:00
# 獲得ポイントの計算Location2025から取得
point_value = event_cp.checkin_point if event_cp else 0
2025-08-27 15:01:06 +09:00
bonus_points = 0
scoring_breakdown = {
"base_points": point_value,
"camera_bonus": 0,
"total_points": point_value
}
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] 🎯 Point calculation - base_points: {point_value}, bonus_points: {bonus_points}")
2025-08-27 15:01:06 +09:00
# カメラボーナス計算
if image_url and event_cp and hasattr(event_cp, 'evaluation_value'):
if event_cp.evaluation_value == "1": # 写真撮影必須ポイント
bonus_points += 5
scoring_breakdown["camera_bonus"] = 5
scoring_breakdown["total_points"] += 5
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] 📸 Camera bonus applied - additional_points: 5, total: {scoring_breakdown['total_points']}")
2025-08-27 15:01:06 +09:00
2025-09-02 17:01:40 +09:00
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}")
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] 🏆 FINAL RESULT - GpsLog_ID: {checkpoint.id}, Serial: {checkpoint.serial_number}, Total_Points: {scoring_breakdown['total_points']}, Request_ID: {request_id}")
2025-09-02 17:01:40 +09:00
2025-09-04 19:25:14 +09:00
# 競技状態を更新(スタート・ゴール以外のチェックイン時)
if cp_number not in ["START", "GOAL", -2, -1]:
entry.rogaining_counted = True
entry.last_checkin_time = checkpoint.checkin_time
entry.save()
2025-09-06 02:23:25 +09:00
logger.info(f"[CHECKIN] ✅ Competition status updated - rogaining_counted: True, last_checkin_time: {checkpoint.checkin_time}")
logger.info(f"[GPSLOG] 🎮 Entry updated - entry_id: {entry.id}, rogaining_counted: {entry.rogaining_counted}")
else:
logger.info(f"[GPSLOG] Special checkpoint ({cp_number}) - Entry status not updated")
2025-09-04 19:25:14 +09:00
2025-08-27 15:01:06 +09:00
# 拡張情報があれば保存
if gps_coordinates or camera_metadata:
try:
from ..models import CheckinExtended
2025-09-06 02:23:25 +09:00
extended_record = CheckinExtended.objects.create(
2025-08-27 15:01:06 +09:00
gpslog=checkpoint,
gps_latitude=gps_coordinates.get('latitude'),
gps_longitude=gps_coordinates.get('longitude'),
gps_accuracy=gps_coordinates.get('accuracy'),
gps_timestamp=gps_coordinates.get('timestamp'),
camera_capture_time=camera_metadata.get('capture_time'),
device_info=camera_metadata.get('device_info'),
bonus_points=bonus_points,
scoring_breakdown=scoring_breakdown
)
2025-09-06 02:23:25 +09:00
logger.info(f"[GPSLOG] 📋 Extended info saved - CheckinExtended_ID: {extended_record.id}")
2025-08-27 15:01:06 +09:00
except Exception as ext_error:
2025-09-06 02:23:25 +09:00
logger.warning(f"[GPSLOG] ⚠️ Failed to save extended checkin info: {ext_error}")
else:
logger.info(f"[GPSLOG] No extended GPS/camera data to save")
2025-08-20 19:15:19 +09:00
2025-09-06 02:23:25 +09:00
# レスポンス作成
response_data = {
2025-08-20 19:15:19 +09:00
"status": "OK",
"message": "チェックポイントが正常に登録されました",
"team_name": team_name,
"cp_number": cp_number,
"checkpoint_id": checkpoint.id,
"checkin_time": checkpoint.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
2025-08-27 15:01:06 +09:00
"point_value": point_value,
"bonus_points": bonus_points,
"scoring_breakdown": scoring_breakdown,
"validation_status": "pending",
2025-09-03 07:56:40 +09:00
"requires_manual_review": bool(gps_coordinates.get('accuracy', 0) > 10), # 10m以上は要審査
2025-09-04 19:25:14 +09:00
"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
}
2025-09-06 02:23:25 +09:00
}
logger.info(f"[GPSLOG] 📤 Creating response - ID: {request_id}")
logger.info(f"[GPSLOG] 📊 Response summary - checkpoint_id: {checkpoint.id}, total_points: {scoring_breakdown['total_points']}, status: OK")
logger.info(f"[GPSLOG] 🔄 Transaction completed successfully - ID: {request_id}")
return Response(response_data)
except Exception as e:
# より詳細なエラー情報をログに記録
import traceback
error_details = {
"error_type": type(e).__name__,
"error_message": str(e),
"traceback": traceback.format_exc(),
"request_data": request.data if hasattr(request, 'data') else "No data",
"user_agent": user_agent if 'user_agent' in locals() else "Unknown",
"client_ip": client_ip if 'client_ip' in locals() else "Unknown"
}
logger.error(f"[CHECKIN] ❌ DETAILED ERROR: {error_details}")
2025-09-06 02:23:25 +09:00
logger.error(f"[GPSLOG] ❌ GpsLog creation failed - ID: {request_id if 'request_id' in locals() else 'Unknown'}, Error: {type(e).__name__}: {str(e)}")
# GpsLogトランザクション失敗の詳細
if 'checkpoint' in locals():
logger.error(f"[GPSLOG] 💾 Transaction state - checkpoint created: True, checkpoint_id: {getattr(checkpoint, 'id', 'Unknown')}")
else:
logger.error(f"[GPSLOG] 💾 Transaction state - checkpoint created: False, transaction rolled back")
# より具体的なエラーメッセージを返す
if "データベース" in str(e).lower() or "database" in str(e).lower():
error_message = "データベースの構造に問題があります。アプリを再起動してください。"
elif "s3" in str(e).lower() or "boto" in str(e).lower():
error_message = "画像アップロードに問題があります。しばらく後に再試行してください。"
elif "import" in str(e).lower() or "module" in str(e).lower():
error_message = "サーバーモジュールの読み込みエラーです。管理者にお問い合わせください。"
else:
error_message = "サーバーエラーが発生しました。しばらく後に再試行してください。"
2025-09-03 20:22:39 +09:00
return Response({
"status": "ERROR",
"message": error_message,
"error_code": "CHECKIN_API_ERROR"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
2025-08-20 19:15:19 +09:00
"""
Goal API from rogapp - 解説
2025-08-20 19:15:19 +09:00
この実装では以下の処理を行っています
1. イベントコードチーム名画像URLゴール時間のパラメータを受け取ります
2. パラメータが不足している場合はエラーを返します
3. 指定されたイベントとチームの存在を確認します
4. チームがスタートしているかを確認します
- スタートしていない場合はエラーを返します
5. 既にゴールしているかチェックします
- ゴール済みの場合は警告メッセージを返します
6. ゴール時間を処理します提供されていない場合は現在時刻を使用
7. チームのスコアを計算します
8. スコアボードを生成します実際の生成ロジックは実装によって異なります
9. ゴール情報を登録します
10. 成功した場合ゴール情報スコアスコアボードURLを含む成功メッセージを返します
2025-08-20 19:15:19 +09:00
スコアボードの生成部分は実際のシステムの要件に合わせて詳細に実装する必要があります
この例では単純にPDFファイルのパスとURLを生成していますが
実際にはPDF生成ライブラリReportLabWeasyPrintなどを使用してスコアボードを生成する必要があります
"""
@api_view(['POST'])
def goal_from_rogapp(request):
"""
アプリからゴール処理を実行しスコアボードを生成
パラメータ:
- event_code: イベントコード
- team_name: チーム名
- image: 画像URL
- goal_time: ゴール時間
"""
2025-09-02 17:01:40 +09:00
# ログ用のリクエストID生成
request_id = uuid.uuid4().hex[:8]
request_time = time.time()
client_ip = request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', 'Unknown'))
user_agent = request.META.get('HTTP_USER_AGENT', 'Unknown')
user_info = f"{request.user.username}({request.user.id})" if request.user.is_authenticated else "Anonymous"
2025-08-20 19:15:19 +09:00
# リクエストからパラメータを取得
event_code = request.data.get('event_code')
team_name = request.data.get('team_name')
image_url = request.data.get('image')
goal_time_str = request.data.get('goal_time')
2025-09-02 17:01:40 +09:00
logger.info(f"[GOAL] 🏁 API call started - ID: {request_id}, event_code: '{event_code}', team_name: '{team_name}', has_image: {bool(image_url)}, goal_time: '{goal_time_str}', Client IP: {client_ip}, User: {user_info}, User-Agent: {user_agent[:100]}")
2025-08-20 19:15:19 +09:00
# パラメータ検証
if not all([event_code, team_name]):
2025-09-02 17:01:40 +09:00
logger.error(f"[GOAL] ❌ Missing required parameters - ID: {request_id}, event_code: '{event_code}', team_name: '{team_name}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "イベントコードとチーム名が必要です"
}, status=status.HTTP_400_BAD_REQUEST)
try:
# イベントの存在確認
event = NewEvent2.objects.filter(event_name=event_code).first()
if not event:
2025-09-02 17:01:40 +09:00
logger.error(f"[GOAL] ❌ Event not found - ID: {request_id}, event_code: '{event_code}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたイベントが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
logger.info(f"[GOAL] ✅ Event found - ID: {request_id}, event: '{event.event_name}', event_id: {event.id}")
2025-08-20 19:15:19 +09:00
# チームの存在確認
entry = Entry.objects.filter(
event=event,
2025-09-02 23:14:14 +09:00
team__team_name=team_name
2025-08-20 19:15:19 +09:00
).first()
if not entry:
2025-09-02 17:01:40 +09:00
logger.error(f"[GOAL] ❌ Team not found - ID: {request_id}, team_name: '{team_name}', event_code: '{event_code}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
logger.info(f"[GOAL] ✅ Team found - ID: {request_id}, team_name: '{team_name}', zekken: {entry.zekken_number}, entry_id: {entry.id}")
2025-09-02 17:10:33 +09:00
# チームがスタートしているか確認GpsLogでSTARTレコードを確認
start_record = GpsLog.objects.filter(
2025-09-02 23:14:14 +09:00
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
2025-09-02 17:10:33 +09:00
cp_number="START",
serial_number=0
).first()
if not start_record:
2025-08-20 19:15:19 +09:00
logger.warning(f"Team {team_name} has not started yet")
return Response({
"status": "ERROR",
"message": "このチームはまだスタートしていません。先にスタート処理を行ってください。"
}, status=status.HTTP_400_BAD_REQUEST)
2025-09-02 17:10:33 +09:00
# 既にゴールしているかチェックGpsLogでGOALレコードを確認
existing_goal = GpsLog.objects.filter(
2025-09-02 23:14:14 +09:00
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
2025-09-02 17:10:33 +09:00
cp_number="GOAL",
serial_number=9999
).first()
if existing_goal:
logger.warning(f"Team {team_name} already reached goal at {existing_goal.checkin_time}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "WARNING",
"message": "このチームは既にゴールしています",
2025-09-02 17:10:33 +09:00
"goal_time": existing_goal.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
"scoreboard_url": existing_goal.extra_data.get('scoreboard_url', '') if existing_goal.extra_data else ''
2025-08-20 19:15:19 +09:00
})
# ゴール時間の処理
if goal_time_str:
try:
goal_time = datetime.strptime(goal_time_str, "%Y-%m-%d %H:%M:%S")
except ValueError:
logger.warning(f"Invalid goal_time format: {goal_time_str}")
goal_time = timezone.now()
else:
goal_time = timezone.now()
# トランザクション開始
with transaction.atomic():
# スコアの計算
score = calculate_team_score(entry)
# スコアボードの生成
scoreboard_filename = f"scoreboard_{entry.zekken_number}_{uuid.uuid4().hex[:8]}.pdf"
scoreboard_path = os.path.join(settings.MEDIA_ROOT, 'scoreboards', scoreboard_filename)
os.makedirs(os.path.dirname(scoreboard_path), exist_ok=True)
# ここでスコアボードを実際に生成する処理を実装
# 例: generate_scoreboard(entry, score, scoreboard_path)
# スコアボードへのURL
scoreboard_url = f"{settings.MEDIA_URL}scoreboards/{scoreboard_filename}"
2025-09-02 17:10:33 +09:00
# ゴール情報をGpsLogとして登録
goal_info = GpsLog.objects.create(
2025-09-02 23:14:14 +09:00
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
2025-09-02 17:10:33 +09:00
cp_number="GOAL",
serial_number=9999, # ゴール記録の固定シリアル番号
checkin_time=goal_time,
image_address=image_url,
2025-09-02 23:14:14 +09:00
create_at=timezone.now(),
update_at=timezone.now(),
buy_flag=False,
score=score,
scoreboard_url=scoreboard_url,
colabo_company_memo=""
2025-08-20 19:15:19 +09:00
)
2025-09-04 19:25:14 +09:00
# 競技状態を更新
entry.is_at_goal = True
entry.goal_time = goal_time
entry.last_checkin_time = goal_time
entry.save()
2025-09-02 17:01:40 +09:00
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}")
2025-09-04 19:25:14 +09:00
logger.info(f"[GOAL] ✅ Competition status updated - is_at_goal: True")
2025-08-20 19:15:19 +09:00
return Response({
"status": "OK",
"message": "ゴール処理が正常に完了しました",
2025-09-04 19:25:14 +09:00
"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")
},
2025-08-20 19:15:19 +09:00
"team_name": team_name,
2025-09-02 23:14:14 +09:00
"goal_time": goal_info.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
2025-08-20 19:15:19 +09:00
"score": score,
"scoreboard_url": scoreboard_url
})
except Exception as e:
2025-09-02 17:01:40 +09:00
logger.error(f"[GOAL] ❌ ERROR - team_name: {team_name}, event_code: {event_code}, Client IP: {client_ip}, Error: {str(e)}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def calculate_team_score(entry):
"""チームのスコアを計算する補助関数"""
# チームが通過したチェックポイントを取得
2025-09-02 23:14:14 +09:00
checkpoints = GpsLog.objects.filter(
zekken_number=entry.zekken_number,
event_code=entry.event.event_name
)
2025-08-20 19:15:19 +09:00
total_score = 0
for cp in checkpoints:
# チェックポイントの得点を取得
cp_point = 0
try:
# Location2025
event_cp = Location2025.objects.filter(
2025-08-20 19:15:19 +09:00
event=entry.event,
cp_number=cp.cp_number
).first()
if event_cp:
cp_point = event_cp.cp_point
except:
# モデルが存在しない場合はデフォルト値を使用
cp_point = 10
total_score += cp_point
return total_score