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

308 lines
11 KiB
Python
Raw Permalink 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.

"""
競技状態管理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)