705 lines
33 KiB
Python
Executable File
705 lines
33 KiB
Python
Executable File
# 既存のインポート部分に追加
|
||
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, TeamStart, TeamGoal
|
||
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
|
||
from django.db import transaction
|
||
from django.utils import timezone
|
||
from datetime import datetime
|
||
import uuid
|
||
import time
|
||
|
||
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_id=event.id).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.buy_point > 0 # buy_pointが0より大きい場合はサービスポイント
|
||
}
|
||
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: チーム名
|
||
"""
|
||
# リクエスト詳細情報を取得
|
||
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]
|
||
|
||
logger.info(f"[COMPETITION_START] 🏁 API Request Started - ID: {request_id}, Time: {request_time}, Client IP: {client_ip}, User: {user_info}")
|
||
|
||
# リクエストからパラメータを取得
|
||
event_code = request.data.get('event_code')
|
||
team_name = request.data.get('team_name')
|
||
|
||
logger.info(f"[COMPETITION_START] Request parameters - ID: {request_id}, event_code: '{event_code}', team_name: '{team_name}', user_agent: '{user_agent[:100]}'")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, team_name]):
|
||
logger.warning(f"[COMPETITION_START] ❌ Missing required parameters - ID: {request_id}, event_code: '{event_code}', team_name: '{team_name}', Client IP: {client_ip}")
|
||
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"[COMPETITION_START] ❌ Event not found - ID: {request_id}, event_code: '{event_code}', Client IP: {client_ip}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたイベントが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
logger.info(f"[COMPETITION_START] ✅ Event found - ID: {request_id}, Event ID: {event.id}, Name: '{event.event_name}', Start: {event.start_datetime}, End: {event.end_datetime}")
|
||
|
||
# チームの存在確認
|
||
entry = Entry.objects.filter(
|
||
event=event,
|
||
team_name=team_name
|
||
).first()
|
||
|
||
if not entry:
|
||
logger.warning(f"[COMPETITION_START] ❌ Team not found - ID: {request_id}, team_name: '{team_name}', event_code: '{event_code}', Client IP: {client_ip}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
logger.info(f"[COMPETITION_START] ✅ 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'}', Owner: '{entry.owner.email if entry.owner else 'N/A'}')")
|
||
|
||
# 既にスタート済みかチェック
|
||
if hasattr(entry, 'start_info'):
|
||
logger.warning(f"[COMPETITION_START] ⚠️ Team already started - ID: {request_id}, team_name: '{team_name}', zekken: {entry.zekken_number}, start_time: {entry.start_info.start_time}, Client IP: {client_ip}")
|
||
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()
|
||
)
|
||
|
||
# 統計情報取得
|
||
total_checkpoints = Location2025.objects.filter(event=event).count() if hasattr(Location2025, 'event') else 0
|
||
|
||
logger.info(f"[COMPETITION_START] 🎉 SUCCESS - ID: {request_id}, Team: '{team_name}', Zekken: {entry.zekken_number}, Event: '{event_code}', Start Time: {start_info.start_time}, Total CPs Available: {total_checkpoints}, Client IP: {client_ip}, User: {user_info}")
|
||
|
||
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"[COMPETITION_START] 💥 CRITICAL ERROR - ID: {request_id}, team_name: '{team_name}', event_code: '{event_code}', Client IP: {client_ip}, Error: {str(e)}", exc_info=True)
|
||
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: カメラメタデータ (新規)
|
||
"""
|
||
# リクエスト詳細情報を取得
|
||
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]
|
||
|
||
logger.info(f"[CHECKIN] 📍 API Request Started - ID: {request_id}, Time: {request_time}, Client IP: {client_ip}, User: {user_info}")
|
||
|
||
# リクエストからパラメータを取得
|
||
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.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')}")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, team_name, cp_number]):
|
||
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}")
|
||
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"[CHECKIN] ❌ Event not found - ID: {request_id}, event_code: '{event_code}', Client IP: {client_ip}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたイベントが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
logger.info(f"[CHECKIN] ✅ Event found - ID: {request_id}, Event ID: {event.id}, Name: '{event.event_name}'")
|
||
|
||
# チームの存在確認
|
||
entry = Entry.objects.filter(
|
||
event=event,
|
||
team_name=team_name
|
||
).first()
|
||
|
||
if not entry:
|
||
logger.warning(f"[CHECKIN] ❌ Team not found - ID: {request_id}, team_name: '{team_name}', event_code: '{event_code}', Client IP: {client_ip}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
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'}'")
|
||
|
||
# チームがスタートしているか確認
|
||
if not hasattr(entry, 'start_info'):
|
||
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}")
|
||
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"[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}")
|
||
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()
|
||
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}")
|
||
|
||
# トランザクション開始
|
||
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
|
||
)
|
||
|
||
# 獲得ポイントの計算(イベント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
|
||
|
||
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 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"[CHECKIN] ❌ ERROR - team_name: {team_name}, event_code: {event_code}, cp_number: {cp_number}, Client IP: {client_ip}, Error: {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: ゴール時間
|
||
"""
|
||
# ログ用のリクエスト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"
|
||
|
||
# リクエストからパラメータを取得
|
||
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.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]}")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, team_name]):
|
||
logger.error(f"[GOAL] ❌ Missing required parameters - ID: {request_id}, event_code: '{event_code}', team_name: '{team_name}', Client IP: {client_ip}")
|
||
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.error(f"[GOAL] ❌ Event not found - ID: {request_id}, event_code: '{event_code}', Client IP: {client_ip}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたイベントが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
logger.info(f"[GOAL] ✅ Event found - ID: {request_id}, event: '{event.event_name}', event_id: {event.id}")
|
||
|
||
# チームの存在確認
|
||
entry = Entry.objects.filter(
|
||
event=event,
|
||
team_name=team_name
|
||
).first()
|
||
|
||
if not entry:
|
||
logger.error(f"[GOAL] ❌ Team not found - ID: {request_id}, team_name: '{team_name}', event_code: '{event_code}', Client IP: {client_ip}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
logger.info(f"[GOAL] ✅ Team found - ID: {request_id}, team_name: '{team_name}', zekken: {entry.zekken_number}, entry_id: {entry.id}")
|
||
|
||
# チームがスタートしているか確認
|
||
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"[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}")
|
||
|
||
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"[GOAL] ❌ ERROR - team_name: {team_name}, event_code: {event_code}, Client IP: {client_ip}, Error: {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
|
||
|