Files
rogaining_srv/rog/views_apis/api_play.py
2025-08-27 15:01:06 +09:00

656 lines
27 KiB
Python
Executable File
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.

# 既存のインポート部分に追加
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
from rog.models import GpsLog
import logging
from django.db.models import F, Q
from django.conf import settings
import os
from urllib.parse import urljoin
logger = logging.getLogger(__name__)
"""
解説
この実装では以下の処理を行っています:
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(
entry=entry,
cp_number=cp_number
).first()
if existing_checkpoint:
logger.warning(f"Checkpoint {cp_number} already registered for team: {entry.team_name}")
return Response({
"status": "WARNING",
"message": "このチェックポイントは既に登録されています",
"checkpoint_id": existing_checkpoint.id
})
# トランザクション開始
with transaction.atomic():
# チェックポイント登録
checkpoint = GpsLog.objects.create(
entry=entry,
cp_number=cp_number,
image_address=image_address,
checkin_time=timezone.now()
)
logger.info(f"Successfully registered CP {cp_number} for team: {entry.team_name} "
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)
# イベントのチェックポイント情報を取得
checkpoints = Location2025.objects.filter(event=event).order_by('cp_number')
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,
"is_service_cp": cp.is_service_cp
}
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: チーム名
"""
logger.info("start_from_rogapp called")
# リクエストからパラメータを取得
event_code = request.data.get('event_code')
team_name = request.data.get('team_name')
logger.debug(f"Parameters: event_code={event_code}, team_name={team_name}")
# パラメータ検証
if not all([event_code, team_name]):
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,
team_name=team_name
).first()
if not entry:
logger.warning(f"Team not found: {team_name} in event: {event_code}")
return Response({
"status": "ERROR",
"message": "指定されたチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
# 既にスタート済みかチェック
if hasattr(entry, 'start_info'):
logger.warning(f"Team {team_name} already started at {entry.start_info.start_time}")
return Response({
"status": "WARNING",
"message": "このチームは既にスタートしています",
"start_time": entry.start_info.start_time.strftime("%Y-%m-%d %H:%M:%S")
})
# トランザクション開始
with transaction.atomic():
# スタート情報を登録
start_info = TeamStart.objects.create(
entry=entry,
start_time=timezone.now()
)
logger.info(f"Team {team_name} started at {start_info.start_time}")
return Response({
"status": "OK",
"message": "スタート処理が完了しました",
"team_name": team_name,
"event_code": event_code,
"start_time": start_info.start_time.strftime("%Y-%m-%d %H:%M:%S")
})
except Exception as e:
logger.error(f"Error in start_from_rogapp: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
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
- buy_flag: 購入フラグ (新規)
- gps_coordinates: GPS座標情報 (新規)
- camera_metadata: カメラメタデータ (新規)
"""
logger.info("checkin_from_rogapp called")
# リクエストからパラメータを取得
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')
# API変更要求書対応: 新パラメータ追加
buy_flag = request.data.get('buy_flag', False)
gps_coordinates = request.data.get('gps_coordinates', {})
camera_metadata = request.data.get('camera_metadata', {})
logger.debug(f"Parameters: event_code={event_code}, team_name={team_name}, "
f"cp_number={cp_number}, image={image_url}")
# パラメータ検証
if not all([event_code, team_name, 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,
team_name=team_name
).first()
if not entry:
logger.warning(f"Team not found: {team_name} in event: {event_code}")
return Response({
"status": "ERROR",
"message": "指定されたチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
# チームがスタートしているか確認
if not hasattr(entry, 'start_info'):
logger.warning(f"Team {team_name} has not started yet")
return Response({
"status": "ERROR",
"message": "このチームはまだスタートしていません。先にスタート処理を行ってください。"
}, status=status.HTTP_400_BAD_REQUEST)
# 既に同じCPを登録済みかチェック
existing_checkpoint = GpsLog.objects.filter(
entry=entry,
cp_number=cp_number
).first()
if existing_checkpoint:
logger.warning(f"Checkpoint {cp_number} already registered for team: {team_name}")
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(
event=event,
cp_number=cp_number
).first()
except:
logger.info(f"Location2025 model not available or CP {cp_number} not defined for event")
# トランザクション開始
with transaction.atomic():
# チェックポイント登録
checkpoint = GpsLog.objects.create(
entry=entry,
cp_number=cp_number,
image_address=image_url,
checkin_time=timezone.now(),
is_service_checked=event_cp.is_service_cp if event_cp else False
)
logger.info(f"Successfully registered CP {cp_number} for team: {team_name} in event: {event_code}")
# 獲得ポイントの計算イベントCPが定義されている場合
point_value = event_cp.cp_point if event_cp else 0
bonus_points = 0
scoring_breakdown = {
"base_points": point_value,
"camera_bonus": 0,
"total_points": point_value
}
# カメラボーナス計算
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
# 拡張情報があれば保存
if gps_coordinates or camera_metadata:
try:
from ..models import CheckinExtended
CheckinExtended.objects.create(
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
)
except Exception as ext_error:
logger.warning(f"Failed to save extended checkin info: {ext_error}")
return Response({
"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"),
"point_value": point_value,
"bonus_points": bonus_points,
"scoring_breakdown": scoring_breakdown,
"validation_status": "pending",
"requires_manual_review": bool(gps_coordinates.get('accuracy', 0) > 10) # 10m以上は要審査
})
except Exception as e:
logger.error(f"Error in checkin_from_rogapp: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.イベントコード、チーム名、画像URL、ゴール時間のパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントとチームの存在を確認します
4.チームがスタートしているかを確認します
- スタートしていない場合はエラーを返します
5.既にゴールしているかチェックします
- ゴール済みの場合は警告メッセージを返します
6.ゴール時間を処理します(提供されていない場合は現在時刻を使用)
7.チームのスコアを計算します
8.スコアボードを生成します(実際の生成ロジックは実装によって異なります)
9.ゴール情報を登録します
10.成功した場合、ゴール情報、スコア、スコアボードURLを含む成功メッセージを返します
スコアボードの生成部分は、実際のシステムの要件に合わせて詳細に実装する必要があります。
この例では、単純にPDFファイルのパスとURLを生成していますが、
実際にはPDF生成ライブラリReportLab、WeasyPrintなどを使用してスコアボードを生成する必要があります。
"""
@api_view(['POST'])
def goal_from_rogapp(request):
"""
アプリからゴール処理を実行し、スコアボードを生成
パラメータ:
- event_code: イベントコード
- team_name: チーム名
- image: 画像URL
- goal_time: ゴール時間
"""
logger.info("goal_from_rogapp called")
# リクエストからパラメータを取得
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')
logger.debug(f"Parameters: event_code={event_code}, team_name={team_name}, "
f"image={image_url}, goal_time={goal_time_str}")
# パラメータ検証
if not all([event_code, team_name]):
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,
team_name=team_name
).first()
if not entry:
logger.warning(f"Team not found: {team_name} in event: {event_code}")
return Response({
"status": "ERROR",
"message": "指定されたチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
# チームがスタートしているか確認
if not hasattr(entry, 'start_info'):
logger.warning(f"Team {team_name} has not started yet")
return Response({
"status": "ERROR",
"message": "このチームはまだスタートしていません。先にスタート処理を行ってください。"
}, status=status.HTTP_400_BAD_REQUEST)
# 既にゴールしているかチェック
if hasattr(entry, 'goal_info'):
logger.warning(f"Team {team_name} already reached goal at {entry.goal_info.goal_time}")
return Response({
"status": "WARNING",
"message": "このチームは既にゴールしています",
"goal_time": entry.goal_info.goal_time.strftime("%Y-%m-%d %H:%M:%S"),
"scoreboard_url": entry.goal_info.scoreboard_url
})
# ゴール時間の処理
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}"
# ゴール情報を登録
goal_info = TeamGoal.objects.create(
entry=entry,
goal_time=goal_time,
image_url=image_url,
score=score,
scoreboard_url=scoreboard_url
)
logger.info(f"Team {team_name} reached goal at {goal_time} with score {score}")
return Response({
"status": "OK",
"message": "ゴール処理が正常に完了しました",
"team_name": team_name,
"goal_time": goal_info.goal_time.strftime("%Y-%m-%d %H:%M:%S"),
"score": score,
"scoreboard_url": scoreboard_url
})
except Exception as e:
logger.error(f"Error in goal_from_rogapp: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def calculate_team_score(entry):
"""チームのスコアを計算する補助関数"""
# チームが通過したチェックポイントを取得
checkpoints = GpsLog.objects.filter(entry=entry)
total_score = 0
for cp in checkpoints:
# チェックポイントの得点を取得
cp_point = 0
try:
# Location2025
event_cp = Location2025.objects.filter(
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