# 既存のインポート部分に追加 from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework import status from rog.models import NewEvent2, Entry, GpsCheckin, Team 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.team_name if entry.team else 'Unknown'}") 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: logger.info(f"Getting photos for zekken: {zekken_number}, event: {event_code}") # イベントの存在確認(event_codeで検索) event = NewEvent2.objects.filter(event_code=event_code).first() if not event: logger.warning(f"Event not found with event_code: {event_code}") # event_nameでも試してみる event = NewEvent2.objects.filter(event_name=event_code).first() if not event: logger.warning(f"Event not found with event_name: {event_code}") return Response({ "status": "ERROR", "message": "指定されたイベントが見つかりません" }, status=status.HTTP_404_NOT_FOUND) logger.info(f"Found event: {event.event_name} (ID: {event.id})") # まずEntryテーブルを確認 entry = Entry.objects.filter( event=event, zekken_number=zekken_number ).first() team_name = "Unknown Team" if entry and entry.team: team_name = entry.team.team_name logger.info(f"Found team in Entry: {team_name}") else: logger.info(f"No Entry found for zekken {zekken_number}, checking GpsCheckin for legacy data") # GpsCheckinテーブルからチーム情報を取得(レガシーデータ対応) gps_checkin_sample = GpsCheckin.objects.filter( event_code=event_code, zekken_number=str(zekken_number) ).first() if gps_checkin_sample and gps_checkin_sample.team: team_name = gps_checkin_sample.team.team_name logger.info(f"Found team in GpsCheckin: {team_name}") else: team_name = f"Team {zekken_number}" logger.info(f"No team found, using default: {team_name}") # GpsCheckinテーブルから写真データを取得 gps_checkins = GpsCheckin.objects.filter( event_code=event_code, zekken_number=str(zekken_number), image_address__isnull=False ).exclude( image_address='' ).order_by('path_order', 'create_at') logger.info(f"Found {gps_checkins.count()} GPS checkins with images") # 写真リストを作成 photos = [] for gps in gps_checkins: if gps.image_address: # 画像URLを構築 if gps.image_address.startswith('http'): # 絶対URLの場合はそのまま使用 image_url = gps.image_address else: # 相対パスの場合はベースURLと結合 # settings.MEDIA_URLやstatic fileの設定に基づいて調整 image_url = f"/media/{gps.image_address}" if not gps.image_address.startswith('/') else gps.image_address photo_data = { "cp_number": gps.cp_number if gps.cp_number is not None else 0, "photo_url": image_url, "checkin_time": gps.create_at.strftime("%Y-%m-%d %H:%M:%S") if gps.create_at else None, "path_order": gps.path_order, "buy_flag": gps.buy_flag, "validate_location": gps.validate_location, "points": gps.points } photos.append(photo_data) logger.debug(f"Added photo: CP {gps.cp_number}, URL: {image_url}") # チームの基本情報 team_info = { "team_name": team_name, "zekken_number": zekken_number, "event_name": event.event_name, "photo_count": len(photos) } # レスポンス構築 response_data = { "status": "SUCCESS", "message": f"写真を{len(photos)}枚取得しました", "team_info": team_info, "photo_list": photos } logger.info(f"Successfully retrieved {len(photos)} photos for team {team_name}") return Response(response_data, status=status.HTTP_200_OK) 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)