281 lines
11 KiB
Python
Executable File
281 lines
11 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, GpsLog
|
||
import logging
|
||
from django.db.models import F, Q
|
||
from django.conf import settings
|
||
import os
|
||
from urllib.parse import urljoin
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
1.2つのエンドポイントを提供しています:
|
||
- /get_photo_list - 認証なしで写真とレポートURLを取得
|
||
- /get_photo_list_prod - パスワード認証付きで同じ情報を取得
|
||
2.共通のロジックは get_team_photos 関数に集約し、以下の情報を取得します:
|
||
- チームの基本情報(名前、ゼッケン番号、クラス名)
|
||
- チェックポイント通過時の写真(時間順、サービスチェック情報含む)
|
||
- スタート写真とゴール写真(あれば)
|
||
- チームレポートのURL(存在する場合)
|
||
- スコアボードのURL(存在する場合)
|
||
- チームのスコア(ゴール済みの場合)
|
||
3.レポートとスコアボードのファイルパスを実際に確認し、存在する場合のみURLを提供します
|
||
4.写真の表示順はスタート→チェックポイント(時間順)→ゴールとなっており、チェックポイントについてはそれぞれ番号、撮影時間、サービスチェック状態などの情報も含めています
|
||
|
||
この実装により、チームは自分たちの競技中の写真やレポートを簡単に確認できます。
|
||
本番環境(_prod版)ではパスワード認証によりセキュリティを確保しています。
|
||
"""
|
||
|
||
@api_view(['GET'])
|
||
def get_photo_list(request):
|
||
"""
|
||
チームの写真とレポートURLを取得(認証なし版)
|
||
|
||
パラメータ:
|
||
- zekken: ゼッケン番号
|
||
- event: イベントコード
|
||
"""
|
||
logger.info("get_photo_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)
|
||
|
||
return get_team_photos(zekken_number, event_code)
|
||
|
||
@api_view(['GET'])
|
||
def get_photo_list_prod(request):
|
||
"""
|
||
チームの写真とレポートURLを取得(認証あり版)
|
||
|
||
パラメータ:
|
||
- zekken: ゼッケン番号
|
||
- pw: パスワード
|
||
- event: イベントコード
|
||
"""
|
||
logger.info("get_photo_list_prod called")
|
||
|
||
# リクエストからパラメータを取得
|
||
zekken_number = request.query_params.get('zekken')
|
||
password = request.query_params.get('pw')
|
||
event_code = request.query_params.get('event')
|
||
|
||
logger.debug(f"Parameters: zekken={zekken_number}, event={event_code}, has_password={bool(password)}")
|
||
|
||
# パラメータ検証
|
||
if not all([zekken_number, password, 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)
|
||
|
||
# パスワード検証
|
||
if not hasattr(entry, 'password') or entry.password != password:
|
||
logger.warning(f"Invalid password for team: {entry.team_name}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "パスワードが一致しません"
|
||
}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
return get_team_photos(zekken_number, event_code)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in get_photo_list_prod: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
def get_team_photos(zekken_number, event_code):
|
||
"""
|
||
チームの写真とレポートURLを取得する共通関数
|
||
"""
|
||
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)
|
||
|
||
# チームの基本情報を取得
|
||
team_info = {
|
||
"team_name": entry.team_name,
|
||
"zekken_number": entry.zekken_number,
|
||
"class_name": entry.class_name,
|
||
"event_name": event.event_name
|
||
}
|
||
|
||
# チェックポイント通過情報(写真を含む)を取得
|
||
checkpoints = GpsLog.objects.filter(
|
||
entry=entry
|
||
).order_by('checkin_time')
|
||
|
||
# 写真リストを作成
|
||
photos = []
|
||
|
||
for cp in checkpoints:
|
||
# 写真URLがある場合のみ追加
|
||
if hasattr(cp, 'image') and cp.image:
|
||
photo_data = {
|
||
"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": request.build_absolute_uri(cp.image.url) if hasattr(request, 'build_absolute_uri') else cp.image.url
|
||
}
|
||
|
||
# サービスチェックの情報があれば追加
|
||
if hasattr(cp, 'is_service_checked'):
|
||
photo_data["is_service_checked"] = cp.is_service_checked
|
||
|
||
photos.append(photo_data)
|
||
|
||
# スタート写真があれば追加
|
||
if hasattr(entry, 'start_info') and hasattr(entry.start_info, 'start_image') and entry.start_info.start_image:
|
||
start_image = {
|
||
"cp_number": "START",
|
||
"checkin_time": entry.start_info.start_time.strftime("%Y-%m-%d %H:%M:%S") if entry.start_info.start_time else None,
|
||
"image_url": request.build_absolute_uri(entry.start_info.start_image.url) if hasattr(request, 'build_absolute_uri') else entry.start_info.start_image.url
|
||
}
|
||
photos.insert(0, start_image) # リストの先頭に追加
|
||
|
||
# ゴール写真があれば追加
|
||
if hasattr(entry, 'goal_info') and hasattr(entry.goal_info, 'goal_image') and entry.goal_info.goal_image:
|
||
goal_image = {
|
||
"cp_number": "GOAL",
|
||
"checkin_time": entry.goal_info.goal_time.strftime("%Y-%m-%d %H:%M:%S") if entry.goal_info.goal_time else None,
|
||
"image_url": request.build_absolute_uri(entry.goal_info.goal_image.url) if hasattr(request, 'build_absolute_uri') else entry.goal_info.goal_image.url
|
||
}
|
||
photos.append(goal_image) # リストの末尾に追加
|
||
|
||
# チームレポートURLを生成
|
||
# レポートURLは「/レポートディレクトリ/イベント名/ゼッケン番号.pdf」のパターンを想定
|
||
report_directory = getattr(settings, 'REPORT_DIRECTORY', 'reports')
|
||
report_base_url = getattr(settings, 'REPORT_BASE_URL', '/media/reports/')
|
||
|
||
# レポートファイルの物理パスをチェック
|
||
report_path = os.path.join(
|
||
settings.MEDIA_ROOT,
|
||
report_directory,
|
||
event_code,
|
||
f"{zekken_number}.pdf"
|
||
)
|
||
|
||
# レポートURLを生成
|
||
has_report = os.path.exists(report_path)
|
||
report_url = None
|
||
|
||
if has_report:
|
||
report_url = urljoin(
|
||
report_base_url,
|
||
f"{event_code}/{zekken_number}.pdf"
|
||
)
|
||
|
||
# 絶対URLに変換
|
||
if hasattr(request, 'build_absolute_uri'):
|
||
report_url = request.build_absolute_uri(report_url)
|
||
|
||
# スコアボードURLを生成
|
||
scoreboard_path = os.path.join(
|
||
settings.MEDIA_ROOT,
|
||
'scoreboards',
|
||
event_code,
|
||
f"scoreboard_{zekken_number}.pdf"
|
||
)
|
||
|
||
has_scoreboard = os.path.exists(scoreboard_path)
|
||
scoreboard_url = None
|
||
|
||
if has_scoreboard:
|
||
scoreboard_url = urljoin(
|
||
'/media/scoreboards/',
|
||
f"{event_code}/scoreboard_{zekken_number}.pdf"
|
||
)
|
||
|
||
# 絶対URLに変換
|
||
if hasattr(request, 'build_absolute_uri'):
|
||
scoreboard_url = request.build_absolute_uri(scoreboard_url)
|
||
|
||
# チームのスコア情報
|
||
score = None
|
||
if hasattr(entry, 'goal_info') and hasattr(entry.goal_info, 'score'):
|
||
score = entry.goal_info.score
|
||
|
||
# レスポンスデータ
|
||
response_data = {
|
||
"status": "OK",
|
||
"team": team_info,
|
||
"photos": photos,
|
||
"photo_count": len(photos),
|
||
"has_report": has_report,
|
||
"report_url": report_url,
|
||
"has_scoreboard": has_scoreboard,
|
||
"scoreboard_url": scoreboard_url,
|
||
"score": score
|
||
}
|
||
|
||
return Response(response_data)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in get_team_photos: {str(e)}")
|
||
return Response({
|
||
"status": "ERROR",
|
||
"message": "サーバーエラーが発生しました"
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|