almost finish migrate new circumstances
This commit is contained in:
304
rog/views_apis/api_bulk_upload.py
Normal file
304
rog/views_apis/api_bulk_upload.py
Normal file
@ -0,0 +1,304 @@
|
||||
"""
|
||||
写真一括アップロード機能
|
||||
写真の位置情報と撮影時刻を使用してチェックイン処理を行う
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.contrib.gis.measure import Distance
|
||||
from PIL import Image
|
||||
from PIL.ExifTags import TAGS
|
||||
import piexif
|
||||
|
||||
from rog.models import NewEvent2, Entry, GpsCheckin, Location2025, Checkpoint
|
||||
from rog.services.s3_service import S3Service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def extract_gps_from_image(image_file):
|
||||
"""
|
||||
画像からGPS情報と撮影時刻を抽出
|
||||
"""
|
||||
try:
|
||||
image = Image.open(image_file)
|
||||
exif_data = piexif.load(image.info.get('exif', b''))
|
||||
|
||||
gps_info = {}
|
||||
datetime_info = None
|
||||
|
||||
# GPS情報の抽出
|
||||
if 'GPS' in exif_data:
|
||||
gps_data = exif_data['GPS']
|
||||
|
||||
# 緯度の取得
|
||||
if piexif.GPSIFD.GPSLatitude in gps_data and piexif.GPSIFD.GPSLatitudeRef in gps_data:
|
||||
lat = gps_data[piexif.GPSIFD.GPSLatitude]
|
||||
lat_ref = gps_data[piexif.GPSIFD.GPSLatitudeRef].decode('utf-8')
|
||||
latitude = lat[0][0]/lat[0][1] + lat[1][0]/lat[1][1]/60 + lat[2][0]/lat[2][1]/3600
|
||||
if lat_ref == 'S':
|
||||
latitude = -latitude
|
||||
gps_info['latitude'] = latitude
|
||||
|
||||
# 経度の取得
|
||||
if piexif.GPSIFD.GPSLongitude in gps_data and piexif.GPSIFD.GPSLongitudeRef in gps_data:
|
||||
lon = gps_data[piexif.GPSIFD.GPSLongitude]
|
||||
lon_ref = gps_data[piexif.GPSIFD.GPSLongitudeRef].decode('utf-8')
|
||||
longitude = lon[0][0]/lon[0][1] + lon[1][0]/lon[1][1]/60 + lon[2][0]/lon[2][1]/3600
|
||||
if lon_ref == 'W':
|
||||
longitude = -longitude
|
||||
gps_info['longitude'] = longitude
|
||||
|
||||
# 撮影時刻の抽出
|
||||
if 'Exif' in exif_data:
|
||||
exif_ifd = exif_data['Exif']
|
||||
if piexif.ExifIFD.DateTimeOriginal in exif_ifd:
|
||||
datetime_str = exif_ifd[piexif.ExifIFD.DateTimeOriginal].decode('utf-8')
|
||||
datetime_info = datetime.strptime(datetime_str, '%Y:%m:%d %H:%M:%S')
|
||||
|
||||
return gps_info, datetime_info
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error extracting GPS/datetime from image: {str(e)}")
|
||||
return {}, None
|
||||
|
||||
|
||||
def find_nearby_checkpoint(latitude, longitude, event, radius_meters=50):
|
||||
"""
|
||||
位置情報から近くのチェックポイントを検索
|
||||
"""
|
||||
try:
|
||||
point = Point(longitude, latitude, srid=4326)
|
||||
|
||||
# Location2025モデルからチェックポイントを検索
|
||||
nearby_checkpoints = Location2025.objects.filter(
|
||||
event=event,
|
||||
location__distance_lte=(point, Distance(m=radius_meters))
|
||||
).order_by('location__distance')
|
||||
|
||||
if nearby_checkpoints.exists():
|
||||
return nearby_checkpoints.first()
|
||||
|
||||
# 従来のCheckpointモデルからも検索
|
||||
nearby_legacy_checkpoints = Checkpoint.objects.filter(
|
||||
event=event,
|
||||
location__distance_lte=(point, Distance(m=radius_meters))
|
||||
).order_by('location__distance')
|
||||
|
||||
if nearby_legacy_checkpoints.exists():
|
||||
return nearby_legacy_checkpoints.first()
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error finding nearby checkpoint: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def bulk_upload_photos(request):
|
||||
"""
|
||||
写真一括アップロード処理
|
||||
|
||||
POST /api/bulk-upload-photos/
|
||||
{
|
||||
"event_code": "FC岐阜",
|
||||
"zekken_number": "123",
|
||||
"images": [<files>]
|
||||
}
|
||||
"""
|
||||
try:
|
||||
event_code = request.data.get('event_code')
|
||||
zekken_number = request.data.get('zekken_number')
|
||||
images = request.FILES.getlist('images')
|
||||
|
||||
if not all([event_code, zekken_number, images]):
|
||||
return Response({
|
||||
'error': 'event_code, zekken_number, and images are required'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# イベントの確認
|
||||
try:
|
||||
event = NewEvent2.objects.get(event_code=event_code)
|
||||
except NewEvent2.DoesNotExist:
|
||||
return Response({
|
||||
'error': 'Event not found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# エントリーの確認
|
||||
try:
|
||||
entry = Entry.objects.get(event=event, zekken_number=zekken_number)
|
||||
except Entry.DoesNotExist:
|
||||
return Response({
|
||||
'error': 'Team entry not found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
results = {
|
||||
'successful_uploads': [],
|
||||
'failed_uploads': [],
|
||||
'created_checkins': [],
|
||||
'failed_checkins': []
|
||||
}
|
||||
|
||||
s3_service = S3Service()
|
||||
|
||||
with transaction.atomic():
|
||||
for image in images:
|
||||
image_result = {
|
||||
'filename': image.name,
|
||||
'status': 'processing'
|
||||
}
|
||||
|
||||
try:
|
||||
# GPS情報と撮影時刻を抽出
|
||||
gps_info, capture_time = extract_gps_from_image(image)
|
||||
|
||||
# S3にアップロード
|
||||
s3_url = s3_service.upload_checkin_image(
|
||||
image_file=image,
|
||||
event_code=event_code,
|
||||
team_code=str(zekken_number),
|
||||
cp_number=0 # 後で更新
|
||||
)
|
||||
|
||||
image_result['s3_url'] = s3_url
|
||||
image_result['gps_info'] = gps_info
|
||||
image_result['capture_time'] = capture_time.isoformat() if capture_time else None
|
||||
|
||||
# GPS情報がある場合、近くのチェックポイントを検索
|
||||
if gps_info.get('latitude') and gps_info.get('longitude'):
|
||||
checkpoint = find_nearby_checkpoint(
|
||||
gps_info['latitude'],
|
||||
gps_info['longitude'],
|
||||
event
|
||||
)
|
||||
|
||||
if checkpoint:
|
||||
# チェックイン記録の作成
|
||||
# 既存のチェックイン記録数を取得して順序を決定
|
||||
existing_count = GpsCheckin.objects.filter(
|
||||
zekken_number=str(zekken_number),
|
||||
event_code=event_code
|
||||
).count()
|
||||
|
||||
checkin = GpsCheckin.objects.create(
|
||||
zekken_number=str(zekken_number),
|
||||
event_code=event_code,
|
||||
cp_number=checkpoint.cp_number,
|
||||
path_order=existing_count + 1,
|
||||
lattitude=gps_info['latitude'],
|
||||
longitude=gps_info['longitude'],
|
||||
image_address=s3_url,
|
||||
create_at=capture_time or timezone.now(),
|
||||
validate_location=False, # 初期状態では未確定
|
||||
buy_flag=False,
|
||||
points=checkpoint.cp_point if hasattr(checkpoint, 'cp_point') else 0,
|
||||
create_user=request.user.email if request.user.is_authenticated else None
|
||||
)
|
||||
|
||||
image_result['checkpoint'] = {
|
||||
'cp_number': checkpoint.cp_number,
|
||||
'cp_name': checkpoint.cp_name,
|
||||
'points': checkin.points
|
||||
}
|
||||
image_result['checkin_id'] = checkin.id
|
||||
results['created_checkins'].append(image_result)
|
||||
else:
|
||||
image_result['error'] = 'No nearby checkpoint found'
|
||||
results['failed_checkins'].append(image_result)
|
||||
else:
|
||||
image_result['error'] = 'No GPS information in image'
|
||||
results['failed_checkins'].append(image_result)
|
||||
|
||||
image_result['status'] = 'success'
|
||||
results['successful_uploads'].append(image_result)
|
||||
|
||||
except Exception as e:
|
||||
image_result['status'] = 'failed'
|
||||
image_result['error'] = str(e)
|
||||
results['failed_uploads'].append(image_result)
|
||||
logger.error(f"Error processing image {image.name}: {str(e)}")
|
||||
|
||||
return Response({
|
||||
'status': 'completed',
|
||||
'summary': {
|
||||
'total_images': len(images),
|
||||
'successful_uploads': len(results['successful_uploads']),
|
||||
'failed_uploads': len(results['failed_uploads']),
|
||||
'created_checkins': len(results['created_checkins']),
|
||||
'failed_checkins': len(results['failed_checkins'])
|
||||
},
|
||||
'results': results
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in bulk_upload_photos: {str(e)}")
|
||||
return Response({
|
||||
'error': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def confirm_checkin_validation(request):
|
||||
"""
|
||||
通過情報の確定・否確定処理
|
||||
|
||||
POST /api/confirm-checkin-validation/
|
||||
{
|
||||
"checkin_ids": [1, 2, 3],
|
||||
"validation_status": true/false
|
||||
}
|
||||
"""
|
||||
try:
|
||||
checkin_ids = request.data.get('checkin_ids', [])
|
||||
validation_status = request.data.get('validation_status')
|
||||
|
||||
if not checkin_ids or validation_status is None:
|
||||
return Response({
|
||||
'error': 'checkin_ids and validation_status are required'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
updated_checkins = []
|
||||
|
||||
with transaction.atomic():
|
||||
for checkin_id in checkin_ids:
|
||||
try:
|
||||
checkin = GpsCheckin.objects.get(id=checkin_id)
|
||||
checkin.validate_location = validation_status
|
||||
checkin.update_user = request.user.email if request.user.is_authenticated else None
|
||||
checkin.update_at = timezone.now()
|
||||
checkin.save()
|
||||
|
||||
updated_checkins.append({
|
||||
'id': checkin.id,
|
||||
'cp_number': checkin.cp_number,
|
||||
'validation_status': checkin.validate_location
|
||||
})
|
||||
|
||||
except GpsCheckin.DoesNotExist:
|
||||
logger.warning(f"Checkin with ID {checkin_id} not found")
|
||||
continue
|
||||
|
||||
return Response({
|
||||
'status': 'success',
|
||||
'message': f'{len(updated_checkins)} checkins updated',
|
||||
'updated_checkins': updated_checkins
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in confirm_checkin_validation: {str(e)}")
|
||||
return Response({
|
||||
'error': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
Reference in New Issue
Block a user