Files
rogaining_srv/rog/views_apis/api_edit.py

1458 lines
64 KiB
Python
Raw Normal View History

2025-08-20 19:15:19 +09:00
# 既存のインポート部分に追加
from datetime import datetime, timezone
# from sqlalchemy import Transaction # 削除 - SQLAlchemy 2.0では利用不可
2025-08-20 19:15:19 +09:00
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
2025-09-02 17:10:33 +09:00
from rog.models import Location2025, NewEvent2, Entry, GpsLog
2025-08-20 19:15:19 +09:00
import logging
import uuid
import os
2025-08-20 19:15:19 +09:00
from django.db.models import F, Q
from django.db import transaction
2025-08-20 19:15:19 +09:00
from django.conf import settings
import os
from urllib.parse import urljoin
logger = logging.getLogger(__name__)
"""
解説
この実装では以下の処理を行っています
1.イベントコードチーム名チェックポイント番号のパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントとチームの存在を確認します
4.指定されたチェックポイント記録を検索します
- 該当する記録が存在しない場合はエラーを返します
5.チェックポイント記録を削除します
6.成功した場合削除情報を含む成功メッセージを返します
このエンドポイントによりロゲイニングアプリから誤って登録したチェックポイント記録を削除することができます
これは例えば通過していないチェックポイントを誤って登録してしまった場合などに役立ちます
"""
@api_view(['POST'])
def remove_checkin_from_rogapp(request):
"""
アプリからチェックイン記録を削除
パラメータ:
- event_code: イベントコード
- team_name: チーム名
- cp_number: チェックポイント番号
"""
2025-09-02 17:01:40 +09:00
# ログ用のリクエスト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"
2025-08-20 19:15:19 +09:00
# リクエストからパラメータを取得
event_code = request.data.get('event_code')
team_name = request.data.get('team_name')
cp_number = request.data.get('cp_number')
2025-09-02 17:01:40 +09:00
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}")
2025-08-20 19:15:19 +09:00
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]):
2025-09-02 17:01:40 +09:00
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}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "イベントコード、チーム名、チェックポイント番号が必要です"
}, status=status.HTTP_400_BAD_REQUEST)
try:
# イベントの存在確認
event = NewEvent2.objects.filter(event_name=event_code).first()
if not event:
2025-09-02 17:01:40 +09:00
logger.error(f"[REMOVE_CHECKIN] ❌ Event not found - ID: {request_id}, event_code: '{event_code}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたイベントが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
logger.info(f"[REMOVE_CHECKIN] ✅ Event found - ID: {request_id}, event: '{event.event_name}', event_id: {event.id}")
2025-08-20 19:15:19 +09:00
# チームの存在確認
entry = Entry.objects.filter(
event=event,
team_name=team_name
).first()
if not entry:
2025-09-02 17:01:40 +09:00
logger.error(f"[REMOVE_CHECKIN] ❌ Team not found - ID: {request_id}, team_name: '{team_name}', event_code: '{event_code}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
logger.info(f"[REMOVE_CHECKIN] ✅ Team found - ID: {request_id}, team_name: '{team_name}', zekken: {entry.zekken_number}, entry_id: {entry.id}")
2025-08-20 19:15:19 +09:00
# 対象のチェックポイント記録を検索
checkpoint = GpsLog.objects.filter(
entry=entry,
cp_number=cp_number
).first()
if not checkpoint:
2025-09-02 17:01:40 +09:00
logger.warning(f"[REMOVE_CHECKIN] ⚠️ Checkpoint not found - ID: {request_id}, team_name: '{team_name}', zekken: {entry.zekken_number}, cp_number: {cp_number}, Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたチェックポイント記録が見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
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)}")
2025-08-20 19:15:19 +09:00
# チェックポイント記録を削除
checkin_time = checkpoint.checkin_time
checkpoint_id = checkpoint.id
2025-09-02 17:01:40 +09:00
image_address = checkpoint.image_address
2025-08-20 19:15:19 +09:00
2025-09-02 17:01:40 +09:00
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.zekken_number}, cp_number: {cp_number}, checkpoint_id: {checkpoint_id}, had_image: {bool(image_address)}, Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
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:
2025-09-02 17:01:40 +09:00
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)
2025-08-20 19:15:19 +09:00
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: ゼッケン番号
"""
2025-09-02 17:01:40 +09:00
# リクエスト詳細情報を取得
client_ip = request.META.get('REMOTE_ADDR', 'Unknown')
user_agent = request.META.get('HTTP_USER_AGENT', 'Unknown')
user_info = request.user.email if request.user.is_authenticated else 'Anonymous'
logger.info(f"[ADMIN_START] API called - Client IP: {client_ip}, User: {user_info}")
2025-08-20 19:15:19 +09:00
# リクエストからパラメータを取得
event_code = request.query_params.get('event')
zekken_number = request.query_params.get('zekken')
2025-09-02 17:01:40 +09:00
logger.info(f"[ADMIN_START] Request parameters - event_code: {event_code}, zekken_number: {zekken_number}, user_agent: {user_agent[:100]}")
2025-08-20 19:15:19 +09:00
# パラメータ検証
if not all([event_code, zekken_number]):
2025-09-02 17:01:40 +09:00
logger.warning(f"[ADMIN_START] Missing required parameters - event_code: {event_code}, zekken_number: {zekken_number}, Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "イベントコードとゼッケン番号が必要です"
}, status=status.HTTP_400_BAD_REQUEST)
try:
# イベントの存在確認
event = NewEvent2.objects.filter(event_name=event_code).first()
if not event:
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)
2025-09-02 17:10:33 +09:00
# 既にスタート済みかチェックGpsLogでSTARTレコードを確認
existing_start = GpsLog.objects.filter(
entry=entry,
cp_number="START",
serial_number=0
).first()
if existing_start:
logger.warning(f"Team {entry.team_name} (zekken: {zekken_number}) already started at {existing_start.checkin_time}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "WARNING",
"message": "このチームは既にスタートしています",
2025-09-02 17:10:33 +09:00
"start_time": existing_start.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
2025-08-20 19:15:19 +09:00
"team_name": entry.team_name
})
# トランザクション開始
with transaction.atomic():
2025-09-02 17:10:33 +09:00
# スタート情報をGpsLogとして登録
start_info = GpsLog.objects.create(
2025-08-20 19:15:19 +09:00
entry=entry,
2025-09-02 17:10:33 +09:00
cp_number="START",
serial_number=0,
latitude=0.0,
longitude=0.0,
checkin_time=timezone.now(),
extra_data={"source": "admin_start"}
2025-08-20 19:15:19 +09:00
)
2025-09-02 17:10:33 +09:00
logger.info(f"Team {entry.team_name} (zekken: {zekken_number}) started at {start_info.checkin_time}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "OK",
"message": "スタート処理が完了しました",
"team_name": entry.team_name,
"zekken_number": zekken_number,
"event_code": event_code,
2025-09-02 17:10:33 +09:00
"start_time": start_info.checkin_time.strftime("%Y-%m-%d %H:%M:%S")
2025-08-20 19:15:19 +09:00
})
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: カンマ区切りのチェックポイント番号リスト
"""
2025-09-02 17:01:40 +09:00
# ログ用のリクエスト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"
2025-08-20 19:15:19 +09:00
# リクエストからパラメータを取得
event_code = request.query_params.get('event')
zekken_number = request.query_params.get('zekken')
cp_list_string = request.query_params.get('list')
2025-09-02 17:01:40 +09:00
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]}")
2025-08-20 19:15:19 +09:00
# パラメータ検証
if not all([event_code, zekken_number, cp_list_string]):
2025-09-02 17:01:40 +09:00
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}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "イベントコード、ゼッケン番号、チェックポイントリストが必要です"
}, status=status.HTTP_400_BAD_REQUEST)
try:
# イベントの存在確認
event = NewEvent2.objects.filter(event_name=event_code).first()
if not event:
2025-09-02 17:01:40 +09:00
logger.error(f"[ADMIN_CHECKIN] ❌ Event not found - ID: {request_id}, event_code: '{event_code}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたイベントが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
logger.info(f"[ADMIN_CHECKIN] ✅ Event found - ID: {request_id}, event: '{event.event_name}', event_id: {event.id}")
2025-08-20 19:15:19 +09:00
# チームの存在確認
entry = Entry.objects.filter(
event=event,
zekken_number=zekken_number
).first()
if not entry:
2025-09-02 17:01:40 +09:00
logger.error(f"[ADMIN_CHECKIN] ❌ Team not found - ID: {request_id}, zekken: {zekken_number}, event_code: '{event_code}', Client IP: {client_ip}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "ERROR",
"message": "指定されたゼッケン番号のチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
2025-09-02 17:01:40 +09:00
logger.info(f"[ADMIN_CHECKIN] ✅ Team found - ID: {request_id}, team_name: '{entry.team_name}', zekken: {zekken_number}, entry_id: {entry.id}")
2025-08-20 19:15:19 +09:00
# チームがスタートしているか確認(オプション)
2025-09-02 17:10:33 +09:00
start_record = GpsLog.objects.filter(
entry=entry,
cp_number="START",
serial_number=0
).first()
if not start_record:
2025-08-20 19:15:19 +09:00
# スタート情報がない場合は自動的にスタートさせる
# 注意: 管理画面からの操作なので、自動スタートを許可
2025-09-02 17:10:33 +09:00
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_bulk_checkin"}
)
2025-09-02 17:01:40 +09:00
logger.info(f"[ADMIN_CHECKIN] ✅ Auto-started team - ID: {request_id}, team_name: '{entry.team_name}', zekken: {zekken_number}")
2025-08-20 19:15:19 +09:00
# チェックポイントリストを解析
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(
entry=entry,
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:
2025-08-30 02:20:25 +09:00
event_cp = Location2025.objects.filter(
event_id=event.id,
2025-08-20 19:15:19 +09:00
cp_number=cp_number
).first()
except:
pass
# チェックポイント登録
checkpoint = GpsLog.objects.create(
entry=entry,
cp_number=cp_number,
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: {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)
2025-09-02 17:10:33 +09:00
# チームがスタートしているか確認GpsLogでSTARTレコードを確認
start_record = GpsLog.objects.filter(
entry=entry,
cp_number="START",
serial_number=0
).first()
if not start_record:
2025-08-20 19:15:19 +09:00
# 管理画面からの操作なので、自動的にスタートさせる
2025-09-02 17:10:33 +09:00
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"}
)
2025-08-20 19:15:19 +09:00
logger.info(f"Auto-started team {entry.team_name} (zekken: {zekken_number})")
2025-09-02 17:10:33 +09:00
# 既にゴールしているかチェック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}")
2025-08-20 19:15:19 +09:00
return Response({
"status": "WARNING",
"message": "このチームは既にゴールしています",
2025-09-02 17:10:33 +09:00
"goal_time": existing_goal.checkin_time.strftime("%Y-%m-%d %H:%M:%S"),
2025-08-20 19:15:19 +09:00
"team_name": entry.team_name,
2025-09-02 17:10:33 +09:00
"scoreboard_url": existing_goal.extra_data.get('scoreboard_url', '') if existing_goal.extra_data else ''
2025-08-20 19:15:19 +09:00
})
# ゴール時間の処理
if goal_time_str:
try:
goal_time = datetime.strptime(goal_time_str, "%Y-%m-%d %H:%M:%S")
except ValueError:
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():
2025-08-20 19:15:19 +09:00
# スコアの計算
score = calculate_team_score(entry)
# スコアボードの生成
scoreboard_filename = f"scoreboard_{entry.zekken_number}_{uuid.uuid4().hex[:8]}.pdf"
scoreboard_path = os.path.join(settings.MEDIA_ROOT, 'scoreboards', scoreboard_filename)
os.makedirs(os.path.dirname(scoreboard_path), exist_ok=True)
# ここでスコアボードを実際に生成する処理を実装
# 例: generate_scoreboard(entry, score, scoreboard_path)
# スコアボードへのURL
scoreboard_url = f"{settings.MEDIA_URL}scoreboards/{scoreboard_filename}"
2025-09-02 17:10:33 +09:00
# ゴール情報をGpsLogとして登録
goal_info = GpsLog.objects.create(
2025-08-20 19:15:19 +09:00
entry=entry,
2025-09-02 17:10:33 +09:00
cp_number="GOAL",
serial_number=9999,
latitude=0.0,
longitude=0.0,
checkin_time=goal_time,
extra_data={
"score": score,
"scoreboard_url": scoreboard_url
}
2025-08-20 19:15:19 +09:00
)
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(entry=entry)
total_score = 0
for cp in checkpoints:
# チェックポイントの得点を取得
cp_point = 0
try:
2025-08-30 02:20:25 +09:00
# Location2025 モデルが存在する場合はそこから得点を取得
event_cp = Location2025.objects.filter(
event_id=entry.event.id,
2025-08-20 19:15:19 +09:00
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")
# リクエストからパラメータを取得
zekken_number = request.query_params.get('zekken')
event_code = request.query_params.get('event')
logger.debug(f"Parameters: zekken={zekken_number}, event={event_code}")
# パラメータ検証
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)
# チームの存在確認
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)
# チェックイン記録を取得
checkpoints = GpsLog.objects.filter(
entry=entry
).order_by('checkin_time')
# スタート情報を取得
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 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 if hasattr(cp, 'is_service_checked') else False
}
2025-08-30 02:20:25 +09:00
# チェックポイントの得点情報を取得( Location2025 モデルがある場合)
2025-08-20 19:15:19 +09:00
try:
2025-08-30 02:20:25 +09:00
event_cp = Location2025.objects.filter(
event_id=event.id,
2025-08-20 19:15:19 +09:00
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
2025-08-30 02:20:25 +09:00
checkpoint_data["is_service_cp"] = event_cp.buy_point > 0 # buy_pointが0より大きい場合はサービスポイント
2025-08-20 19:15:19 +09:00
except:
# Location モデルが存在しない場合はスキップ
pass
checkpoint_list.append(checkpoint_data)
# レスポンスを返す
return Response({
"status": "OK",
"team_info": {
"team_name": entry.team_name,
"zekken_number": zekken_number,
"class_name": entry.class_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:
2025-08-30 02:20:25 +09:00
event_cp = Location2025.objects.filter(
event_id=event.id,
2025-08-20 19:15:19 +09:00
cp_number=cp.cp_number
).first()
2025-08-30 02:20:25 +09:00
if event_cp and event_cp.buy_point > 0: # buy_pointが0より大きい場合はサービスポイント
2025-08-20 19:15:19 +09:00
is_service_cp = True
except:
2025-08-30 02:20:25 +09:00
# Location2025 モデルがない場合は、チェックポイントのプロパティだけで判断
2025-08-20 19:15:19 +09:00
pass
# サービスチェックが必要なチェックポイントならリストに追加
if is_service_cp or getattr(cp, 'is_service_cp', False):
# チェックポイントをリストに追加
pending_service_checks.append({
"id": cp.id,
"team_name": entry.team_name,
"zekken_number": entry.zekken_number,
"cp_number": cp.cp_number,
"class_name": entry.class_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)