208 lines
7.9 KiB
Python
Executable File
208 lines
7.9 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, GpsCheckin, Team
|
||
import logging
|
||
from django.db.models import F, Q
|
||
from django.conf import settings
|
||
import os
|
||
from urllib.parse import urljoin, quote
|
||
from django.http import JsonResponse
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
from django.views.decorators.http import require_http_methods
|
||
import boto3
|
||
from botocore.exceptions import ClientError
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def generate_image_url(image_address, event_code, zekken_number):
|
||
"""
|
||
画像アドレスからS3 URLまたは適切なURLを生成
|
||
"""
|
||
if not image_address:
|
||
return None
|
||
|
||
# 既にHTTP URLの場合はそのまま返す
|
||
if image_address.startswith('http'):
|
||
return image_address
|
||
|
||
# simulation_image.jpgなどのテスト画像の場合はS3にないのでスキップ
|
||
if image_address in ['simulation_image.jpg', 'test_image']:
|
||
return f"/media/{image_address}"
|
||
|
||
# S3パスを構築してURLを生成
|
||
s3_key = f"{event_code}/{zekken_number}/{image_address}"
|
||
|
||
try:
|
||
# S3 URLを生成
|
||
s3_url = f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_S3_REGION_NAME}.amazonaws.com/{quote(s3_key)}"
|
||
return s3_url
|
||
except Exception as e:
|
||
# S3設定に問題がある場合はmediaパスを返す
|
||
return f"/media/{image_address}"
|
||
|
||
|
||
"""
|
||
解説
|
||
この実装では以下の処理を行っています:
|
||
|
||
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(request):
|
||
"""
|
||
チーム別の写真データを取得するAPI
|
||
"""
|
||
zekken = request.GET.get('zekken')
|
||
event = request.GET.get('event')
|
||
|
||
if not zekken or not event:
|
||
return JsonResponse({
|
||
'error': 'zekken and event parameters are required'
|
||
}, status=400)
|
||
|
||
try:
|
||
# GpsCheckinからチームの画像データを取得
|
||
gps_checkins = GpsCheckin.objects.filter(
|
||
zekken_number=zekken,
|
||
event_code=event
|
||
).exclude(
|
||
image_address__isnull=True
|
||
).exclude(
|
||
image_address=''
|
||
).order_by('create_at')
|
||
|
||
photos = []
|
||
for gps in gps_checkins:
|
||
# image_addressを処理してS3 URLまたは既存URLを生成
|
||
image_url = generate_image_url(gps.image_address, event, zekken)
|
||
|
||
photos.append({
|
||
'id': gps.id,
|
||
'image_url': image_url,
|
||
'created_at': gps.create_at.strftime('%Y-%m-%d %H:%M:%S') if gps.create_at else None,
|
||
'point_name': gps.checkpoint_id,
|
||
'latitude': float(gps.lattitude) if gps.lattitude else None,
|
||
'longitude': float(gps.longitude) if gps.longitude else None,
|
||
})
|
||
|
||
return JsonResponse({
|
||
'photos': photos,
|
||
'count': len(photos)
|
||
})
|
||
|
||
except Exception as e:
|
||
return JsonResponse({
|
||
'error': f'Error retrieving photos: {str(e)}'
|
||
}, status=500)
|