""" Location checkin view with evaluation_value based interaction logic """ from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views import View import json import logging logger = logging.getLogger(__name__) @method_decorator(csrf_exempt, name='dispatch') @method_decorator(login_required, name='dispatch') class LocationCheckinView(View): """ evaluation_valueに基づく拡張チェックイン処理 """ def post(self, request): """ ロケーションチェックイン処理 Request body: { "location_id": int, "latitude": float, "longitude": float, "photo": str (base64) - evaluation_value=1の場合必須, "qr_code_data": str - evaluation_value=2の場合必須, "quiz_answer": str - evaluation_value=2の場合必須 } """ try: data = json.loads(request.body) location_id = data.get('location_id') user_lat = data.get('latitude') user_lon = data.get('longitude') if not all([location_id, user_lat, user_lon]): return JsonResponse({ 'success': False, 'error': 'location_id, latitude, longitude are required' }, status=400) # ロケーション取得 from .models import Location try: location = Location.objects.get(id=location_id) except Location.DoesNotExist: return JsonResponse({ 'success': False, 'error': 'Location not found' }, status=404) # 距離チェック if not self._is_within_checkin_radius(location, user_lat, user_lon): return JsonResponse({ 'success': False, 'error': 'Too far from location', 'required_radius': location.checkin_radius or 15.0 }, status=400) # evaluation_valueに基づく要件検証 from .location_interaction import validate_interaction_requirements validation_result = validate_interaction_requirements(location, data) if not validation_result['valid']: return JsonResponse({ 'success': False, 'error': 'Interaction requirements not met', 'errors': validation_result['errors'] }, status=400) # インタラクション処理 interaction_result = self._process_interaction(location, data) # ポイント計算 from .location_interaction import get_point_calculation point_info = get_point_calculation(location, interaction_result) # チェックイン記録保存 checkin_record = self._save_checkin_record( request.user, location, user_lat, user_lon, interaction_result, point_info ) # レスポンス response_data = { 'success': True, 'checkin_id': checkin_record.id, 'points_awarded': point_info['points_awarded'], 'point_type': point_info['point_type'], 'message': point_info['message'], 'location_name': location.location_name, 'interaction_type': location.evaluation_value or "0", } # インタラクション結果の詳細を追加 if interaction_result: response_data['interaction_result'] = interaction_result return JsonResponse(response_data) except json.JSONDecodeError: return JsonResponse({ 'success': False, 'error': 'Invalid JSON data' }, status=400) except Exception as e: logger.error(f"Checkin error: {e}") return JsonResponse({ 'success': False, 'error': 'Internal server error' }, status=500) def _is_within_checkin_radius(self, location, user_lat, user_lon): """チェックイン範囲内かどうかを判定""" from math import radians, cos, sin, asin, sqrt # ロケーションの座標を取得 if location.geom and location.geom.coords: loc_lon, loc_lat = location.geom.coords[0][:2] else: loc_lat = location.latitude loc_lon = location.longitude if not all([loc_lat, loc_lon]): return False # Haversine公式で距離計算 def haversine(lon1, lat1, lon2, lat2): lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2]) dlon = lon2 - lon1 dlat = lat2 - lat1 a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 c = 2 * asin(sqrt(a)) r = 6371000 # 地球の半径(メートル) return c * r distance = haversine(loc_lon, loc_lat, user_lon, user_lat) allowed_radius = location.checkin_radius or 15.0 return distance <= allowed_radius def _process_interaction(self, location, data): """evaluation_valueに基づくインタラクション処理""" evaluation_value = location.evaluation_value or "0" result = {} if evaluation_value == "1": # 写真撮影処理 photo_data = data.get('photo') if photo_data: result['photo_saved'] = True result['photo_filename'] = self._save_photo(photo_data, location) elif evaluation_value == "2": # QRコード + クイズ処理 qr_data = data.get('qr_code_data') quiz_answer = data.get('quiz_answer') if qr_data and quiz_answer: result['qr_scanned'] = True result['quiz_answer'] = quiz_answer result['quiz_correct'] = self._check_quiz_answer(qr_data, quiz_answer) return result def _save_photo(self, photo_data, location): """写真データを保存(実装は要調整)""" import base64 import os from django.conf import settings from datetime import datetime try: # Base64デコード photo_binary = base64.b64decode(photo_data) # ファイル名生成 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') filename = f"checkin_{location.id}_{timestamp}.jpg" # 保存先ディレクトリ photo_dir = os.path.join(settings.MEDIA_ROOT, 'checkin_photos') os.makedirs(photo_dir, exist_ok=True) # ファイル保存 file_path = os.path.join(photo_dir, filename) with open(file_path, 'wb') as f: f.write(photo_binary) return filename except Exception as e: logger.error(f"Photo save error: {e}") return None def _check_quiz_answer(self, qr_data, quiz_answer): """クイズ回答の正答チェック(実装は要調整)""" # QRコードデータから正答を取得 # 実際の実装では、QRコードに含まれるクイズIDから正答を取得 try: import json qr_info = json.loads(qr_data) correct_answer = qr_info.get('correct_answer', '').lower() user_answer = quiz_answer.lower().strip() return correct_answer == user_answer except (json.JSONDecodeError, KeyError): # QRデータの形式が不正な場合はデフォルトで不正解 return False def _save_checkin_record(self, user, location, lat, lon, interaction_result, point_info): """チェックイン記録を保存""" from .models import Useractions from datetime import datetime # Useractionsレコード作成/更新 checkin_record, created = Useractions.objects.get_or_create( user=user, location=location, defaults={ 'checkin': True, 'created_at': datetime.now(), 'last_updated_at': datetime.now() } ) if not created: checkin_record.checkin = True checkin_record.last_updated_at = datetime.now() checkin_record.save() return checkin_record