Fix photo upload 2

This commit is contained in:
2025-09-06 01:23:28 +09:00
parent f50d1e1c79
commit 66aacbb69e

View File

@ -21,12 +21,150 @@ from knox.auth import TokenAuthentication
from PIL import Image from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS from PIL.ExifTags import TAGS, GPSTAGS
import math import math
import tempfile
import json
from ..models import NewEvent2, Entry, GpsLog, Location2025, GpsCheckin from ..models import NewEvent2, Entry, GpsLog, Location2025, GpsCheckin, CheckinImages
# ログ設定 # ログ設定
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def upload_photo_to_s3(uploaded_file, event_code, zekken_number, cp_number=None, request_id=None):
"""
アップロードされた写真をS3にアップロードする
Args:
uploaded_file: アップロードされたファイル
event_code: イベントコード
zekken_number: ゼッケン番号
cp_number: チェックポイント番号
request_id: リクエストID
Returns:
dict: {
'success': bool,
's3_url': str,
's3_key': str,
'error': str
}
"""
try:
# S3のバケット設定を取得
bucket_name = getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'rogaining-images')
# S3キーの生成
timestamp = timezone.now().strftime("%Y%m%d_%H%M%S")
file_extension = os.path.splitext(uploaded_file.name)[1].lower()
if cp_number:
s3_key = f"checkin_photos/{event_code}/{zekken_number}/CP{cp_number}_{timestamp}_{request_id}{file_extension}"
else:
s3_key = f"bulk_upload/{event_code}/{zekken_number}/{timestamp}_{request_id}_{uploaded_file.name}"
# 一時ファイルとして保存
with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
for chunk in uploaded_file.chunks():
temp_file.write(chunk)
temp_file_path = temp_file.name
try:
# S3クライアントの設定環境変数から取得
import boto3
from botocore.exceptions import ClientError
s3_client = boto3.client(
's3',
aws_access_key_id=getattr(settings, 'AWS_ACCESS_KEY_ID', None),
aws_secret_access_key=getattr(settings, 'AWS_SECRET_ACCESS_KEY', None),
region_name=getattr(settings, 'AWS_S3_REGION_NAME', 'ap-northeast-1')
)
# S3にアップロード
s3_client.upload_file(
temp_file_path,
bucket_name,
s3_key,
ExtraArgs={
'ContentType': uploaded_file.content_type,
'Metadata': {
'event_code': event_code,
'zekken_number': str(zekken_number),
'cp_number': str(cp_number) if cp_number else '',
'original_filename': uploaded_file.name,
'upload_source': 'bulk_upload_api'
}
}
)
# S3 URLの生成
s3_url = f"https://{bucket_name}.s3.{getattr(settings, 'AWS_S3_REGION_NAME', 'ap-northeast-1')}.amazonaws.com/{s3_key}"
logger.info(f"[S3_UPLOAD] ✅ Successfully uploaded to S3 - key: {s3_key}")
return {
'success': True,
's3_url': s3_url,
's3_key': s3_key,
'error': None
}
finally:
# 一時ファイルを削除
if os.path.exists(temp_file_path):
os.unlink(temp_file_path)
except ImportError:
logger.warning(f"[S3_UPLOAD] ❌ boto3 not available, saving locally")
return {
'success': False,
's3_url': None,
's3_key': None,
'error': 'S3 upload not available (boto3 not installed)'
}
except Exception as e:
logger.error(f"[S3_UPLOAD] ❌ Error uploading to S3: {str(e)}")
return {
'success': False,
's3_url': None,
's3_key': None,
'error': str(e)
}
def create_checkin_image_record(gps_checkin, s3_url, s3_key, original_filename, exif_data, request_id, user):
"""
CheckinImagesテーブルにレコードを作成する
Args:
gps_checkin: GpsCheckinオブジェクト
s3_url: S3のURL
s3_key: S3のキー
original_filename: 元のファイル名
exif_data: EXIF情報
request_id: リクエストID
user: ユーザーオブジェクト
Returns:
CheckinImagesオブジェクトまたはNone
"""
try:
# CheckinImagesレコードを作成
checkin_image = CheckinImages.objects.create(
user=user,
checkinimage=s3_url, # S3のURLを画像URLとして保存
checkintime=timezone.now(),
team_name=f"Team_{gps_checkin.zekken}", # ゼッケン番号からチーム名を生成
event_code=gps_checkin.event_code,
cp_number=gps_checkin.cp_number
)
logger.info(f"[CHECKIN_IMAGE] ✅ Created CheckinImages record - ID: {checkin_image.id}, checkin_id: {gps_checkin.id}")
return checkin_image
except Exception as e:
logger.error(f"[CHECKIN_IMAGE] ❌ Error creating CheckinImages record: {str(e)}")
return None
def extract_exif_data(image_file): def extract_exif_data(image_file):
""" """
画像ファイルからEXIF情報を抽出する 画像ファイルからEXIF情報を抽出する
@ -164,9 +302,9 @@ def calculate_distance(lat1, lon1, lat2, lon2):
distance = R * c distance = R * c
return distance return distance
def create_checkin_from_photo(entry, checkpoint, photo_datetime, zekken_number, event_code, photo_filename, request_id): def create_checkin_from_photo(entry, checkpoint, photo_datetime, zekken_number, event_code, uploaded_file, exif_data, request_id, user):
""" """
写真情報からチェックインデータを作成する 写真情報からチェックインデータを作成し、S3アップロードとCheckinImages登録も行う
Args: Args:
entry: Entryオブジェクト entry: Entryオブジェクト
@ -174,11 +312,18 @@ def create_checkin_from_photo(entry, checkpoint, photo_datetime, zekken_number,
photo_datetime: 撮影日時 photo_datetime: 撮影日時
zekken_number: ゼッケン番号 zekken_number: ゼッケン番号
event_code: イベントコード event_code: イベントコード
photo_filename: 写真ファイル uploaded_file: アップロードされたファイル
exif_data: EXIF情報辞書
request_id: リクエストID request_id: リクエストID
user: ユーザーオブジェクト
Returns: Returns:
GpsCheckinオブジェクトまたはNone dict: {
'gps_checkin': GpsCheckinオブジェクト,
'checkin_image': CheckinImagesオブジェクト,
's3_info': S3アップロード情報,
'created': bool
}
""" """
try: try:
# 既存のチェックインをチェック(重複防止) # 既存のチェックインをチェック(重複防止)
@ -188,44 +333,75 @@ def create_checkin_from_photo(entry, checkpoint, photo_datetime, zekken_number,
cp_number=checkpoint.cp_number cp_number=checkpoint.cp_number
).first() ).first()
gps_checkin = None
created = False
if existing_checkin: if existing_checkin:
logger.info(f"[BULK_UPLOAD] 📍 Existing checkin found - ID: {request_id}, CP: {checkpoint.cp}, existing_id: {existing_checkin.id}") logger.info(f"[BULK_UPLOAD] 📍 Existing checkin found - ID: {request_id}, CP: {checkpoint.cp_number}, existing_id: {existing_checkin.id}")
return existing_checkin gps_checkin = existing_checkin
# 新規チェックインの作成
# 撮影時刻をJSTに変換
if photo_datetime:
# 撮影時刻をUTCとして扱い、JSTに変換
create_at = timezone.make_aware(photo_datetime, timezone.utc)
else: else:
create_at = timezone.now() # 新規チェックインの作成
# 撮影時刻をJSTに変換
if photo_datetime:
# 撮影時刻をUTCとして扱い、JSTに変換
create_at = timezone.make_aware(photo_datetime, timezone.utc)
else:
create_at = timezone.now()
# シリアル番号を決定(既存のチェックイン数+1 # シリアル番号を決定(既存のチェックイン数+1
existing_count = GpsCheckin.objects.filter( existing_count = GpsCheckin.objects.filter(
zekken=str(zekken_number), zekken=str(zekken_number),
event_code=event_code event_code=event_code
).count() ).count()
serial_number = existing_count + 1 serial_number = existing_count + 1
# チェックインデータを作成 # チェックインデータを作成
checkin = GpsCheckin.objects.create( gps_checkin = GpsCheckin.objects.create(
zekken=str(zekken_number), zekken=str(zekken_number),
event_code=event_code, event_code=event_code,
cp_number=checkpoint.cp_number, cp_number=checkpoint.cp_number,
serial_number=serial_number, serial_number=serial_number,
create_at=create_at, create_at=create_at,
update_at=timezone.now(), update_at=timezone.now(),
validate_location=True, # 写真から作成されたものは自動承認 validate_location=True, # 写真から作成されたものは自動承認
buy_flag=False, buy_flag=False,
points=checkpoint.checkin_point if checkpoint.checkin_point else 0, points=checkpoint.checkin_point if checkpoint.checkin_point else 0,
create_user=f"bulk_upload_{request_id}", # 一括アップロードで作成されたことを示す create_user=f"bulk_upload_{request_id}", # 一括アップロードで作成されたことを示す
update_user=f"bulk_upload_{request_id}", update_user=f"bulk_upload_{request_id}",
photo_source=photo_filename # 元画像ファイル名を記録(カスタムフィールドがあれば) )
logger.info(f"[BULK_UPLOAD] ✅ Created checkin - ID: {request_id}, checkin_id: {gps_checkin.id}, CP: {checkpoint.cp_number}, time: {create_at}, points: {gps_checkin.points}")
created = True
# S3に写真をアップロード
logger.info(f"[BULK_UPLOAD] 📤 Uploading photo to S3 - ID: {request_id}, CP: {checkpoint.cp_number}")
s3_result = upload_photo_to_s3(
uploaded_file,
event_code,
zekken_number,
checkpoint.cp_number,
request_id
) )
logger.info(f"[BULK_UPLOAD] ✅ Created checkin - ID: {request_id}, checkin_id: {checkin.id}, CP: {checkpoint.cp_number}, time: {create_at}, points: {checkin.points}") checkin_image = None
if s3_result['success']:
# CheckinImagesテーブルにレコード作成
checkin_image = create_checkin_image_record(
gps_checkin,
s3_result['s3_url'],
s3_result['s3_key'],
uploaded_file.name,
exif_data,
request_id,
user
)
return checkin return {
'gps_checkin': gps_checkin,
'checkin_image': checkin_image,
's3_info': s3_result,
'created': created
}
except Exception as e: except Exception as e:
logger.error(f"[BULK_UPLOAD] ❌ Error creating checkin - ID: {request_id}, CP: {checkpoint.cp_number if checkpoint else 'None'}, error: {str(e)}") logger.error(f"[BULK_UPLOAD] ❌ Error creating checkin - ID: {request_id}, CP: {checkpoint.cp_number if checkpoint else 'None'}, error: {str(e)}")
@ -457,27 +633,46 @@ def bulk_upload_checkin_photos(request):
) )
if nearest_checkpoint: if nearest_checkpoint:
# チェックインデータを作成 # ファイルポインタを先頭に戻すS3アップロード用
checkin = create_checkin_from_photo( uploaded_file.seek(0)
# チェックインデータを作成S3アップロードとCheckinImages登録も含む
checkin_result = create_checkin_from_photo(
entry, entry,
nearest_checkpoint, nearest_checkpoint,
exif_data['datetime'], exif_data['datetime'],
zekken_number, zekken_number,
event_code, event_code,
uploaded_file.name, uploaded_file,
request_id exif_data,
request_id,
request.user
) )
if checkin: if checkin_result and checkin_result['gps_checkin']:
gps_checkin = checkin_result['gps_checkin']
checkin_image = checkin_result['checkin_image']
s3_info = checkin_result['s3_info']
file_result.update({ file_result.update({
"auto_process_status": "success", "auto_process_status": "success",
"auto_process_message": f"チェックイン作成完了", "auto_process_message": f"チェックイン作成完了",
"checkin_info": { "checkin_info": {
"checkpoint_name": nearest_checkpoint.location, "checkpoint_name": nearest_checkpoint.location,
"cp_number": nearest_checkpoint.cp_number, "cp_number": nearest_checkpoint.cp_number,
"points": checkin.points, "points": gps_checkin.points,
"checkin_time": checkin.create_at.strftime("%Y-%m-%d %H:%M:%S"), "checkin_time": gps_checkin.create_at.strftime("%Y-%m-%d %H:%M:%S"),
"checkin_id": checkin.id "checkin_id": gps_checkin.id,
"was_existing": not checkin_result['created']
},
"s3_info": {
"uploaded": s3_info['success'],
"s3_url": s3_info['s3_url'],
"error": s3_info['error']
},
"checkin_image_info": {
"created": checkin_image is not None,
"image_id": checkin_image.id if checkin_image else None
}, },
"gps_info": { "gps_info": {
"latitude": exif_data['latitude'], "latitude": exif_data['latitude'],