1553 lines
68 KiB
Python
Executable File
1553 lines
68 KiB
Python
Executable File
# 既存のインポート部分に追加
|
||
from datetime import datetime
|
||
from django.utils import timezone
|
||
# from sqlalchemy import Transaction # 削除 - SQLAlchemy 2.0では利用不可
|
||
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, GpsCheckin
|
||
import logging
|
||
import uuid
|
||
import os
|
||
import json
|
||
from django.db.models import F, Q
|
||
from django.db import transaction
|
||
from django.conf import settings
|
||
import os
|
||
from urllib.parse import urljoin
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
def get_next_serial_number():
|
||
"""次のserial_numberを取得"""
|
||
from django.db.models import Max
|
||
max_serial = GpsLog.objects.aggregate(Max('serial_number'))['serial_number__max']
|
||
return (max_serial or 0) + 1
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.イベントコード、チーム名、チェックポイント番号のパラメータを受け取ります
|
||
2.パラメータが不足している場合はエラーを返します
|
||
3.指定されたイベントとチームの存在を確認します
|
||
4.指定されたチェックポイント記録を検索します
|
||
- 該当する記録が存在しない場合はエラーを返します
|
||
5.チェックポイント記録を削除します
|
||
6.成功した場合、削除情報を含む成功メッセージを返します
|
||
|
||
このエンドポイントにより、ロゲイニングアプリから誤って登録したチェックポイント記録を削除することができます。
|
||
これは、例えば通過していないチェックポイントを誤って登録してしまった場合などに役立ちます。
|
||
"""
|
||
|
||
@api_view(['POST'])
|
||
def remove_checkin_from_rogapp(request):
|
||
"""
|
||
アプリからチェックイン記録を削除
|
||
|
||
パラメータ:
|
||
- event_code: イベントコード
|
||
- team_name: チーム名
|
||
- cp_number: チェックポイント番号
|
||
"""
|
||
# ログ用のリクエストID生成
|
||
request_id = uuid.uuid4().hex[:8]
|
||
request_time = timezone.now()
|
||
client_ip = request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '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')
|
||
cp_number = request.data.get('cp_number')
|
||
|
||
logger.info(f"[REMOVE_CHECKIN] 🗑️ API call started - ID: {request_id}, event_code: '{event_code}', team_name: '{team_name}', cp_number: {cp_number}, Client IP: {client_ip}, User: {user_info}")
|
||
|
||
logger.debug(f"Parameters: event_code={event_code}, team_name={team_name}, cp_number={cp_number}")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, team_name, cp_number]):
|
||
logger.error(f"[REMOVE_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.error(f"[REMOVE_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"[REMOVE_CHECKIN] ✅ Event found - ID: {request_id}, event: '{event.event_name}', event_id: {event.id}")
|
||
|
||
# チームの存在確認
|
||
entry = Entry.objects.filter(
|
||
event=event,
|
||
team__team_name=team_name
|
||
).first()
|
||
|
||
if not entry:
|
||
logger.error(f"[REMOVE_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"[REMOVE_CHECKIN] ✅ Team found - ID: {request_id}, team_name: '{team_name}', zekken: {entry.team.zekken_number}, entry_id: {entry.id}")
|
||
|
||
# 対象のチェックポイント記録を検索
|
||
checkpoint = GpsLog.objects.filter(
|
||
zekken_number=entry.team.zekken_number,
|
||
event_code=event_code,
|
||
cp_number=cp_number
|
||
).first()
|
||
|
||
if not checkpoint:
|
||
logger.warning(f"[REMOVE_CHECKIN] ⚠️ Checkpoint not found - ID: {request_id}, team_name: '{team_name}', zekken: {entry.team.zekken_number}, cp_number: {cp_number}, Client IP: {client_ip}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたチェックポイント記録が見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
logger.info(f"[REMOVE_CHECKIN] ✅ Checkpoint found for removal - ID: {request_id}, checkpoint_id: {checkpoint.id}, checkin_time: {checkpoint.checkin_time}, has_image: {bool(checkpoint.image_address)}")
|
||
|
||
# チェックポイント記録を削除
|
||
checkin_time = checkpoint.checkin_time
|
||
checkpoint_id = checkpoint.id
|
||
image_address = checkpoint.image_address
|
||
|
||
with transaction.atomic():
|
||
# 拡張情報も一緒に削除(存在する場合)
|
||
try:
|
||
from ..models import CheckinExtended
|
||
extended_info = CheckinExtended.objects.filter(gpslog=checkpoint).first()
|
||
if extended_info:
|
||
extended_info.delete()
|
||
logger.info(f"[REMOVE_CHECKIN] ✅ Extended info also removed - ID: {request_id}, checkpoint_id: {checkpoint_id}")
|
||
except Exception as ext_error:
|
||
logger.warning(f"[REMOVE_CHECKIN] ⚠️ Failed to remove extended info - ID: {request_id}, Error: {ext_error}")
|
||
|
||
checkpoint.delete()
|
||
|
||
logger.success(f"[REMOVE_CHECKIN] 🎉 Successfully removed checkpoint - ID: {request_id}, team_name: '{team_name}', zekken: {entry.team.zekken_number}, cp_number: {cp_number}, checkpoint_id: {checkpoint_id}, had_image: {bool(image_address)}, Client IP: {client_ip}")
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"message": "チェックポイント記録が正常に削除されました",
|
||
"team_name": team_name,
|
||
"cp_number": cp_number,
|
||
"checkpoint_id": checkpoint_id,
|
||
"checkin_time": checkin_time.strftime("%Y-%m-%d %H:%M:%S") if checkin_time else None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"[REMOVE_CHECKIN] 💥 ERROR - ID: {request_id}, team_name: '{team_name}', cp_number: {cp_number}, Client IP: {client_ip}, Error: {str(e)}", exc_info=True)
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.イベントコードとゼッケン番号のパラメータを受け取ります
|
||
2.パラメータが不足している場合はエラーを返します
|
||
3.指定されたイベントとゼッケン番号のチームの存在を確認します
|
||
- 存在しない場合はエラーを返します
|
||
4.チームが既にスタート済みかどうかをチェックします
|
||
- 既にスタート済みの場合は警告メッセージを返します
|
||
5.スタート情報を登録します
|
||
6.成功した場合、チーム情報とスタート時間を含む成功メッセージを返します
|
||
|
||
このエンドポイントは先に実装した /start_from_rogapp と似ていますが、以下の点が異なります:
|
||
|
||
- HTTPメソッドがPOSTではなくGETを使用
|
||
- チーム名ではなくゼッケン番号でチームを特定
|
||
- パラメータ名が異なる (event_code → event, team_name → zekken)
|
||
|
||
これにより、管理画面からチームのスタート処理を行うことができるようになります。
|
||
"""
|
||
|
||
@api_view(['GET'])
|
||
def start_checkin(request):
|
||
"""
|
||
管理画面からスタート処理を実行
|
||
|
||
パラメータ:
|
||
- event: イベントコード
|
||
- zekken: ゼッケン番号
|
||
"""
|
||
# リクエスト詳細情報を取得
|
||
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'
|
||
|
||
logger.info(f"[ADMIN_START] API called - Client IP: {client_ip}, User: {user_info}")
|
||
|
||
# リクエストからパラメータを取得
|
||
event_code = request.query_params.get('event')
|
||
zekken_number = request.query_params.get('zekken')
|
||
|
||
logger.info(f"[ADMIN_START] Request parameters - event_code: {event_code}, zekken_number: {zekken_number}, user_agent: {user_agent[:100]}")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, zekken_number]):
|
||
logger.warning(f"[ADMIN_START] Missing required parameters - event_code: {event_code}, zekken_number: {zekken_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"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 with zekken number {zekken_number} not found in event: {event_code}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 既にスタート済みかチェック(GpsLogでSTARTレコードを確認)
|
||
existing_start = GpsLog.objects.filter(
|
||
zekken_number=zekken_number,
|
||
event_code=event_code,
|
||
cp_number="START",
|
||
serial_number=0
|
||
).first()
|
||
|
||
if existing_start:
|
||
logger.warning(f"Team {entry.team.team_name} (zekken: {zekken_number}) already started at {existing_start.checkin_time}")
|
||
return Response({
|
||
"status": "WARNING",
|
||
"message": "このチームは既にスタートしています",
|
||
"start_time": existing_start.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||
"team_name": entry.team.team_name
|
||
})
|
||
|
||
# トランザクション開始
|
||
with transaction.atomic():
|
||
# スタート情報をGpsLogとして登録
|
||
start_info = GpsLog.objects.create(
|
||
zekken_number=zekken_number,
|
||
event_code=event_code,
|
||
cp_number="START",
|
||
serial_number=0,
|
||
checkin_time=timezone.now()
|
||
)
|
||
|
||
logger.info(f"Team {entry.team.team_name} (zekken: {zekken_number}) started at {start_info.checkin_time}")
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"message": "スタート処理が完了しました",
|
||
"team_name": entry.team.team_name,
|
||
"zekken_number": zekken_number,
|
||
"event_code": event_code,
|
||
"start_time": start_info.checkin_time.strftime("%Y-%m-%d %H:%M:%S")
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in start_checkin: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.イベントコード、ゼッケン番号、カンマ区切りのチェックポイント番号リストのパラメータを受け取ります
|
||
2.パラメータが不足している場合はエラーを返します
|
||
3.指定されたイベントとチームの存在を確認します
|
||
4.チームがスタートしていない場合は、自動的にスタート情報を作成します(管理画面からの操作のため)
|
||
5.チェックポイントリストを解析し、各チェックポイントを処理します:
|
||
- 既に登録済みのチェックポイントはスキップし、重複リストに追加
|
||
- 新しいチェックポイントは登録し、成功リストに追加
|
||
6.処理結果(登録済み、重複、成功/失敗)を返します
|
||
|
||
この実装はGETリクエストを使用していますが、データベースを変更する操作であるため、通常はPOSTリクエストが推奨されます。
|
||
ただし、仕様に従ってGETメソッドを使用しています。
|
||
"""
|
||
@api_view(['GET'])
|
||
def add_checkin(request):
|
||
"""
|
||
管理画面から複数チェックポイントを一括登録
|
||
|
||
パラメータ:
|
||
- event: イベントコード
|
||
- zekken: ゼッケン番号
|
||
- list: カンマ区切りのチェックポイント番号リスト
|
||
"""
|
||
# ログ用のリクエストID生成
|
||
request_id = uuid.uuid4().hex[:8]
|
||
request_time = timezone.now()
|
||
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.query_params.get('event')
|
||
zekken_number = request.query_params.get('zekken')
|
||
cp_list_string = request.query_params.get('list')
|
||
|
||
logger.info(f"[ADMIN_CHECKIN] 📝 Bulk checkin API call started - ID: {request_id}, event_code: '{event_code}', zekken_number: {zekken_number}, cp_list: '{cp_list_string}', Client IP: {client_ip}, User: {user_info}, User-Agent: {user_agent[:100]}")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, zekken_number, cp_list_string]):
|
||
logger.error(f"[ADMIN_CHECKIN] ❌ Missing required parameters - ID: {request_id}, event_code: '{event_code}', zekken_number: {zekken_number}, cp_list: '{cp_list_string}', 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"[ADMIN_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"[ADMIN_CHECKIN] ✅ Event found - ID: {request_id}, event: '{event.event_name}', event_id: {event.id}")
|
||
|
||
# チームの存在確認
|
||
entry = Entry.objects.filter(
|
||
event=event,
|
||
zekken_number=zekken_number
|
||
).first()
|
||
|
||
if not entry:
|
||
logger.error(f"[ADMIN_CHECKIN] ❌ Team not found - ID: {request_id}, zekken: {zekken_number}, event_code: '{event_code}', Client IP: {client_ip}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
logger.info(f"[ADMIN_CHECKIN] ✅ Team found - ID: {request_id}, team_name: '{entry.team.team_name}', zekken: {zekken_number}, entry_id: {entry.id}")
|
||
|
||
# チームがスタートしているか確認(オプション)
|
||
start_record = GpsLog.objects.filter(
|
||
zekken_number=zekken_number,
|
||
event_code=event_code,
|
||
cp_number="START",
|
||
serial_number=0
|
||
).first()
|
||
|
||
if not start_record:
|
||
# スタート情報がない場合は自動的にスタートさせる
|
||
# 注意: 管理画面からの操作なので、自動スタートを許可
|
||
GpsLog.objects.create(
|
||
serial_number=get_next_serial_number(),
|
||
zekken_number=zekken_number,
|
||
event_code=event_code,
|
||
cp_number="START",
|
||
checkin_time=timezone.now()
|
||
)
|
||
logger.info(f"[ADMIN_CHECKIN] ✅ Auto-started team - ID: {request_id}, team_name: '{entry.team.team_name}', zekken: {zekken_number}")
|
||
|
||
# チェックポイントリストを解析
|
||
cp_list = [cp.strip() for cp in cp_list_string.split(',') if cp.strip()]
|
||
|
||
if not cp_list:
|
||
logger.warning("Empty checkpoint list")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "チェックポイントリストが空です"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 登録結果を格納する辞書
|
||
results = {
|
||
"success": [],
|
||
"duplicate": []
|
||
}
|
||
|
||
# トランザクション開始
|
||
with transaction.atomic():
|
||
for cp_number in cp_list:
|
||
# 既に同じCPを登録済みかチェック
|
||
existing_checkpoint = GpsLog.objects.filter(
|
||
zekken_number=zekken_number,
|
||
event_code=event_code,
|
||
cp_number=cp_number
|
||
).first()
|
||
|
||
if existing_checkpoint:
|
||
logger.warning(f"Checkpoint {cp_number} already registered for team: {entry.team_name}")
|
||
results["duplicate"].append(cp_number)
|
||
continue
|
||
|
||
# イベントのチェックポイント定義を確認(必要に応じて)
|
||
event_cp = None
|
||
try:
|
||
event_cp = Location2025.objects.filter(
|
||
event_id=event.id,
|
||
cp_number=cp_number
|
||
).first()
|
||
except:
|
||
pass
|
||
|
||
# チェックポイント登録
|
||
checkpoint = GpsLog.objects.create(
|
||
serial_number=get_next_serial_number(),
|
||
zekken_number=zekken_number,
|
||
event_code=event_code,
|
||
cp_number=cp_number,
|
||
checkin_time=timezone.now()
|
||
)
|
||
|
||
logger.info(f"Successfully registered CP {cp_number} for team: {entry.team_name} (zekken: {zekken_number})")
|
||
results["success"].append(cp_number)
|
||
|
||
if not results["success"] and results["duplicate"]:
|
||
# 全てのチェックポイントが既に登録済みの場合
|
||
return Response({
|
||
"status": "WARNING",
|
||
"message": "指定されたチェックポイントは全て既に登録されています",
|
||
"team_name": entry.team_name,
|
||
"duplicate_checkpoints": results["duplicate"]
|
||
})
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"message": "チェックポイントが正常に登録されました",
|
||
"team_name": entry.team_name,
|
||
"registered_checkpoints": results["success"],
|
||
"duplicate_checkpoints": results["duplicate"],
|
||
"total_registered": len(results["success"]),
|
||
"total_duplicate": len(results["duplicate"])
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in add_checkin: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.ゼッケン番号、イベントコード、シリアル番号(チェックポイント記録のID)のパラメータを受け取ります
|
||
2.パラメータが不足している場合はエラーを返します
|
||
3.シリアル番号が整数に変換できることを確認します
|
||
4.指定されたイベントとチームの存在を確認します
|
||
5.指定されたシリアル番号のチェックポイント記録を検索します
|
||
- 該当する記録が存在しない場合はエラーを返します
|
||
6.チェックポイント記録を削除します
|
||
7.成功した場合、削除された記録の情報を含む成功メッセージを返します
|
||
|
||
この実装では、シリアル番号を GpsLog モデルの id フィールドとして扱っています。
|
||
実際のシステムでは別のフィールドを使用している可能性もあるため、必要に応じて調整してください。
|
||
"""
|
||
@api_view(['GET'])
|
||
def delete_checkin(request):
|
||
"""
|
||
チェックイン記録を削除
|
||
|
||
パラメータ:
|
||
- zekken: ゼッケン番号
|
||
- event_code: イベントコード
|
||
- sn: シリアル番号
|
||
"""
|
||
logger.info("delete_checkin called")
|
||
|
||
# リクエストからパラメータを取得
|
||
zekken_number = request.query_params.get('zekken')
|
||
event_code = request.query_params.get('event_code')
|
||
serial_number = request.query_params.get('sn')
|
||
|
||
logger.debug(f"Parameters: zekken={zekken_number}, event_code={event_code}, sn={serial_number}")
|
||
|
||
# パラメータ検証
|
||
if not all([zekken_number, event_code, serial_number]):
|
||
logger.warning("Missing required parameters")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "ゼッケン番号、イベントコード、シリアル番号が必要です"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# シリアル番号を整数に変換
|
||
try:
|
||
serial_number = int(serial_number)
|
||
except ValueError:
|
||
logger.warning(f"Invalid serial number format: {serial_number}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "シリアル番号は整数である必要があります"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# イベントの存在確認
|
||
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 with zekken number {zekken_number} not found in event: {event_code}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 対象のチェックポイント記録を検索
|
||
checkpoint = GpsLog.objects.filter(
|
||
id=serial_number,
|
||
entry=entry
|
||
).first()
|
||
|
||
if not checkpoint:
|
||
logger.warning(f"Checkpoint with ID {serial_number} not found for team: {entry.team_name}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたシリアル番号のチェックポイント記録が見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# チェックポイント情報を保存
|
||
cp_number = checkpoint.cp_number
|
||
checkin_time = checkpoint.checkin_time
|
||
|
||
# チェックポイント記録を削除
|
||
checkpoint.delete()
|
||
|
||
logger.info(f"Successfully deleted checkpoint with ID {serial_number} (CP {cp_number}) "
|
||
f"for team: {entry.team_name} (zekken: {zekken_number})")
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"message": "チェックポイント記録が正常に削除されました",
|
||
"team_name": entry.team_name,
|
||
"zekken_number": zekken_number,
|
||
"cp_number": cp_number,
|
||
"serial_number": serial_number,
|
||
"checkin_time": checkin_time.strftime("%Y-%m-%d %H:%M:%S") if checkin_time else None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in delete_checkin: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.必要なパラメータ(ゼッケン番号、イベントコード、移動元シリアル番号、移動先シリアル番号)を受け取ります
|
||
2.パラメータが不足している場合はエラーを返します
|
||
3.シリアル番号を整数に変換できるか確認します
|
||
4.指定されたイベントとチームの存在を確認します
|
||
5.指定されたシリアル番号の両方のチェックポイント記録を検索します
|
||
- どちらかが存在しない場合はエラーを返します
|
||
6.移動処理を実行します:
|
||
- この実装では、チェックポイントの順序はcheckin_time(登録時間)によって決まると仮定
|
||
- 二つのチェックポイント記録のチェックイン時間を入れ替えることで順序を変更
|
||
7.成功した場合、変更情報を含む成功メッセージを返します
|
||
|
||
注意点:この実装では、チェックポイントの順序はcheckin_timeフィールドによって決まると仮定しています。
|
||
もし実際のシステムで別のフィールド(例:orderやsequenceなど)を使って順序を管理している場合は、
|
||
そのフィールドを更新するよう実装を調整してください。
|
||
"""
|
||
@api_view(['GET'])
|
||
def move_checkin(request):
|
||
"""
|
||
チェックイン記録を移動(順序変更)
|
||
|
||
パラメータ:
|
||
- zekken: ゼッケン番号
|
||
- event_code: イベントコード
|
||
- old_sn: 移動元シリアル番号
|
||
- new_sn: 移動先シリアル番号
|
||
"""
|
||
logger.info("move_checkin called")
|
||
|
||
# リクエストからパラメータを取得
|
||
zekken_number = request.query_params.get('zekken')
|
||
event_code = request.query_params.get('event_code')
|
||
old_sn = request.query_params.get('old_sn')
|
||
new_sn = request.query_params.get('new_sn')
|
||
|
||
logger.debug(f"Parameters: zekken={zekken_number}, event_code={event_code}, "
|
||
f"old_sn={old_sn}, new_sn={new_sn}")
|
||
|
||
# パラメータ検証
|
||
if not all([zekken_number, event_code, old_sn, new_sn]):
|
||
logger.warning("Missing required parameters")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "ゼッケン番号、イベントコード、移動元シリアル番号、移動先シリアル番号が必要です"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# シリアル番号を整数に変換
|
||
try:
|
||
old_sn = int(old_sn)
|
||
new_sn = int(new_sn)
|
||
except ValueError:
|
||
logger.warning(f"Invalid serial number format")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "シリアル番号は整数である必要があります"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# イベントの存在確認
|
||
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 with zekken number {zekken_number} not found in event: {event_code}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 対象のチェックポイント記録を検索
|
||
old_checkpoint = GpsLog.objects.filter(
|
||
id=old_sn,
|
||
entry=entry
|
||
).first()
|
||
|
||
new_checkpoint = GpsLog.objects.filter(
|
||
id=new_sn,
|
||
entry=entry
|
||
).first()
|
||
|
||
if not old_checkpoint:
|
||
logger.warning(f"Checkpoint with ID {old_sn} not found for team: {entry.team_name}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "移動元のチェックポイント記録が見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
if not new_checkpoint:
|
||
logger.warning(f"Checkpoint with ID {new_sn} not found for team: {entry.team_name}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "移動先のチェックポイント記録が見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# トランザクション開始
|
||
with transaction.atomic():
|
||
# 移動前の情報を保存
|
||
old_cp_number = old_checkpoint.cp_number
|
||
old_cp_time = old_checkpoint.checkin_time
|
||
old_cp_image = old_checkpoint.image_address
|
||
|
||
new_cp_number = new_checkpoint.cp_number
|
||
new_cp_time = new_checkpoint.checkin_time
|
||
new_cp_image = new_checkpoint.image_address
|
||
|
||
# チェックイン順序の入れ替え(チェックイン時間を入れ替えることで順序を変更)
|
||
old_checkpoint.checkin_time = new_cp_time
|
||
old_checkpoint.save()
|
||
|
||
new_checkpoint.checkin_time = old_cp_time
|
||
new_checkpoint.save()
|
||
|
||
logger.info(f"Successfully moved checkpoint {old_cp_number} (ID: {old_sn}) "
|
||
f"to position of checkpoint {new_cp_number} (ID: {new_sn}) "
|
||
f"for team: {entry.team_name} (zekken: {zekken_number})")
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"message": "チェックポイント記録の順序が正常に変更されました",
|
||
"team_name": entry.team_name,
|
||
"zekken_number": zekken_number,
|
||
"old_checkpoint": {
|
||
"id": old_sn,
|
||
"cp_number": old_cp_number,
|
||
"new_time": old_checkpoint.checkin_time.strftime("%Y-%m-%d %H:%M:%S")
|
||
},
|
||
"new_checkpoint": {
|
||
"id": new_sn,
|
||
"cp_number": new_cp_number,
|
||
"new_time": new_checkpoint.checkin_time.strftime("%Y-%m-%d %H:%M:%S")
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in move_checkin: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.イベントコード、ゼッケン番号、ゴール時間のパラメータを受け取ります
|
||
2.パラメータが不足している場合はエラーを返します
|
||
3.指定されたイベントとチームの存在を確認します
|
||
4.チームがスタートしていない場合は、管理画面からの操作のため自動的にスタート情報を作成します
|
||
5.既にゴールしているかチェックします
|
||
- ゴール済みの場合は警告メッセージを返します
|
||
6.ゴール時間を処理します(提供されていない場合は現在時刻を使用)
|
||
- 複数の日付形式に対応するため、異なるフォーマットを試みます
|
||
7.スコアを計算します
|
||
8.スコアボードを生成します
|
||
9.ゴール情報を登録します
|
||
10.成功した場合、ゴール情報、スコア、スコアボードURLを含む成功メッセージを返します
|
||
|
||
この実装は /goal_from_rogapp エンドポイントと似ていますが、主な違いは:
|
||
|
||
- HTTPメソッドがPOSTではなくGETを使用
|
||
- チーム名ではなくゼッケン番号でチームを特定
|
||
- パラメータ名が異なる (event_code → event)
|
||
- チームがスタートしていない場合に自動的にスタート情報を作成
|
||
|
||
これにより、管理画面からチームのゴール処理を行うことができるようになります。
|
||
"""
|
||
@api_view(['GET'])
|
||
def goal_checkin(request):
|
||
"""
|
||
管理画面からゴール処理を実行
|
||
|
||
パラメータ:
|
||
- event: イベントコード
|
||
- zekken: ゼッケン番号
|
||
- goal_time: ゴール時間
|
||
"""
|
||
logger.info("goal_checkin called")
|
||
|
||
# リクエストからパラメータを取得
|
||
event_code = request.query_params.get('event')
|
||
zekken_number = request.query_params.get('zekken')
|
||
goal_time_str = request.query_params.get('goal_time')
|
||
|
||
logger.debug(f"Parameters: event={event_code}, zekken={zekken_number}, goal_time={goal_time_str}")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, 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"Team with zekken number {zekken_number} not found in event: {event_code}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# チームがスタートしているか確認(GpsLogでSTARTレコードを確認)
|
||
start_record = GpsLog.objects.filter(
|
||
entry=entry,
|
||
cp_number="START",
|
||
serial_number=0
|
||
).first()
|
||
|
||
if not start_record:
|
||
# 管理画面からの操作なので、自動的にスタートさせる
|
||
GpsLog.objects.create(
|
||
entry=entry,
|
||
cp_number="START",
|
||
serial_number=0,
|
||
latitude=0.0,
|
||
longitude=0.0,
|
||
checkin_time=timezone.now(),
|
||
extra_data={"auto_start": True, "source": "admin_goal"}
|
||
)
|
||
logger.info(f"Auto-started team {entry.team_name} (zekken: {zekken_number})")
|
||
|
||
# 既にゴールしているかチェック(GpsLogでGOALレコードを確認)
|
||
existing_goal = GpsLog.objects.filter(
|
||
entry=entry,
|
||
cp_number="GOAL",
|
||
serial_number=9999
|
||
).first()
|
||
|
||
if existing_goal:
|
||
logger.warning(f"Team {entry.team_name} (zekken: {zekken_number}) already reached goal at {existing_goal.checkin_time}")
|
||
return Response({
|
||
"status": "WARNING",
|
||
"message": "このチームは既にゴールしています",
|
||
"goal_time": existing_goal.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||
"team_name": entry.team_name,
|
||
"scoreboard_url": existing_goal.extra_data.get('scoreboard_url', '') if existing_goal.extra_data else ''
|
||
})
|
||
|
||
# ゴール時間の処理
|
||
if goal_time_str:
|
||
try:
|
||
goal_time = datetime.strptime(goal_time_str, "%Y-%m-%d %H:%M:%S")
|
||
except ValueError:
|
||
try:
|
||
# 別の形式を試す(例:YYYY/MM/DD HH:MM:SS)
|
||
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}"
|
||
|
||
# ゴール情報をGpsLogとして登録
|
||
goal_info = GpsLog.objects.create(
|
||
entry=entry,
|
||
cp_number="GOAL",
|
||
serial_number=9999,
|
||
latitude=0.0,
|
||
longitude=0.0,
|
||
checkin_time=goal_time,
|
||
extra_data={
|
||
"score": score,
|
||
"scoreboard_url": scoreboard_url
|
||
}
|
||
)
|
||
|
||
logger.info(f"Team {entry.team_name} (zekken: {zekken_number}) reached goal at {goal_time} with score {score}")
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"message": "ゴール処理が正常に完了しました",
|
||
"team_name": entry.team_name,
|
||
"zekken_number": zekken_number,
|
||
"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_checkin: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 既に定義されている場合は省略可能
|
||
def calculate_team_score(entry):
|
||
"""チームのスコアを計算する補助関数"""
|
||
# チームが通過したチェックポイントを取得
|
||
checkpoints = GpsLog.objects.filter(
|
||
zekken_number=entry.team.zekken_number,
|
||
event_code=entry.event.event_code
|
||
)
|
||
|
||
total_score = 0
|
||
|
||
for cp in checkpoints:
|
||
# チェックポイントの得点を取得
|
||
cp_point = 0
|
||
try:
|
||
# Location2025 モデルが存在する場合はそこから得点を取得
|
||
event_cp = Location2025.objects.filter(
|
||
event_id=entry.event.id,
|
||
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
|
||
|
||
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.イベントコード、ゼッケン番号、新しいゴール時間のパラメータを受け取ります
|
||
2.パラメータが不足している場合はエラーを返します
|
||
3.指定されたイベントとチームの存在を確認します
|
||
4.チームがゴール済みであることを確認します
|
||
- ゴールしていない場合はエラーを返します
|
||
5.新しいゴール時間の形式を検証し、パースします
|
||
- 複数の日付形式に対応するため、いくつかのフォーマットを試みます
|
||
6.ゴール時間を更新します
|
||
7.成功した場合、古いゴール時間と新しいゴール時間を含む成功メッセージを返します
|
||
|
||
このエンドポイントは、イベント管理者が管理画面からチームのゴール時間を修正する場合に役立ちます。
|
||
例えば、手動でゴール時間を記録していたが、後から正確な時間に修正する必要がある場合などに使用できます。
|
||
"""
|
||
@api_view(['GET'])
|
||
def change_goal_time_checkin(request):
|
||
"""
|
||
ゴール時間を変更
|
||
|
||
パラメータ:
|
||
- event: イベントコード
|
||
- zekken: ゼッケン番号
|
||
- goal_time: 新しいゴール時間
|
||
"""
|
||
logger.info("change_goal_time_checkin called")
|
||
|
||
# リクエストからパラメータを取得
|
||
event_code = request.query_params.get('event')
|
||
zekken_number = request.query_params.get('zekken')
|
||
goal_time_str = request.query_params.get('goal_time')
|
||
|
||
logger.debug(f"Parameters: event={event_code}, zekken={zekken_number}, goal_time={goal_time_str}")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, zekken_number, goal_time_str]):
|
||
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 with zekken number {zekken_number} not found in event: {event_code}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# チームがゴールしているか確認
|
||
if not hasattr(entry, 'goal_info'):
|
||
logger.warning(f"Team {entry.team_name} (zekken: {zekken_number}) has not reached goal yet")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "このチームはまだゴールしていません"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ゴール時間の解析
|
||
try:
|
||
# 複数の日付形式に対応
|
||
goal_time = None
|
||
for date_format in ["%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"]:
|
||
try:
|
||
goal_time = datetime.strptime(goal_time_str, date_format)
|
||
break
|
||
except ValueError:
|
||
continue
|
||
|
||
if goal_time is None:
|
||
raise ValueError("No matching date format found")
|
||
|
||
except ValueError:
|
||
logger.warning(f"Invalid goal_time format: {goal_time_str}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "ゴール時間の形式が無効です。YYYY-MM-DD HH:MM:SS形式を使用してください"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# トランザクション開始
|
||
with transaction.atomic():
|
||
# 古いゴール時間を保存
|
||
old_goal_time = entry.goal_info.goal_time
|
||
|
||
# ゴール時間を更新
|
||
entry.goal_info.goal_time = goal_time
|
||
entry.goal_info.save()
|
||
|
||
logger.info(f"Goal time for team {entry.team_name} (zekken: {zekken_number}) "
|
||
f"changed from {old_goal_time} to {goal_time}")
|
||
|
||
# スコアボードの再生成が必要かもしれない
|
||
# 実際の実装では、必要に応じてスコアボードを再生成するロジックを追加
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"message": "ゴール時間が正常に更新されました",
|
||
"team_name": entry.team_name,
|
||
"zekken_number": zekken_number,
|
||
"old_goal_time": old_goal_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||
"new_goal_time": goal_time.strftime("%Y-%m-%d %H:%M:%S")
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in change_goal_time_checkin: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.ゼッケン番号とイベントコードのパラメータを受け取ります
|
||
2.パラメータが不足している場合はエラーを返します
|
||
3.指定されたイベントとチームの存在を確認します
|
||
4.チームのチェックイン記録を取得します(チェックイン時間順にソート)
|
||
5.スタート情報とゴール情報も取得します(存在する場合)
|
||
6.チェックポイント記録をシリアライズし、関連情報を付加します
|
||
- チェックポイントのID、番号、チェックイン時間、画像URL
|
||
- サービスチェック情報
|
||
- チェックポイントの得点情報( Location モデルがある場合)
|
||
7.チーム情報、スタート情報、ゴール情報、チェックポイントリストを含む応答を返します
|
||
|
||
このエンドポイントは、管理画面や詳細表示画面でチームのチェックイン履歴を表示するために使用できます。
|
||
データは時系列順に並べられ、各チェックポイントの詳細情報も含まれています。
|
||
"""
|
||
|
||
|
||
@api_view(['GET'])
|
||
def get_checkin_list(request):
|
||
"""
|
||
指定チームのチェックイン記録を取得
|
||
|
||
パラメータ:
|
||
- zekken: ゼッケン番号
|
||
- event: イベントコード
|
||
"""
|
||
logger.info("get_checkin_list called")
|
||
|
||
# リクエストからパラメータを取得(GET/POSTの両方に対応)
|
||
# 両方のパラメータ名に対応
|
||
if request.method == 'GET':
|
||
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_number') or data.get('zekken')
|
||
event_code = data.get('event_code') or data.get('event')
|
||
except:
|
||
data = request.POST
|
||
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}")
|
||
logger.info(f"[GET_CHECKIN_LIST] Full GET params: {dict(request.GET.items())}")
|
||
logger.info(f"[GET_CHECKIN_LIST] Full POST params: {dict(request.POST.items()) if request.method == 'POST' else 'N/A'}")
|
||
|
||
# POSTの場合のリクエストボディも確認
|
||
if request.method == 'POST':
|
||
try:
|
||
body_content = request.body.decode('utf-8')
|
||
logger.info(f"[GET_CHECKIN_LIST] Request body: {body_content}")
|
||
except:
|
||
logger.info(f"[GET_CHECKIN_LIST] Could not decode request body")
|
||
|
||
# パラメータ検証
|
||
if not all([zekken_number, event_code]):
|
||
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)
|
||
|
||
# チームの存在確認
|
||
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,
|
||
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)
|
||
|
||
# チェックイン記録を取得
|
||
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'):
|
||
start_info = {
|
||
"start_time": entry.start_info.start_time.strftime("%Y-%m-%d %H:%M:%S")
|
||
}
|
||
|
||
# ゴール情報を取得
|
||
goal_info = None
|
||
if hasattr(entry, 'goal_info'):
|
||
goal_info = {
|
||
"goal_time": entry.goal_info.goal_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||
"score": entry.goal_info.score,
|
||
"scoreboard_url": entry.goal_info.scoreboard_url
|
||
}
|
||
|
||
# チェックイン記録をシリアライズ
|
||
checkpoint_list = []
|
||
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'],
|
||
"source": cp['source']
|
||
}
|
||
|
||
# チェックポイントの得点情報を取得( Location2025 モデルがある場合)
|
||
try:
|
||
event_cp = Location2025.objects.filter(
|
||
event_id=event.id,
|
||
cp_number=cp['cp_number']
|
||
).first()
|
||
|
||
if event_cp:
|
||
checkpoint_data["cp_point"] = event_cp.cp_point
|
||
checkpoint_data["cp_name"] = event_cp.cp_name
|
||
checkpoint_data["is_service_cp"] = event_cp.buy_point > 0 # buy_pointが0より大きい場合はサービスポイント
|
||
except:
|
||
# Location モデルが存在しない場合はスキップ
|
||
pass
|
||
|
||
checkpoint_list.append(checkpoint_data)
|
||
|
||
# レスポンスを返す
|
||
return Response({
|
||
"status": "OK",
|
||
"team_info": {
|
||
"team_name": entry.team.team_name,
|
||
"zekken_number": zekken_number,
|
||
"class_name": entry.category.category_name,
|
||
"event_code": event_code
|
||
},
|
||
"start_info": start_info,
|
||
"goal_info": goal_info,
|
||
"checkpoints": checkpoint_list,
|
||
"total_checkpoints": len(checkpoint_list)
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in get_checkin_list: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.共通処理を行う内部関数 _handle_service_check を作成し、2つのエンドポイントで再利用
|
||
2.それぞれのエンドポイントは、この関数を呼び出し、サービスチェックフラグを True または False に設定
|
||
3.処理の流れは以下の通り:
|
||
- イベントコード、ゼッケン番号、シリアル番号の検証
|
||
- シリアル番号の整数変換確認
|
||
- イベントとチームの存在確認
|
||
- 指定されたシリアル番号のチェックポイント記録を検索
|
||
- サービスチェックの現在の状態を確認し、既に希望の値であれば警告を返す
|
||
- サービスチェックフラグを更新
|
||
- 成功メッセージを返す
|
||
|
||
この2つのエンドポイントは、チェックポイントの is_service_checked フラグを設定または解除するために使用されます。
|
||
これにより、特定のチェックポイントにサービスチェックのマークを付けたり、解除したりすることができます。
|
||
|
||
サービスチェックとは、一部のチェックポイントに付けられる特別なマークで、例えば追加ポイントの対象であることを示すなど、
|
||
特別な処理を行うためのフラグと考えられます。
|
||
"""
|
||
|
||
def _handle_service_check(request, set_value):
|
||
"""
|
||
サービスチェックフラグを設定する共通処理
|
||
|
||
パラメータ:
|
||
- event: イベントコード
|
||
- zekken: ゼッケン番号
|
||
- sn: シリアル番号
|
||
"""
|
||
# リクエストからパラメータを取得
|
||
event_code = request.query_params.get('event')
|
||
zekken_number = request.query_params.get('zekken')
|
||
serial_number = request.query_params.get('sn')
|
||
|
||
logger.debug(f"Parameters: event={event_code}, zekken={zekken_number}, sn={serial_number}, set_value={set_value}")
|
||
|
||
# パラメータ検証
|
||
if not all([event_code, zekken_number, serial_number]):
|
||
logger.warning("Missing required parameters")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "イベントコード、ゼッケン番号、シリアル番号が必要です"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# シリアル番号を整数に変換
|
||
try:
|
||
serial_number = int(serial_number)
|
||
except ValueError:
|
||
logger.warning(f"Invalid serial number format: {serial_number}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "シリアル番号は整数である必要があります"
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# イベントの存在確認
|
||
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 with zekken number {zekken_number} not found in event: {event_code}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 対象のチェックポイント記録を検索
|
||
checkpoint = GpsLog.objects.filter(
|
||
id=serial_number,
|
||
entry=entry
|
||
).first()
|
||
|
||
if not checkpoint:
|
||
logger.warning(f"Checkpoint with ID {serial_number} not found for team: {entry.team_name}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "指定されたシリアル番号のチェックポイント記録が見つかりません"
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# サービスチェックの現在の状態を確認
|
||
current_value = getattr(checkpoint, 'is_service_checked', False)
|
||
|
||
# 状態が既に希望する値である場合は早期リターン
|
||
if current_value == set_value:
|
||
status_str = "すでにチェック済み" if set_value else "すでにチェック解除済み"
|
||
return Response({
|
||
"status": "WARNING",
|
||
"message": f"このチェックポイントは{status_str}です",
|
||
"checkpoint_id": checkpoint.id,
|
||
"cp_number": checkpoint.cp_number,
|
||
"is_service_checked": current_value
|
||
})
|
||
|
||
# トランザクション開始
|
||
with transaction.atomic():
|
||
# サービスチェックフラグを設定
|
||
checkpoint.is_service_checked = set_value
|
||
checkpoint.save()
|
||
|
||
action = "設定" if set_value else "解除"
|
||
logger.info(f"サービスチェックを{action}: CP {checkpoint.cp_number} "
|
||
f"for team: {entry.team_name} (zekken: {zekken_number})")
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"message": f"サービスチェックを{action}しました",
|
||
"team_name": entry.team_name,
|
||
"zekken_number": zekken_number,
|
||
"checkpoint_id": checkpoint.id,
|
||
"cp_number": checkpoint.cp_number,
|
||
"is_service_checked": set_value
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in handling service check: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
@api_view(['GET'])
|
||
def service_check_true(request):
|
||
"""サービスチェックフラグをTrueに設定"""
|
||
logger.info("service_check_true called")
|
||
return _handle_service_check(request, True)
|
||
|
||
@api_view(['GET'])
|
||
def service_check_false(request):
|
||
"""サービスチェックフラグをFalseに設定"""
|
||
logger.info("service_check_false called")
|
||
return _handle_service_check(request, False)
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.イベントコードのパラメータを受け取り、検証します
|
||
2.指定されたイベントの存在を確認します
|
||
3.そのイベントに参加している全てのチームのエントリーを取得します
|
||
4.各チームのチェックポイント記録を調査し、以下の条件を満たすものを特定します:
|
||
- チームがスタートしていること
|
||
- チェックポイントが「is_service_checked=False」であること(サービスチェックが完了していない)
|
||
- チェックポイントが「is_service_cp=True」であること(サービスチェックが必要)
|
||
- これは Location モデルから取得するか、チェックポイント自体のプロパティから判断
|
||
5.該当するチェックポイントの情報をリストにまとめ、チェックイン時間順にソートします
|
||
6.結果をレスポンスとして返します
|
||
|
||
このエンドポイントは、管理者が未処理のサービスチェックポイントを把握するために使用できます。
|
||
サービスチェックとは、一部のチェックポイントに関連する特別な処理で、例えば追加ポイントの付与や特別なサービスの提供などが該当します。
|
||
"""
|
||
|
||
@api_view(['GET'])
|
||
def get_yet_check_service_list(request):
|
||
"""
|
||
未チェックのサービスチェックポイントリストを取得
|
||
|
||
パラメータ:
|
||
- event: イベントコード
|
||
"""
|
||
logger.info("get_yet_check_service_list called")
|
||
|
||
# リクエストからパラメータを取得
|
||
event_code = request.query_params.get('event')
|
||
|
||
logger.debug(f"Parameters: event={event_code}")
|
||
|
||
# パラメータ検証
|
||
if not event_code:
|
||
logger.warning("Missing required event parameter")
|
||
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)
|
||
|
||
# イベントの全チームのエントリーを取得
|
||
entries = Entry.objects.filter(event=event)
|
||
|
||
# チェックが必要なサービスチェックポイントのリスト
|
||
pending_service_checks = []
|
||
|
||
# 各チームのチェックポイントを調査
|
||
for entry in entries:
|
||
# チームのチェックポイントで、未チェックのサービスポイントを取得
|
||
checkpoints = GpsLog.objects.filter(
|
||
entry=entry,
|
||
is_service_checked=False # サービスチェックが完了していないもの
|
||
)
|
||
|
||
# チェックポイントがあるか確認
|
||
if not checkpoints.exists():
|
||
continue
|
||
|
||
# チームがスタートしているか確認
|
||
if not hasattr(entry, 'start_info'):
|
||
continue
|
||
|
||
# チームがゴールしていないか確認(オプション)
|
||
# if hasattr(entry, 'goal_info'):
|
||
# continue
|
||
|
||
# チェックポイント情報を処理
|
||
for cp in checkpoints:
|
||
# サービスチェックが必要なチェックポイントかを確認
|
||
# イベントのチェックポイント定義から確認(必要に応じて)
|
||
is_service_cp = False
|
||
|
||
try:
|
||
event_cp = Location2025.objects.filter(
|
||
event_id=event.id,
|
||
cp_number=cp.cp_number
|
||
).first()
|
||
|
||
if event_cp and event_cp.buy_point > 0: # buy_pointが0より大きい場合はサービスポイント
|
||
is_service_cp = True
|
||
except:
|
||
# Location2025 モデルがない場合は、チェックポイントのプロパティだけで判断
|
||
pass
|
||
|
||
# サービスチェックが必要なチェックポイントならリストに追加
|
||
if is_service_cp or getattr(cp, 'is_service_cp', False):
|
||
# チェックポイントをリストに追加
|
||
pending_service_checks.append({
|
||
"id": cp.id,
|
||
"team_name": entry.team.team_name,
|
||
"zekken_number": entry.zekken_number,
|
||
"cp_number": cp.cp_number,
|
||
"class_name": entry.category.category_name,
|
||
"checkin_time": cp.checkin_time.strftime("%Y-%m-%d %H:%M:%S") if cp.checkin_time else None,
|
||
"image_url": cp.image_address
|
||
})
|
||
|
||
# 結果を返す(チェックイン時間でソート)
|
||
pending_service_checks.sort(key=lambda x: x.get('checkin_time', ''))
|
||
|
||
return Response({
|
||
"status": "OK",
|
||
"event_code": event_code,
|
||
"pending_service_checks": pending_service_checks,
|
||
"total_pending": len(pending_service_checks)
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in get_yet_check_service_list: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|