# 既存のインポート部分に追加 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)