Finish basic API implementation
This commit is contained in:
240
rog/location_checkin_view.py
Normal file
240
rog/location_checkin_view.py
Normal file
@ -0,0 +1,240 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user